1 /*
2  * Copyright 2014-2017 MongoDB, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <php.h>
18 #include <Zend/zend_hash.h>
19 #include <Zend/zend_interfaces.h>
20 #include <ext/standard/file.h>
21 
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "php_array_api.h"
27 #include "phongo_compat.h"
28 #include "php_phongo.h"
29 #include "Session.h"
30 
31 #define PHONGO_MANAGER_URI_DEFAULT "mongodb://127.0.0.1/"
32 
33 /**
34  * Manager abstracts a cluster of Server objects (i.e. socket connections).
35  *
36  * Typically, users will connect to a cluster using a URI, and the Manager will
37  * perform tasks such as replica set discovery and create the necessary Server
38  * objects. That said, it is also possible to create a Manager with an arbitrary
39  * collection of Server objects using the static factory method (this can be
40  * useful for testing or administration).
41  *
42  * Operation methods do not take socket-level options (e.g. socketTimeoutMS).
43  * Those options should be specified during construction.
44  */
45 zend_class_entry* php_phongo_manager_ce;
46 
47 /* Checks if driverOptions contains a stream context resource in the "context"
48  * key and incorporates any of its SSL options into the base array that did not
49  * already exist (i.e. array union). The "context" key is then unset from the
50  * base array.
51  *
52  * This handles the merging of any legacy SSL context options and also makes
53  * driverOptions suitable for serialization by removing the resource zval. */
php_phongo_manager_merge_context_options(zval * zdriverOptions)54 static bool php_phongo_manager_merge_context_options(zval* zdriverOptions) /* {{{ */
55 {
56 	php_stream_context* context;
57 	zval *              zcontext, *zcontextOptions;
58 
59 	if (!php_array_existsc(zdriverOptions, "context")) {
60 		return true;
61 	}
62 
63 	zcontext = php_array_fetchc(zdriverOptions, "context");
64 	context  = php_stream_context_from_zval(zcontext, 1);
65 
66 	if (!context) {
67 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "\"context\" driver option is not a valid Stream-Context resource");
68 		return false;
69 	}
70 
71 	zcontextOptions = php_array_fetchc_array(&context->options, "ssl");
72 
73 	if (!zcontextOptions) {
74 		phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Stream-Context resource does not contain \"ssl\" options array");
75 		return false;
76 	}
77 
78 	/* When running PHP in debug mode, php_error_docref duplicates the current
79 	 * scope, leading to a COW violation in zend_hash_merge and
80 	 * zend_symtable_str_del (called by php_array_unsetc). This macro allows
81 	 * that violation in debug mode and is a NOOP when in non-debug. */
82 #if PHP_VERSION_ID >= 70200
83 	HT_ALLOW_COW_VIOLATION(Z_ARRVAL_P(zdriverOptions));
84 #endif
85 
86 	php_error_docref(NULL, E_DEPRECATED, "The \"context\" driver option is deprecated.");
87 
88 	/* Perform array union (see: add_function() in zend_operators.c) */
89 	zend_hash_merge(Z_ARRVAL_P(zdriverOptions), Z_ARRVAL_P(zcontextOptions), zval_add_ref, 0);
90 
91 	php_array_unsetc(zdriverOptions, "context");
92 
93 	return true;
94 } /* }}} */
95 
96 /* Prepare authMechanismProperties for BSON encoding by converting a boolean
97  * value for the "CANONICALIZE_HOST_NAME" option to a string.
98  *
99  * Note: URI options are case-insensitive, so we must iterate through the
100  * HashTable in order to detect options. */
php_phongo_manager_prep_authmechanismproperties(zval * properties)101 static void php_phongo_manager_prep_authmechanismproperties(zval* properties) /* {{{ */
102 {
103 	HashTable* ht_data;
104 
105 	if (Z_TYPE_P(properties) != IS_ARRAY && Z_TYPE_P(properties) != IS_OBJECT) {
106 		return;
107 	}
108 
109 	ht_data = HASH_OF(properties);
110 
111 	{
112 		zend_string* string_key = NULL;
113 		zend_ulong   num_key    = 0;
114 		zval*        property;
115 
116 		ZEND_HASH_FOREACH_KEY_VAL_IND(ht_data, num_key, string_key, property)
117 		{
118 			if (!string_key) {
119 				continue;
120 			}
121 
122 			/* URI options are case-insensitive */
123 			if (!strcasecmp(ZSTR_VAL(string_key), "CANONICALIZE_HOST_NAME")) {
124 				ZVAL_DEREF(property);
125 				if (Z_TYPE_P(property) != IS_STRING && zend_is_true(property)) {
126 					SEPARATE_ZVAL_NOREF(property);
127 					ZVAL_NEW_STR(property, zend_string_init(ZEND_STRL("true"), 0));
128 				}
129 			}
130 		}
131 		ZEND_HASH_FOREACH_END();
132 	}
133 } /* }}} */
134 
135 /* Prepare URI options for BSON encoding.
136  *
137  * Read preference tag sets must be an array of documents. In order to ensure
138  * that empty arrays serialize as empty documents, array elements will be
139  * converted to objects. php_phongo_read_preference_tags_are_valid() handles
140  * actual validation of the tag set structure.
141  *
142  * Auth mechanism properties must have string values, so a boolean true value
143  * for the "CANONICALIZE_HOST_NAME" property will be converted to "true".
144  *
145  * Note: URI options are case-insensitive, so we must iterate through the
146  * HashTable in order to detect options. */
php_phongo_manager_prep_uri_options(zval * options)147 static void php_phongo_manager_prep_uri_options(zval* options) /* {{{ */
148 {
149 	HashTable* ht_data;
150 
151 	if (Z_TYPE_P(options) != IS_ARRAY) {
152 		return;
153 	}
154 
155 	ht_data = HASH_OF(options);
156 
157 	{
158 		zend_string* string_key = NULL;
159 		zend_ulong   num_key    = 0;
160 		zval*        option;
161 
162 		ZEND_HASH_FOREACH_KEY_VAL_IND(ht_data, num_key, string_key, option)
163 		{
164 			if (!string_key) {
165 				continue;
166 			}
167 
168 			if (!strcasecmp(ZSTR_VAL(string_key), MONGOC_URI_READPREFERENCETAGS)) {
169 				ZVAL_DEREF(option);
170 				SEPARATE_ZVAL_NOREF(option);
171 				php_phongo_read_preference_prep_tagsets(option);
172 				continue;
173 			}
174 
175 			if (!strcasecmp(ZSTR_VAL(string_key), MONGOC_URI_AUTHMECHANISMPROPERTIES)) {
176 				ZVAL_DEREF(option);
177 				SEPARATE_ZVAL_NOREF(option);
178 				php_phongo_manager_prep_authmechanismproperties(option);
179 				continue;
180 			}
181 		}
182 		ZEND_HASH_FOREACH_END();
183 	}
184 } /* }}} */
185 
186 /* Selects a server for an execute method. If "for_writes" is true, a primary
187  * will be selected. Otherwise, a read preference will be used to select the
188  * server. If zreadPreference is NULL, the client's read preference will be
189  * used. If zsession is a session object in a sharded transaction, the session
190  * will be checked whether it is pinned to a server. If so, that server will be
191  * selected. Otherwise, server selection
192  *
193  * On success, server_id will be set and the function will return true;
194  * otherwise, false is returned and an exception is thrown. */
php_phongo_manager_select_server(bool for_writes,bool inherit_read_preference,zval * zreadPreference,zval * zsession,mongoc_client_t * client,uint32_t * server_id)195 static bool php_phongo_manager_select_server(bool for_writes, bool inherit_read_preference, zval* zreadPreference, zval* zsession, mongoc_client_t* client, uint32_t* server_id) /* {{{ */
196 {
197 	mongoc_server_description_t* selected_server;
198 	const mongoc_read_prefs_t*   read_preference = NULL;
199 	bson_error_t                 error           = { 0 };
200 
201 	if (zsession) {
202 		const mongoc_client_session_t* session = Z_SESSION_OBJ_P(zsession)->client_session;
203 
204 		/* Attempt to fetch server pinned to session */
205 		if (mongoc_client_session_get_server_id(session) > 0) {
206 			*server_id = mongoc_client_session_get_server_id(session);
207 
208 			return true;
209 		}
210 	}
211 
212 	if (!for_writes) {
213 		if (zreadPreference) {
214 			read_preference = phongo_read_preference_from_zval(zreadPreference);
215 		} else if (inherit_read_preference) {
216 			read_preference = mongoc_client_get_read_prefs(client);
217 		}
218 	}
219 
220 	selected_server = mongoc_client_select_server(client, for_writes, read_preference, &error);
221 
222 	if (selected_server) {
223 		*server_id = mongoc_server_description_id(selected_server);
224 		mongoc_server_description_destroy(selected_server);
225 
226 		return true;
227 	}
228 
229 	/* Check for connection related exceptions */
230 	if (!EG(exception)) {
231 		phongo_throw_exception_from_bson_error_t(&error);
232 	}
233 
234 	return false;
235 } /* }}} */
236 
237 /* {{{ proto void MongoDB\Driver\Manager::__construct([string $uri = "mongodb://127.0.0.1/"[, array $options = array()[, array $driverOptions = array()]]])
238    Constructs a new Manager */
PHP_METHOD(Manager,__construct)239 static PHP_METHOD(Manager, __construct)
240 {
241 	zend_error_handling   error_handling;
242 	php_phongo_manager_t* intern;
243 	char*                 uri_string     = NULL;
244 	size_t                uri_string_len = 0;
245 	zval*                 options        = NULL;
246 	zval*                 driverOptions  = NULL;
247 
248 	intern = Z_MANAGER_OBJ_P(getThis());
249 
250 	/* Separate the options and driverOptions zvals, since we may end up
251 	 * modifying them in php_phongo_manager_prep_uri_options() and
252 	 * php_phongo_manager_merge_context_options() below, respectively. */
253 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
254 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!a/!a/!", &uri_string, &uri_string_len, &options, &driverOptions) == FAILURE) {
255 		zend_restore_error_handling(&error_handling);
256 		return;
257 	}
258 	zend_restore_error_handling(&error_handling);
259 
260 	if (options) {
261 		php_phongo_manager_prep_uri_options(options);
262 	}
263 
264 	if (driverOptions && !php_phongo_manager_merge_context_options(driverOptions)) {
265 		/* Exception should already have been thrown */
266 		return;
267 	}
268 
269 	phongo_manager_init(intern, uri_string ? uri_string : PHONGO_MANAGER_URI_DEFAULT, options, driverOptions);
270 
271 	if (intern->client) {
272 		php_phongo_set_monitoring_callbacks(intern->client);
273 	}
274 } /* }}} */
275 
276 /* {{{ proto MongoDB\Driver\ClientEncryption MongoDB\Driver\Manager::createClientEncryption(array $options)
277    Return a ClientEncryption instance */
PHP_METHOD(Manager,createClientEncryption)278 static PHP_METHOD(Manager, createClientEncryption)
279 {
280 	zend_error_handling            error_handling;
281 	php_phongo_manager_t*          intern;
282 	php_phongo_clientencryption_t* clientencryption;
283 	zval*                          options;
284 
285 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
286 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &options) == FAILURE) {
287 		zend_restore_error_handling(&error_handling);
288 		return;
289 	}
290 	zend_restore_error_handling(&error_handling);
291 
292 	intern = Z_MANAGER_OBJ_P(getThis());
293 
294 	object_init_ex(return_value, php_phongo_clientencryption_ce);
295 	clientencryption = Z_CLIENTENCRYPTION_OBJ_P(return_value);
296 
297 	phongo_clientencryption_init(clientencryption, intern->client, options);
298 } /* }}} */
299 
300 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeCommand(string $db, MongoDB\Driver\Command $command[, array $options = null])
301    Execute a Command */
PHP_METHOD(Manager,executeCommand)302 static PHP_METHOD(Manager, executeCommand)
303 {
304 	zend_error_handling   error_handling;
305 	php_phongo_manager_t* intern;
306 	char*                 db;
307 	size_t                db_len;
308 	zval*                 command;
309 	zval*                 options         = NULL;
310 	bool                  free_options    = false;
311 	zval*                 zreadPreference = NULL;
312 	zval*                 zsession        = NULL;
313 	uint32_t              server_id       = 0;
314 
315 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
316 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|z!", &db, &db_len, &command, php_phongo_command_ce, &options) == FAILURE) {
317 		zend_restore_error_handling(&error_handling);
318 		return;
319 	}
320 	zend_restore_error_handling(&error_handling);
321 
322 	intern = Z_MANAGER_OBJ_P(getThis());
323 
324 	options = php_phongo_prep_legacy_option(options, "readPreference", &free_options);
325 
326 	if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
327 		/* Exception should already have been thrown */
328 		goto cleanup;
329 	}
330 
331 	if (!phongo_parse_read_preference(options, &zreadPreference)) {
332 		/* Exception should already have been thrown */
333 		goto cleanup;
334 	}
335 
336 	if (!php_phongo_manager_select_server(false, false, zreadPreference, zsession, intern->client, &server_id)) {
337 		/* Exception should already have been thrown */
338 		goto cleanup;
339 	}
340 
341 	/* If the Manager was created in a different process, reset the client so
342 	 * that cursors created by this process can be differentiated and its
343 	 * session pool is cleared. */
344 	PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
345 
346 	phongo_execute_command(intern->client, PHONGO_COMMAND_RAW, db, command, options, server_id, return_value);
347 
348 cleanup:
349 	if (free_options) {
350 		php_phongo_prep_legacy_option_free(options);
351 	}
352 } /* }}} */
353 
354 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeReadCommand(string $db, MongoDB\Driver\Command $command[, array $options = null])
355    Execute a ReadCommand */
PHP_METHOD(Manager,executeReadCommand)356 static PHP_METHOD(Manager, executeReadCommand)
357 {
358 	zend_error_handling   error_handling;
359 	php_phongo_manager_t* intern;
360 	char*                 db;
361 	size_t                db_len;
362 	zval*                 command;
363 	zval*                 options         = NULL;
364 	zval*                 zreadPreference = NULL;
365 	uint32_t              server_id       = 0;
366 	zval*                 zsession        = NULL;
367 
368 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
369 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|a!", &db, &db_len, &command, php_phongo_command_ce, &options) == FAILURE) {
370 		zend_restore_error_handling(&error_handling);
371 		return;
372 	}
373 	zend_restore_error_handling(&error_handling);
374 
375 	intern = Z_MANAGER_OBJ_P(getThis());
376 
377 	if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
378 		/* Exception should already have been thrown */
379 		return;
380 	}
381 
382 	if (!phongo_parse_read_preference(options, &zreadPreference)) {
383 		/* Exception should already have been thrown */
384 		return;
385 	}
386 
387 	if (!php_phongo_manager_select_server(false, true, zreadPreference, zsession, intern->client, &server_id)) {
388 		/* Exception should already have been thrown */
389 		return;
390 	}
391 
392 	/* If the Manager was created in a different process, reset the client so
393 	 * that cursors created by this process can be differentiated and its
394 	 * session pool is cleared. */
395 	PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
396 
397 	phongo_execute_command(intern->client, PHONGO_COMMAND_READ, db, command, options, server_id, return_value);
398 } /* }}} */
399 
400 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeWriteCommand(string $db, MongoDB\Driver\Command $command[, array $options = null])
401    Execute a WriteCommand */
PHP_METHOD(Manager,executeWriteCommand)402 static PHP_METHOD(Manager, executeWriteCommand)
403 {
404 	zend_error_handling   error_handling;
405 	php_phongo_manager_t* intern;
406 	char*                 db;
407 	size_t                db_len;
408 	zval*                 command;
409 	zval*                 options   = NULL;
410 	uint32_t              server_id = 0;
411 	zval*                 zsession  = NULL;
412 
413 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
414 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|a!", &db, &db_len, &command, php_phongo_command_ce, &options) == FAILURE) {
415 		zend_restore_error_handling(&error_handling);
416 		return;
417 	}
418 	zend_restore_error_handling(&error_handling);
419 
420 	intern = Z_MANAGER_OBJ_P(getThis());
421 
422 	if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
423 		/* Exception should already have been thrown */
424 		return;
425 	}
426 
427 	if (!php_phongo_manager_select_server(true, false, NULL, zsession, intern->client, &server_id)) {
428 		/* Exception should already have been thrown */
429 		return;
430 	}
431 
432 	/* If the Manager was created in a different process, reset the client so
433 	 * that cursors created by this process can be differentiated and its
434 	 * session pool is cleared. */
435 	PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
436 
437 	phongo_execute_command(intern->client, PHONGO_COMMAND_WRITE, db, command, options, server_id, return_value);
438 } /* }}} */
439 
440 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeReadWriteCommand(string $db, MongoDB\Driver\Command $command[, array $options = null])
441    Execute a ReadWriteCommand */
PHP_METHOD(Manager,executeReadWriteCommand)442 static PHP_METHOD(Manager, executeReadWriteCommand)
443 {
444 	zend_error_handling   error_handling;
445 	php_phongo_manager_t* intern;
446 	char*                 db;
447 	size_t                db_len;
448 	zval*                 command;
449 	zval*                 options   = NULL;
450 	uint32_t              server_id = 0;
451 	zval*                 zsession  = NULL;
452 
453 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
454 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|a!", &db, &db_len, &command, php_phongo_command_ce, &options) == FAILURE) {
455 		zend_restore_error_handling(&error_handling);
456 		return;
457 	}
458 	zend_restore_error_handling(&error_handling);
459 
460 	intern = Z_MANAGER_OBJ_P(getThis());
461 
462 	if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
463 		/* Exception should already have been thrown */
464 		return;
465 	}
466 
467 	if (!php_phongo_manager_select_server(true, false, NULL, zsession, intern->client, &server_id)) {
468 		/* Exception should already have been thrown */
469 		return;
470 	}
471 
472 	/* If the Manager was created in a different process, reset the client so
473 	 * that cursors created by this process can be differentiated and its
474 	 * session pool is cleared. */
475 	PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
476 
477 	phongo_execute_command(intern->client, PHONGO_COMMAND_READ_WRITE, db, command, options, server_id, return_value);
478 } /* }}} */
479 
480 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeQuery(string $namespace, MongoDB\Driver\Query $query[, array $options = null])
481    Execute a Query */
PHP_METHOD(Manager,executeQuery)482 static PHP_METHOD(Manager, executeQuery)
483 {
484 	zend_error_handling   error_handling;
485 	php_phongo_manager_t* intern;
486 	char* namespace;
487 	size_t   namespace_len;
488 	zval*    query;
489 	zval*    options         = NULL;
490 	bool     free_options    = false;
491 	zval*    zreadPreference = NULL;
492 	uint32_t server_id       = 0;
493 	zval*    zsession        = NULL;
494 
495 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
496 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|z!", &namespace, &namespace_len, &query, php_phongo_query_ce, &options) == FAILURE) {
497 		zend_restore_error_handling(&error_handling);
498 		return;
499 	}
500 	zend_restore_error_handling(&error_handling);
501 
502 	intern = Z_MANAGER_OBJ_P(getThis());
503 
504 	options = php_phongo_prep_legacy_option(options, "readPreference", &free_options);
505 
506 	if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
507 		/* Exception should already have been thrown */
508 		goto cleanup;
509 	}
510 
511 	if (!phongo_parse_read_preference(options, &zreadPreference)) {
512 		/* Exception should already have been thrown */
513 		goto cleanup;
514 	}
515 
516 	if (!php_phongo_manager_select_server(false, true, zreadPreference, zsession, intern->client, &server_id)) {
517 		/* Exception should already have been thrown */
518 		goto cleanup;
519 	}
520 
521 	/* If the Manager was created in a different process, reset the client so
522 	 * that cursors created by this process can be differentiated and its
523 	 * session pool is cleared. */
524 	PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
525 
526 	phongo_execute_query(intern->client, namespace, query, options, server_id, return_value);
527 
528 cleanup:
529 	if (free_options) {
530 		php_phongo_prep_legacy_option_free(options);
531 	}
532 } /* }}} */
533 
534 /* {{{ proto MongoDB\Driver\WriteResult MongoDB\Driver\Manager::executeBulkWrite(string $namespace, MongoDB\Driver\BulkWrite $zbulk[, array $options = null])
535    Executes a BulkWrite (i.e. any number of insert, update, and delete ops) */
PHP_METHOD(Manager,executeBulkWrite)536 static PHP_METHOD(Manager, executeBulkWrite)
537 {
538 	zend_error_handling   error_handling;
539 	php_phongo_manager_t* intern;
540 	char* namespace;
541 	size_t                  namespace_len;
542 	zval*                   zbulk;
543 	php_phongo_bulkwrite_t* bulk;
544 	zval*                   options      = NULL;
545 	bool                    free_options = false;
546 	uint32_t                server_id    = 0;
547 	zval*                   zsession     = NULL;
548 
549 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
550 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|z!", &namespace, &namespace_len, &zbulk, php_phongo_bulkwrite_ce, &options) == FAILURE) {
551 		zend_restore_error_handling(&error_handling);
552 		return;
553 	}
554 	zend_restore_error_handling(&error_handling);
555 
556 	intern = Z_MANAGER_OBJ_P(getThis());
557 	bulk   = Z_BULKWRITE_OBJ_P(zbulk);
558 
559 	options = php_phongo_prep_legacy_option(options, "writeConcern", &free_options);
560 
561 	if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
562 		/* Exception should already have been thrown */
563 		return;
564 	}
565 
566 	if (!php_phongo_manager_select_server(true, false, NULL, zsession, intern->client, &server_id)) {
567 		/* Exception should already have been thrown */
568 		goto cleanup;
569 	}
570 
571 	/* If the Server was created in a different process, reset the client so
572 	 * that its session pool is cleared. */
573 	PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
574 
575 	phongo_execute_bulk_write(intern->client, namespace, bulk, options, server_id, return_value);
576 
577 cleanup:
578 	if (free_options) {
579 		php_phongo_prep_legacy_option_free(options);
580 	}
581 } /* }}} */
582 
583 /* {{{ proto MongoDB\Driver\ReadConcern MongoDB\Driver\Manager::getReadConcern()
584    Returns the ReadConcern associated with this Manager */
PHP_METHOD(Manager,getReadConcern)585 static PHP_METHOD(Manager, getReadConcern)
586 {
587 	zend_error_handling   error_handling;
588 	php_phongo_manager_t* intern;
589 
590 	intern = Z_MANAGER_OBJ_P(getThis());
591 
592 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
593 	if (zend_parse_parameters_none() == FAILURE) {
594 		zend_restore_error_handling(&error_handling);
595 		return;
596 	}
597 	zend_restore_error_handling(&error_handling);
598 
599 	phongo_readconcern_init(return_value, mongoc_client_get_read_concern(intern->client));
600 } /* }}} */
601 
602 /* {{{ proto MongoDB\Driver\ReadPreference MongoDB\Driver\Manager::getReadPreference()
603    Returns the ReadPreference associated with this Manager */
PHP_METHOD(Manager,getReadPreference)604 static PHP_METHOD(Manager, getReadPreference)
605 {
606 	zend_error_handling   error_handling;
607 	php_phongo_manager_t* intern;
608 
609 	intern = Z_MANAGER_OBJ_P(getThis());
610 
611 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
612 	if (zend_parse_parameters_none() == FAILURE) {
613 		zend_restore_error_handling(&error_handling);
614 		return;
615 	}
616 	zend_restore_error_handling(&error_handling);
617 
618 	phongo_readpreference_init(return_value, mongoc_client_get_read_prefs(intern->client));
619 } /* }}} */
620 
621 /* {{{ proto MongoDB\Driver\Server[] MongoDB\Driver\Manager::getServers()
622    Returns the Servers associated with this Manager */
PHP_METHOD(Manager,getServers)623 static PHP_METHOD(Manager, getServers)
624 {
625 	zend_error_handling           error_handling;
626 	php_phongo_manager_t*         intern;
627 	mongoc_server_description_t** sds;
628 	size_t                        i, n = 0;
629 
630 	intern = Z_MANAGER_OBJ_P(getThis());
631 
632 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
633 	if (zend_parse_parameters_none() == FAILURE) {
634 		zend_restore_error_handling(&error_handling);
635 		return;
636 	}
637 	zend_restore_error_handling(&error_handling);
638 
639 	sds = mongoc_client_get_server_descriptions(intern->client, &n);
640 	array_init_size(return_value, n);
641 
642 	for (i = 0; i < n; i++) {
643 		zval obj;
644 
645 		phongo_server_init(&obj, intern->client, mongoc_server_description_id(sds[i]));
646 		add_next_index_zval(return_value, &obj);
647 	}
648 
649 	mongoc_server_descriptions_destroy_all(sds, n);
650 } /* }}} */
651 
652 /* {{{ proto MongoDB\Driver\WriteConcern MongoDB\Driver\Manager::getWriteConcern()
653    Returns the WriteConcern associated with this Manager */
PHP_METHOD(Manager,getWriteConcern)654 static PHP_METHOD(Manager, getWriteConcern)
655 {
656 	zend_error_handling   error_handling;
657 	php_phongo_manager_t* intern;
658 
659 	intern = Z_MANAGER_OBJ_P(getThis());
660 
661 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
662 	if (zend_parse_parameters_none() == FAILURE) {
663 		zend_restore_error_handling(&error_handling);
664 		return;
665 	}
666 	zend_restore_error_handling(&error_handling);
667 
668 	phongo_writeconcern_init(return_value, mongoc_client_get_write_concern(intern->client));
669 } /* }}} */
670 
671 /* {{{ proto MongoDB\Driver\Server MongoDB\Driver\Manager::selectServers(MongoDB\Driver\ReadPreference $readPreference)
672    Returns a suitable Server for the given ReadPreference */
PHP_METHOD(Manager,selectServer)673 static PHP_METHOD(Manager, selectServer)
674 {
675 	zend_error_handling   error_handling;
676 	php_phongo_manager_t* intern;
677 	zval*                 zreadPreference = NULL;
678 	uint32_t              server_id       = 0;
679 
680 	intern = Z_MANAGER_OBJ_P(getThis());
681 
682 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
683 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &zreadPreference, php_phongo_readpreference_ce) == FAILURE) {
684 		zend_restore_error_handling(&error_handling);
685 		return;
686 	}
687 	zend_restore_error_handling(&error_handling);
688 
689 	if (!php_phongo_manager_select_server(false, true, zreadPreference, NULL, intern->client, &server_id)) {
690 		/* Exception should already have been thrown */
691 		return;
692 	}
693 
694 	phongo_server_init(return_value, intern->client, server_id);
695 } /* }}} */
696 
697 /* {{{ proto MongoDB\Driver\Session MongoDB\Driver\Manager::startSession([array $options = null])
698    Returns a new client session */
PHP_METHOD(Manager,startSession)699 static PHP_METHOD(Manager, startSession)
700 {
701 	zend_error_handling       error_handling;
702 	php_phongo_manager_t*     intern;
703 	zval*                     options = NULL;
704 	mongoc_session_opt_t*     cs_opts = NULL;
705 	mongoc_client_session_t*  cs;
706 	bson_error_t              error    = { 0 };
707 	mongoc_transaction_opt_t* txn_opts = NULL;
708 
709 	intern = Z_MANAGER_OBJ_P(getThis());
710 
711 	zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
712 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &options) == FAILURE) {
713 		zend_restore_error_handling(&error_handling);
714 		return;
715 	}
716 	zend_restore_error_handling(&error_handling);
717 
718 	if (options && php_array_existsc(options, "causalConsistency")) {
719 		cs_opts = mongoc_session_opts_new();
720 		mongoc_session_opts_set_causal_consistency(cs_opts, php_array_fetchc_bool(options, "causalConsistency"));
721 	}
722 
723 	if (options && php_array_existsc(options, "defaultTransactionOptions")) {
724 		zval* txn_options = php_array_fetchc(options, "defaultTransactionOptions");
725 
726 		/* Thrown exception and return if the defaultTransactionOptions is not an array */
727 		if (Z_TYPE_P(txn_options) != IS_ARRAY) {
728 			phongo_throw_exception(
729 				PHONGO_ERROR_INVALID_ARGUMENT,
730 				"Expected \"defaultTransactionOptions\" option to be an array, %s given",
731 				PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(txn_options));
732 			goto cleanup;
733 		}
734 
735 		/* Parse transaction options */
736 		txn_opts = php_mongodb_session_parse_transaction_options(txn_options);
737 
738 		/* If an exception is thrown while parsing, the txn_opts struct is also
739 		 * NULL, so no need to free it here */
740 		if (EG(exception)) {
741 			goto cleanup;
742 		}
743 
744 		/* If the options are non-empty, add them to the client session opts struct */
745 		if (txn_opts) {
746 			if (!cs_opts) {
747 				cs_opts = mongoc_session_opts_new();
748 			}
749 
750 			mongoc_session_opts_set_default_transaction_opts(cs_opts, txn_opts);
751 			mongoc_transaction_opts_destroy(txn_opts);
752 		}
753 	}
754 
755 	/* If the Manager was created in a different process, reset the client so
756 	 * that its session pool is cleared. This will ensure that we do not re-use
757 	 * a server session (i.e. LSID) created by a parent process. */
758 	PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
759 
760 	cs = mongoc_client_start_session(intern->client, cs_opts, &error);
761 
762 	if (cs) {
763 		phongo_session_init(return_value, cs);
764 	} else {
765 		phongo_throw_exception_from_bson_error_t(&error);
766 	}
767 
768 cleanup:
769 	if (cs_opts) {
770 		mongoc_session_opts_destroy(cs_opts);
771 	}
772 } /* }}} */
773 
774 /* {{{ MongoDB\Driver\Manager function entries */
775 ZEND_BEGIN_ARG_INFO_EX(ai_Manager___construct, 0, 0, 0)
776 	ZEND_ARG_INFO(0, uri)
777 	ZEND_ARG_ARRAY_INFO(0, options, 0)
778 	ZEND_ARG_ARRAY_INFO(0, driverOptions, 0)
779 ZEND_END_ARG_INFO()
780 
781 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_createClientEncryption, 0, 0, 1)
782 	ZEND_ARG_ARRAY_INFO(0, options, 0)
783 ZEND_END_ARG_INFO()
784 
785 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_executeCommand, 0, 0, 2)
786 	ZEND_ARG_INFO(0, db)
787 	ZEND_ARG_OBJ_INFO(0, command, MongoDB\\Driver\\Command, 0)
788 	ZEND_ARG_INFO(0, options)
789 ZEND_END_ARG_INFO()
790 
791 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_executeRWCommand, 0, 0, 2)
792 	ZEND_ARG_INFO(0, db)
793 	ZEND_ARG_OBJ_INFO(0, command, MongoDB\\Driver\\Command, 0)
794 	ZEND_ARG_ARRAY_INFO(0, options, 0)
795 ZEND_END_ARG_INFO()
796 
797 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_executeQuery, 0, 0, 2)
798 	ZEND_ARG_INFO(0, namespace)
799 	ZEND_ARG_OBJ_INFO(0, zquery, MongoDB\\Driver\\Query, 0)
800 	ZEND_ARG_INFO(0, options)
801 ZEND_END_ARG_INFO()
802 
803 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_executeBulkWrite, 0, 0, 2)
804 	ZEND_ARG_INFO(0, namespace)
805 	ZEND_ARG_OBJ_INFO(0, zbulk, MongoDB\\Driver\\BulkWrite, 0)
806 	ZEND_ARG_INFO(0, options)
807 ZEND_END_ARG_INFO()
808 
809 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_selectServer, 0, 0, 1)
810 	ZEND_ARG_OBJ_INFO(0, readPreference, MongoDB\\Driver\\ReadPreference, 0)
811 ZEND_END_ARG_INFO()
812 
813 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_startSession, 0, 0, 0)
814 	ZEND_ARG_ARRAY_INFO(0, options, 1)
815 ZEND_END_ARG_INFO()
816 
817 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_void, 0, 0, 0)
818 ZEND_END_ARG_INFO()
819 
820 static zend_function_entry php_phongo_manager_me[] = {
821 	/* clang-format off */
822 	PHP_ME(Manager, __construct, ai_Manager___construct, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
823 	PHP_ME(Manager, createClientEncryption, ai_Manager_createClientEncryption, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
824 	PHP_ME(Manager, executeCommand, ai_Manager_executeCommand, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
825 	PHP_ME(Manager, executeReadCommand, ai_Manager_executeRWCommand, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
826 	PHP_ME(Manager, executeWriteCommand, ai_Manager_executeRWCommand, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
827 	PHP_ME(Manager, executeReadWriteCommand, ai_Manager_executeCommand, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
828 	PHP_ME(Manager, executeQuery, ai_Manager_executeQuery, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
829 	PHP_ME(Manager, executeBulkWrite, ai_Manager_executeBulkWrite, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
830 	PHP_ME(Manager, getReadConcern, ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
831 	PHP_ME(Manager, getReadPreference, ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
832 	PHP_ME(Manager, getServers, ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
833 	PHP_ME(Manager, getWriteConcern, ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
834 	PHP_ME(Manager, selectServer, ai_Manager_selectServer, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
835 	PHP_ME(Manager, startSession, ai_Manager_startSession, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
836 	ZEND_NAMED_ME(__wakeup, PHP_FN(MongoDB_disabled___wakeup), ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
837 	PHP_FE_END
838 	/* clang-format on */
839 };
840 /* }}} */
841 
842 /* {{{ MongoDB\Driver\Manager object handlers */
843 static zend_object_handlers php_phongo_handler_manager;
844 
php_phongo_manager_free_object(zend_object * object)845 static void php_phongo_manager_free_object(zend_object* object) /* {{{ */
846 {
847 	php_phongo_manager_t* intern = Z_OBJ_MANAGER(object);
848 
849 	zend_object_std_dtor(&intern->std);
850 
851 	if (intern->client) {
852 		MONGOC_DEBUG("Not destroying persistent client for Manager");
853 		intern->client = NULL;
854 	}
855 
856 	if (intern->client_hash) {
857 		efree(intern->client_hash);
858 	}
859 } /* }}} */
860 
php_phongo_manager_create_object(zend_class_entry * class_type)861 static zend_object* php_phongo_manager_create_object(zend_class_entry* class_type) /* {{{ */
862 {
863 	php_phongo_manager_t* intern = NULL;
864 
865 	intern = PHONGO_ALLOC_OBJECT_T(php_phongo_manager_t, class_type);
866 
867 	zend_object_std_init(&intern->std, class_type);
868 	object_properties_init(&intern->std, class_type);
869 
870 	PHONGO_SET_CREATED_BY_PID(intern);
871 
872 	intern->std.handlers = &php_phongo_handler_manager;
873 
874 	return &intern->std;
875 } /* }}} */
876 
php_phongo_manager_get_debug_info(phongo_compat_object_handler_type * object,int * is_temp)877 static HashTable* php_phongo_manager_get_debug_info(phongo_compat_object_handler_type* object, int* is_temp) /* {{{ */
878 {
879 	php_phongo_manager_t*         intern;
880 	mongoc_server_description_t** sds;
881 	size_t                        i, n = 0;
882 	zval                          retval = ZVAL_STATIC_INIT;
883 	zval                          cluster;
884 
885 	*is_temp = 1;
886 	intern   = Z_OBJ_MANAGER(PHONGO_COMPAT_GET_OBJ(object));
887 
888 	array_init_size(&retval, 2);
889 
890 	ADD_ASSOC_STRING(&retval, "uri", mongoc_uri_get_string(mongoc_client_get_uri(intern->client)));
891 
892 	sds = mongoc_client_get_server_descriptions(intern->client, &n);
893 
894 	array_init_size(&cluster, n);
895 
896 	for (i = 0; i < n; i++) {
897 		zval obj;
898 
899 		if (!php_phongo_server_to_zval(&obj, sds[i])) {
900 			/* Exception already thrown */
901 			zval_ptr_dtor(&obj);
902 			zval_ptr_dtor(&cluster);
903 			goto done;
904 		}
905 
906 		add_next_index_zval(&cluster, &obj);
907 	}
908 
909 	ADD_ASSOC_ZVAL_EX(&retval, "cluster", &cluster);
910 
911 done:
912 	mongoc_server_descriptions_destroy_all(sds, n);
913 
914 	return Z_ARRVAL(retval);
915 } /* }}} */
916 /* }}} */
917 
php_phongo_manager_init_ce(INIT_FUNC_ARGS)918 void php_phongo_manager_init_ce(INIT_FUNC_ARGS) /* {{{ */
919 {
920 	zend_class_entry ce;
921 
922 	INIT_NS_CLASS_ENTRY(ce, "MongoDB\\Driver", "Manager", php_phongo_manager_me);
923 	php_phongo_manager_ce                = zend_register_internal_class(&ce);
924 	php_phongo_manager_ce->create_object = php_phongo_manager_create_object;
925 	PHONGO_CE_FINAL(php_phongo_manager_ce);
926 	PHONGO_CE_DISABLE_SERIALIZATION(php_phongo_manager_ce);
927 
928 	memcpy(&php_phongo_handler_manager, phongo_get_std_object_handlers(), sizeof(zend_object_handlers));
929 	php_phongo_handler_manager.get_debug_info = php_phongo_manager_get_debug_info;
930 	php_phongo_handler_manager.free_obj       = php_phongo_manager_free_object;
931 	php_phongo_handler_manager.offset         = XtOffsetOf(php_phongo_manager_t, std);
932 } /* }}} */
933 
934 /*
935  * Local variables:
936  * tab-width: 4
937  * c-basic-offset: 4
938  * End:
939  * vim600: noet sw=4 ts=4 fdm=marker
940  * vim<600: noet sw=4 ts=4
941  */
942