Skip to content

Commit b172a20

Browse files
committed
PHPC-1140: Implement Transactions specification
1 parent 0f2e387 commit b172a20

16 files changed

+665
-34
lines changed

.travis.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ matrix:
3232
- php: 7.0
3333
env:
3434
- SERVER_VERSION=3.4.11
35+
- php: 7.0
36+
env:
37+
- SERVER_VERSION=4.0.0-rc4
3538

3639
before_install:
3740
- pip install "mongo-orchestration>=0.6.7,<1.0" --user `whoami`

php_phongo.c

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,39 @@ void phongo_throw_exception(php_phongo_error_domain_t domain TSRMLS_DC, const ch
180180
efree(message);
181181
va_end(args);
182182
}
183+
183184
void phongo_throw_exception_from_bson_error_t(bson_error_t* error TSRMLS_DC)
184185
{
185186
zend_throw_exception(phongo_exception_from_mongoc_domain(error->domain, error->code), error->message, error->code TSRMLS_CC);
186187
}
187188

189+
void phongo_throw_exception_from_bson_error_and_reply_t(bson_error_t* error, bson_t* reply TSRMLS_DC)
190+
{
191+
/* Server errors (other than ExceededTimeLimit) and write concern errors
192+
* may use CommandException and report the result document for the
193+
* failed command. For BC, ExceededTimeLimit errors will continue to use
194+
* ExcecutionTimeoutException and omit the result document. */
195+
if ((error->domain == MONGOC_ERROR_SERVER && error->code != PHONGO_SERVER_ERROR_EXCEEDED_TIME_LIMIT) || error->domain == MONGOC_ERROR_WRITE_CONCERN) {
196+
#if PHP_VERSION_ID >= 70000
197+
zval zv;
198+
#else
199+
zval* zv;
200+
#endif
201+
202+
zend_throw_exception(php_phongo_commandexception_ce, error->message, error->code TSRMLS_CC);
203+
php_phongo_bson_to_zval(bson_get_data(reply), reply->len, &zv);
204+
205+
#if PHP_VERSION_ID >= 70000
206+
phongo_add_exception_prop(ZEND_STRL("resultDocument"), &zv);
207+
#else
208+
phongo_add_exception_prop(ZEND_STRL("resultDocument"), zv TSRMLS_CC);
209+
#endif
210+
zval_ptr_dtor(&zv);
211+
} else {
212+
phongo_throw_exception_from_bson_error_t(error TSRMLS_CC);
213+
}
214+
}
215+
188216
static void php_phongo_log(mongoc_log_level_t log_level, const char* log_domain, const char* message, void* user_data)
189217
{
190218
struct timeval tv;
@@ -927,29 +955,7 @@ bool phongo_execute_command(mongoc_client_t* client, php_phongo_command_type_t t
927955
free_reply = true;
928956

929957
if (!result) {
930-
/* Server errors (other than ExceededTimeLimit) and write concern errors
931-
* may use CommandException and report the result document for the
932-
* failed command. For BC, ExceededTimeLimit errors will continue to use
933-
* ExcecutionTimeoutException and omit the result document. */
934-
if ((error.domain == MONGOC_ERROR_SERVER && error.code != PHONGO_SERVER_ERROR_EXCEEDED_TIME_LIMIT) || error.domain == MONGOC_ERROR_WRITE_CONCERN) {
935-
#if PHP_VERSION_ID >= 70000
936-
zval zv;
937-
#else
938-
zval* zv;
939-
#endif
940-
941-
zend_throw_exception(php_phongo_commandexception_ce, error.message, error.code TSRMLS_CC);
942-
php_phongo_bson_to_zval(bson_get_data(&reply), reply.len, &zv);
943-
944-
#if PHP_VERSION_ID >= 70000
945-
phongo_add_exception_prop(ZEND_STRL("resultDocument"), &zv);
946-
#else
947-
phongo_add_exception_prop(ZEND_STRL("resultDocument"), zv TSRMLS_CC);
948-
#endif
949-
zval_ptr_dtor(&zv);
950-
} else {
951-
phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC);
952-
}
958+
phongo_throw_exception_from_bson_error_and_reply_t(&error, &reply TSRMLS_CC);
953959
goto cleanup;
954960
}
955961

php_phongo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ void phongo_throw_exception(php_phongo_error_domain_t domain TSRMLS
111111
#endif /* PHP_VERSION_ID < 70000 */
112112
;
113113
void phongo_throw_exception_from_bson_error_t(bson_error_t* error TSRMLS_DC);
114+
void phongo_throw_exception_from_bson_error_and_reply_t(bson_error_t* error, bson_t* reply TSRMLS_DC);
114115

115116
/* This enum is used for processing options in phongo_execute_parse_options and
116117
* selecting a libmongoc function to use in phongo_execute_command. The values

scripts/presets/standalone-30.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"logpath": "/tmp/standalone-30/mongod.log",
99
"journal": true,
1010
"port": 2700,
11-
"bind_ip_all": true,
11+
"bind_ip": "0.0.0.0,::",
1212
"setParameter": {"enableTestCommands": 1}
1313
},
1414
"version": "30-release"

src/MongoDB/Manager.c

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "php_array_api.h"
2727
#include "phongo_compat.h"
2828
#include "php_phongo.h"
29+
#include "Session.h"
2930

3031
#define PHONGO_MANAGER_URI_DEFAULT "mongodb://127.0.0.1/"
3132

@@ -655,11 +656,12 @@ static PHP_METHOD(Manager, selectServer)
655656
Returns a new client session */
656657
static PHP_METHOD(Manager, startSession)
657658
{
658-
php_phongo_manager_t* intern;
659-
zval* options = NULL;
660-
mongoc_session_opt_t* cs_opts = NULL;
661-
mongoc_client_session_t* cs;
662-
bson_error_t error = { 0 };
659+
php_phongo_manager_t* intern;
660+
zval* options = NULL;
661+
mongoc_session_opt_t* cs_opts = NULL;
662+
mongoc_client_session_t* cs;
663+
bson_error_t error = { 0 };
664+
mongoc_transaction_opt_t* txn_opts = NULL;
663665
SUPPRESS_UNUSED_WARNING(return_value_ptr)
664666
SUPPRESS_UNUSED_WARNING(return_value_used)
665667

@@ -674,17 +676,51 @@ static PHP_METHOD(Manager, startSession)
674676
mongoc_session_opts_set_causal_consistency(cs_opts, php_array_fetchc_bool(options, "causalConsistency"));
675677
}
676678

677-
cs = mongoc_client_start_session(intern->client, cs_opts, &error);
679+
if (options && php_array_existsc(options, "defaultTransactionOptions")) {
680+
zval* txn_options = php_array_fetchc(options, "defaultTransactionOptions");
678681

679-
if (cs_opts) {
680-
mongoc_session_opts_destroy(cs_opts);
682+
/* Thrown exception and return if the defaultTransactionOptions is not an array */
683+
if (Z_TYPE_P(txn_options) != IS_ARRAY) {
684+
phongo_throw_exception(
685+
PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC,
686+
"Expected \"defaultTransactionOptions\" option to be an array, %s given",
687+
PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(txn_options)
688+
);
689+
goto cleanup;
690+
}
691+
692+
/* Parse transaction options */
693+
txn_opts = php_mongodb_session_parse_transaction_options(txn_options TSRMLS_CC);
694+
695+
/* If an exception is thrown while parsing, the txn_opts struct is also
696+
* NULL, so no need to free it here */
697+
if (EG(exception)) {
698+
goto cleanup;
699+
}
700+
701+
/* If the options are non-empty, add them to the client session opts struct */
702+
if (txn_opts) {
703+
if (!cs_opts) {
704+
cs_opts = mongoc_session_opts_new();
705+
}
706+
707+
mongoc_session_opts_set_default_transaction_opts(cs_opts, txn_opts);
708+
mongoc_transaction_opts_destroy(txn_opts);
709+
}
681710
}
682711

712+
cs = mongoc_client_start_session(intern->client, cs_opts, &error);
713+
683714
if (cs) {
684715
phongo_session_init(return_value, cs TSRMLS_CC);
685716
} else {
686717
phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC);
687718
}
719+
720+
cleanup:
721+
if (cs_opts) {
722+
mongoc_session_opts_destroy(cs_opts);
723+
}
688724
} /* }}} */
689725

690726
/* {{{ MongoDB\Driver\Manager function entries */

src/MongoDB/Session.c

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include "phongo_compat.h"
2525
#include "php_phongo.h"
2626
#include "php_bson.h"
27+
#include "php_array_api.h"
28+
#include "Session.h"
2729

2830
zend_class_entry* php_phongo_session_ce;
2931

@@ -228,6 +230,166 @@ static PHP_METHOD(Session, getOperationTime)
228230
php_phongo_new_timestamp_from_increment_and_timestamp(return_value, increment, timestamp TSRMLS_CC);
229231
} /* }}} */
230232

233+
/* Creates a opts structure from an array optionally containing an RP, RC,
234+
* and/or WC object. Returns NULL if no options were found, or there was an
235+
* invalid option. If there was an invalid option or structure, an exception
236+
* will be thrown too. */
237+
mongoc_transaction_opt_t* php_mongodb_session_parse_transaction_options(zval* options TSRMLS_DC)
238+
{
239+
mongoc_transaction_opt_t* opts = NULL;
240+
241+
if (php_array_existsc(options, "readConcern")) {
242+
zval* read_concern = php_array_fetchc(options, "readConcern");
243+
244+
if (Z_TYPE_P(read_concern) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(read_concern), php_phongo_readconcern_ce TSRMLS_CC)) {
245+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected \"readConcern\" option to be %s, %s given", ZSTR_VAL(php_phongo_readconcern_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(read_concern));
246+
/* Freeing opts is not needed here, as it can't be set yet. The
247+
* code is here to keep it consistent with the others in case more
248+
* options are added before this one. */
249+
if (opts) {
250+
mongoc_transaction_opts_destroy(opts);
251+
}
252+
return NULL;
253+
}
254+
255+
if (!opts) {
256+
opts = mongoc_transaction_opts_new();
257+
}
258+
259+
mongoc_transaction_opts_set_read_concern(opts, phongo_read_concern_from_zval(read_concern TSRMLS_CC));
260+
}
261+
262+
if (php_array_existsc(options, "readPreference")) {
263+
zval* read_preference = php_array_fetchc(options, "readPreference");
264+
265+
if (Z_TYPE_P(read_preference) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(read_preference), php_phongo_readpreference_ce TSRMLS_CC)) {
266+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected \"readPreference\" option to be %s, %s given", ZSTR_VAL(php_phongo_readpreference_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(read_preference));
267+
if (opts) {
268+
mongoc_transaction_opts_destroy(opts);
269+
}
270+
return NULL;
271+
}
272+
273+
if (!opts) {
274+
opts = mongoc_transaction_opts_new();
275+
}
276+
277+
mongoc_transaction_opts_set_read_prefs(opts, phongo_read_preference_from_zval(read_preference TSRMLS_CC));
278+
}
279+
280+
if (php_array_existsc(options, "writeConcern")) {
281+
zval* write_concern = php_array_fetchc(options, "writeConcern");
282+
283+
if (Z_TYPE_P(write_concern) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(write_concern), php_phongo_writeconcern_ce TSRMLS_CC)) {
284+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Expected \"writeConcern\" option to be %s, %s given", ZSTR_VAL(php_phongo_writeconcern_ce->name), PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(write_concern));
285+
if (opts) {
286+
mongoc_transaction_opts_destroy(opts);
287+
}
288+
return NULL;
289+
}
290+
291+
if (!opts) {
292+
opts = mongoc_transaction_opts_new();
293+
}
294+
295+
mongoc_transaction_opts_set_write_concern(opts, phongo_write_concern_from_zval(write_concern TSRMLS_CC));
296+
}
297+
298+
return opts;
299+
}
300+
301+
/* {{{ proto void MongoDB\Driver\Session::startTransaction([array $options = null])
302+
Starts a new transaction */
303+
static PHP_METHOD(Session, startTransaction)
304+
{
305+
php_phongo_session_t* intern;
306+
zval* options = NULL;
307+
mongoc_transaction_opt_t* txn_options = NULL;
308+
bson_error_t error;
309+
SUPPRESS_UNUSED_WARNING(return_value_ptr)
310+
SUPPRESS_UNUSED_WARNING(return_value_used)
311+
312+
intern = Z_SESSION_OBJ_P(getThis());
313+
314+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a", &options) == FAILURE) {
315+
return;
316+
}
317+
318+
if (options) {
319+
txn_options = php_mongodb_session_parse_transaction_options(options TSRMLS_CC);
320+
}
321+
if (EG(exception)) {
322+
return;
323+
}
324+
325+
if (!mongoc_client_session_start_transaction(intern->client_session, txn_options, &error)) {
326+
phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC);
327+
}
328+
329+
if (txn_options) {
330+
mongoc_transaction_opts_destroy(txn_options);
331+
}
332+
} /* }}} */
333+
334+
/* {{{ proto void MongoDB\Driver\Session::commitTransaction(void)
335+
Commits an existing transaction */
336+
static PHP_METHOD(Session, commitTransaction)
337+
{
338+
php_phongo_session_t* intern;
339+
bson_error_t error;
340+
bson_t reply;
341+
SUPPRESS_UNUSED_WARNING(return_value_ptr)
342+
SUPPRESS_UNUSED_WARNING(return_value_used)
343+
344+
intern = Z_SESSION_OBJ_P(getThis());
345+
346+
if (zend_parse_parameters_none() == FAILURE) {
347+
return;
348+
}
349+
350+
if (!mongoc_client_session_commit_transaction(intern->client_session, &reply, &error)) {
351+
phongo_throw_exception_from_bson_error_and_reply_t(&error, &reply TSRMLS_CC);
352+
bson_destroy(&reply);
353+
}
354+
} /* }}} */
355+
356+
/* {{{ proto void MongoDB\Driver\Session::abortTransaction(void)
357+
Aborts (rolls back) an existing transaction */
358+
static PHP_METHOD(Session, abortTransaction)
359+
{
360+
php_phongo_session_t* intern;
361+
bson_error_t error;
362+
SUPPRESS_UNUSED_WARNING(return_value_ptr)
363+
SUPPRESS_UNUSED_WARNING(return_value_used)
364+
365+
intern = Z_SESSION_OBJ_P(getThis());
366+
367+
if (zend_parse_parameters_none() == FAILURE) {
368+
return;
369+
}
370+
371+
if (!mongoc_client_session_abort_transaction(intern->client_session, &error)) {
372+
phongo_throw_exception_from_bson_error_t(&error TSRMLS_CC);
373+
}
374+
} /* }}} */
375+
376+
/* {{{ proto void MongoDB\Driver\Session::endSession(void)
377+
Ends the session, and a running transaction if active */
378+
static PHP_METHOD(Session, endSession)
379+
{
380+
php_phongo_session_t* intern;
381+
SUPPRESS_UNUSED_WARNING(return_value_ptr)
382+
SUPPRESS_UNUSED_WARNING(return_value_used)
383+
384+
intern = Z_SESSION_OBJ_P(getThis());
385+
386+
if (zend_parse_parameters_none() == FAILURE) {
387+
return;
388+
}
389+
390+
mongoc_client_session_destroy(intern->client_session);
391+
} /* }}} */
392+
231393
/* {{{ MongoDB\Driver\Session function entries */
232394
ZEND_BEGIN_ARG_INFO_EX(ai_Session_advanceClusterTime, 0, 0, 1)
233395
ZEND_ARG_INFO(0, clusterTime)
@@ -237,6 +399,10 @@ ZEND_BEGIN_ARG_INFO_EX(ai_Session_advanceOperationTime, 0, 0, 1)
237399
ZEND_ARG_INFO(0, timestamp)
238400
ZEND_END_ARG_INFO()
239401

402+
ZEND_BEGIN_ARG_INFO_EX(ai_Session_startTransaction, 0, 0, 0)
403+
ZEND_ARG_INFO(0, options)
404+
ZEND_END_ARG_INFO()
405+
240406
ZEND_BEGIN_ARG_INFO_EX(ai_Session_void, 0, 0, 0)
241407
ZEND_END_ARG_INFO()
242408

@@ -247,6 +413,10 @@ static zend_function_entry php_phongo_session_me[] = {
247413
PHP_ME(Session, getClusterTime, ai_Session_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
248414
PHP_ME(Session, getLogicalSessionId, ai_Session_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
249415
PHP_ME(Session, getOperationTime, ai_Session_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
416+
PHP_ME(Session, startTransaction, ai_Session_startTransaction, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
417+
PHP_ME(Session, commitTransaction, ai_Session_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
418+
PHP_ME(Session, abortTransaction, ai_Session_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
419+
PHP_ME(Session, endSession, ai_Session_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
250420
ZEND_NAMED_ME(__construct, PHP_FN(MongoDB_disabled___construct), ai_Session_void, ZEND_ACC_PRIVATE | ZEND_ACC_FINAL)
251421
ZEND_NAMED_ME(__wakeup, PHP_FN(MongoDB_disabled___wakeup), ai_Session_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
252422
PHP_FE_END

src/MongoDB/Session.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 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+
#ifndef PHP_MONGODB_DRIVER_SESSION_H
18+
#define PHP_MONGODB_DRIVER_SESSION_H
19+
20+
mongoc_transaction_opt_t* php_mongodb_session_parse_transaction_options(zval* txnOptions TSRMLS_DC);
21+
22+
#endif /* PHP_MONGODB_DRIVER_SESSION_H */

0 commit comments

Comments
 (0)