From 939e96b6dfc598dead82e5790496c494c9fa9d6b Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 19 Dec 2017 15:12:14 -0500 Subject: [PATCH 1/3] PHPC-987: Add operation time to Session --- src/MongoDB/Session.c | 135 ++++++++++++++++++ .../session-advanceOperationTime-001.phpt | 45 ++++++ .../session-advanceOperationTime-002.phpt | 35 +++++ .../session-advanceOperationTime-003.phpt | 53 +++++++ ...ession-advanceOperationTime_error-001.phpt | 90 ++++++++++++ tests/session/session-debug-001.phpt | 4 +- tests/session/session-debug-002.phpt | 9 +- tests/session/session-debug-003.phpt | 2 + .../session/session-getOperationTime-001.phpt | 36 +++++ 9 files changed, 407 insertions(+), 2 deletions(-) create mode 100644 tests/session/session-advanceOperationTime-001.phpt create mode 100644 tests/session/session-advanceOperationTime-002.phpt create mode 100644 tests/session/session-advanceOperationTime-003.phpt create mode 100644 tests/session/session-advanceOperationTime_error-001.phpt create mode 100644 tests/session/session-getOperationTime-001.phpt diff --git a/src/MongoDB/Session.c b/src/MongoDB/Session.c index 3e718b831..f969a73af 100644 --- a/src/MongoDB/Session.c +++ b/src/MongoDB/Session.c @@ -27,6 +27,61 @@ zend_class_entry *php_phongo_session_ce; +static bool php_phongo_session_get_timestamp_parts(zval *obj, uint32_t *timestamp, uint32_t *increment TSRMLS_DC) +{ + bool retval = false; +#if PHP_VERSION_ID >= 70000 + zval ztimestamp; + zval zincrement; + + zend_call_method_with_0_params(obj, NULL, NULL, "getTimestamp", &ztimestamp); + + if (Z_ISUNDEF(ztimestamp) || EG(exception)) { + goto cleanup; + } + + zend_call_method_with_0_params(obj, NULL, NULL, "getIncrement", &zincrement); + + if (Z_ISUNDEF(zincrement) || EG(exception)) { + goto cleanup; + } + + *timestamp = Z_LVAL(ztimestamp); + *increment = Z_LVAL(zincrement); +#else + zval *ztimestamp = NULL; + zval *zincrement = NULL; + + zend_call_method_with_0_params(&obj, NULL, NULL, "getTimestamp", &ztimestamp); + + if (Z_ISUNDEF(ztimestamp) || EG(exception)) { + goto cleanup; + } + + zend_call_method_with_0_params(&obj, NULL, NULL, "getIncrement", &zincrement); + + if (Z_ISUNDEF(zincrement) || EG(exception)) { + goto cleanup; + } + + *timestamp = Z_LVAL_P(ztimestamp); + *increment = Z_LVAL_P(zincrement); +#endif + + retval = true; + +cleanup: + if (!Z_ISUNDEF(ztimestamp)) { + zval_ptr_dtor(&ztimestamp); + } + + if (!Z_ISUNDEF(zincrement)) { + zval_ptr_dtor(&zincrement); + } + + return retval; +} + /* {{{ proto void MongoDB\Driver\Session::advanceClusterTime(array|object $clusterTime) Advances the cluster time for this Session */ static PHP_METHOD(Session, advanceClusterTime) @@ -56,6 +111,30 @@ static PHP_METHOD(Session, advanceClusterTime) bson_destroy(&cluster_time); } /* }}} */ +/* {{{ proto void MongoDB\Driver\Session::advanceOperationTime(MongoDB\BSON\Timestamp $timestamp) + Advances the operation time for this Session */ +static PHP_METHOD(Session, advanceOperationTime) +{ + php_phongo_session_t *intern; + zval *ztimestamp; + uint32_t timestamp = 0; + uint32_t increment = 0; + SUPPRESS_UNUSED_WARNING(return_value_ptr) SUPPRESS_UNUSED_WARNING(return_value_used) + + + intern = Z_SESSION_OBJ_P(getThis()); + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &ztimestamp, php_phongo_timestamp_interface_ce) == FAILURE) { + return; + } + + if (!php_phongo_session_get_timestamp_parts(ztimestamp, ×tamp, &increment TSRMLS_CC)) { + return; + } + + mongoc_client_session_advance_operation_time(intern->client_session, timestamp, increment); +} /* }}} */ + /* {{{ proto object|null MongoDB\Driver\Session::getClusterTime() Returns the cluster time for this Session */ static PHP_METHOD(Session, getClusterTime) @@ -123,18 +202,51 @@ static PHP_METHOD(Session, getLogicalSessionId) #endif } /* }}} */ +/* {{{ proto MongoDB\BSON\Timestamp MongoDB\Driver\Session::getOperationTime() + Returns the operation time for this Session */ +static PHP_METHOD(Session, getOperationTime) +{ + php_phongo_session_t *intern; + uint32_t timestamp, increment; + SUPPRESS_UNUSED_WARNING(return_value_ptr) SUPPRESS_UNUSED_WARNING(return_value_used) + + + intern = Z_SESSION_OBJ_P(getThis()); + + if (zend_parse_parameters_none() == FAILURE) { + return; + } + + mongoc_client_session_get_operation_time(intern->client_session, ×tamp, &increment); + + /* mongoc_client_session_get_operation_time() returns 0 for both parts if + * the session has not been used. According to the causal consistency spec, + * the operation time for an unused session is null. */ + if (timestamp == 0 && increment == 0) { + RETURN_NULL(); + } + + php_phongo_new_timestamp_from_increment_and_timestamp(return_value, increment, timestamp TSRMLS_CC); +} /* }}} */ + /* {{{ MongoDB\Driver\Session function entries */ ZEND_BEGIN_ARG_INFO_EX(ai_Session_advanceClusterTime, 0, 0, 1) ZEND_ARG_INFO(0, clusterTime) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(ai_Session_advanceOperationTime, 0, 0, 1) + ZEND_ARG_INFO(0, timestamp) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(ai_Session_void, 0, 0, 0) ZEND_END_ARG_INFO() static zend_function_entry php_phongo_session_me[] = { PHP_ME(Session, advanceClusterTime, ai_Session_advanceClusterTime, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + PHP_ME(Session, advanceOperationTime, ai_Session_advanceOperationTime, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) PHP_ME(Session, getClusterTime, ai_Session_void, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) PHP_ME(Session, getLogicalSessionId, ai_Session_void, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) + PHP_ME(Session, getOperationTime, ai_Session_void, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) ZEND_NAMED_ME(__construct, PHP_FN(MongoDB_disabled___construct), ai_Session_void, ZEND_ACC_PRIVATE|ZEND_ACC_FINAL) ZEND_NAMED_ME(__wakeup, PHP_FN(MongoDB_disabled___wakeup), ai_Session_void, ZEND_ACC_PUBLIC|ZEND_ACC_FINAL) PHP_FE_END @@ -243,6 +355,29 @@ static HashTable *php_phongo_session_get_debug_info(zval *object, int *is_temp T cs_opts = mongoc_client_session_get_opts(intern->client_session); ADD_ASSOC_BOOL_EX(&retval, "causalConsistency", mongoc_session_opts_get_causal_consistency(cs_opts)); + { + uint32_t timestamp, increment; + + mongoc_client_session_get_operation_time(intern->client_session, ×tamp, &increment); + + if (timestamp && increment) { +#if PHP_VERSION_ID >= 70000 + zval ztimestamp; + + php_phongo_new_timestamp_from_increment_and_timestamp(&ztimestamp, increment, timestamp TSRMLS_CC); + ADD_ASSOC_ZVAL_EX(&retval, "operationTime", &ztimestamp); +#else + zval *ztimestamp; + + MAKE_STD_ZVAL(ztimestamp); + php_phongo_new_timestamp_from_increment_and_timestamp(ztimestamp, increment, timestamp TSRMLS_CC); + ADD_ASSOC_ZVAL_EX(&retval, "operationTime", ztimestamp); +#endif + } else { + ADD_ASSOC_NULL_EX(&retval, "operationTime"); + } + } + return Z_ARRVAL(retval); } /* }}} */ /* }}} */ diff --git a/tests/session/session-advanceOperationTime-001.phpt b/tests/session/session-advanceOperationTime-001.phpt new file mode 100644 index 000000000..6467c8127 --- /dev/null +++ b/tests/session/session-advanceOperationTime-001.phpt @@ -0,0 +1,45 @@ +--TEST-- +MongoDB\Driver\Session::advanceOperationTime() +--SKIPIF-- + + +--FILE-- +startSession(); +$sessionB = $manager->startSession(); + +$command = new MongoDB\Driver\Command(['ping' => 1]); +$manager->executeCommand(DATABASE_NAME, $command, ['session' => $sessionA]); + +echo "Initial operation time of session B:\n"; +var_dump($sessionB->getOperationTime()); + +$sessionB->advanceOperationTime($sessionA->getOperationTime()); + +echo "\nOperation time after advancing session B:\n"; +var_dump($sessionB->getOperationTime()); + +echo "\nSessions A and B have equivalent operation times:\n"; +var_dump($sessionA->getOperationTime() == $sessionB->getOperationTime()); + +?> +===DONE=== + +--EXPECTF-- +Initial operation time of session B: +NULL + +Operation time after advancing session B: +object(MongoDB\BSON\Timestamp)#%d (%d) { + ["increment"]=> + string(%d) "%d" + ["timestamp"]=> + string(%d) "%d" +} + +Sessions A and B have equivalent operation times: +bool(true) +===DONE=== diff --git a/tests/session/session-advanceOperationTime-002.phpt b/tests/session/session-advanceOperationTime-002.phpt new file mode 100644 index 000000000..e590cd610 --- /dev/null +++ b/tests/session/session-advanceOperationTime-002.phpt @@ -0,0 +1,35 @@ +--TEST-- +MongoDB\Driver\Session::advanceOperationTime() with Timestamp +--SKIPIF-- + + +--FILE-- +startSession(); + +echo "Initial operation time of session:\n"; +var_dump($session->getOperationTime()); + +$session->advanceOperationTime(new MongoDB\BSON\Timestamp(5678, 1234)); + +echo "\nOperation time after advancing session:\n"; +var_dump($session->getOperationTime()); + +?> +===DONE=== + +--EXPECTF-- +Initial operation time of session: +NULL + +Operation time after advancing session: +object(MongoDB\BSON\Timestamp)#%d (%d) { + ["increment"]=> + string(4) "5678" + ["timestamp"]=> + string(4) "1234" +} +===DONE=== diff --git a/tests/session/session-advanceOperationTime-003.phpt b/tests/session/session-advanceOperationTime-003.phpt new file mode 100644 index 000000000..34b46c32e --- /dev/null +++ b/tests/session/session-advanceOperationTime-003.phpt @@ -0,0 +1,53 @@ +--TEST-- +MongoDB\Driver\Session::advanceOperationTime() with TimestampInterface +--SKIPIF-- + + +--FILE-- +getIncrement(), $this->getTimestamp()); + } +} + +$manager = new MongoDB\Driver\Manager(REPLICASET); +$session = $manager->startSession(); + +echo "Initial operation time of session:\n"; +var_dump($session->getOperationTime()); + +$session->advanceOperationTime(new MyTimestamp); + +echo "\nOperation time after advancing session:\n"; +var_dump($session->getOperationTime()); + +?> +===DONE=== + +--EXPECTF-- +Initial operation time of session: +NULL + +Operation time after advancing session: +object(MongoDB\BSON\Timestamp)#%d (%d) { + ["increment"]=> + string(4) "5678" + ["timestamp"]=> + string(4) "1234" +} +===DONE=== diff --git a/tests/session/session-advanceOperationTime_error-001.phpt b/tests/session/session-advanceOperationTime_error-001.phpt new file mode 100644 index 000000000..682544771 --- /dev/null +++ b/tests/session/session-advanceOperationTime_error-001.phpt @@ -0,0 +1,90 @@ +--TEST-- +MongoDB\Driver\Session::advanceOperationTime() with TimestampInterface exceptions +--SKIPIF-- + + +--FILE-- +failIncrement = $failIncrement; + $this->failTimestamp = $failTimestamp; + } + + public function getIncrement() + { + if ($this->failIncrement) { + throw new Exception('getIncrement() failed'); + } + + return 5678; + } + + public function getTimestamp() + { + if ($this->failTimestamp) { + throw new Exception('getTimestamp() failed'); + } + + return 1234; + } + + public function __toString() + { + return sprintf('[%d:%d]', $this->getIncrement(), $this->getTimestamp()); + } +} + +$manager = new MongoDB\Driver\Manager(REPLICASET); +$session = $manager->startSession(); + +echo "Initial operation time of session:\n"; +var_dump($session->getOperationTime()); + +$timestamps = [ + new MyTimestamp(true, false), + new MyTimestamp(false, true), + new MyTimestamp(true, true), +]; + +foreach ($timestamps as $timestamp) { + echo "\n", throws(function() use ($session, $timestamp) { + $session->advanceOperationTime($timestamp); + }, 'Exception'), "\n"; + + echo "\nOperation time after advancing session fails:\n"; + var_dump($session->getOperationTime()); +} + +?> +===DONE=== + +--EXPECT-- +Initial operation time of session: +NULL + +OK: Got Exception +getIncrement() failed + +Operation time after advancing session fails: +NULL + +OK: Got Exception +getTimestamp() failed + +Operation time after advancing session fails: +NULL + +OK: Got Exception +getTimestamp() failed + +Operation time after advancing session fails: +NULL +===DONE=== diff --git a/tests/session/session-debug-001.phpt b/tests/session/session-debug-001.phpt index ea8cf86e8..cd70b989d 100644 --- a/tests/session/session-debug-001.phpt +++ b/tests/session/session-debug-001.phpt @@ -1,5 +1,5 @@ --TEST-- -MongoDB\Driver\Session debug output (before processing $clusterTime) +MongoDB\Driver\Session debug output (before an operation) --SKIPIF-- @@ -32,5 +32,7 @@ object(MongoDB\Driver\Session)#%d (%d) { NULL ["causalConsistency"]=> bool(true) + ["operationTime"]=> + NULL } ===DONE=== diff --git a/tests/session/session-debug-002.phpt b/tests/session/session-debug-002.phpt index 133003c88..502d0a87c 100644 --- a/tests/session/session-debug-002.phpt +++ b/tests/session/session-debug-002.phpt @@ -1,5 +1,5 @@ --TEST-- -MongoDB\Driver\Session debug output (after processing $clusterTime) +MongoDB\Driver\Session debug output (after an operation) --SKIPIF-- @@ -44,5 +44,12 @@ object(MongoDB\Driver\Session)#%d (%d) { } ["causalConsistency"]=> bool(true) + ["operationTime"]=> + object(MongoDB\BSON\Timestamp)#%d (%d) { + ["increment"]=> + string(%d) "%d" + ["timestamp"]=> + string(%d) "%d" + } } ===DONE=== diff --git a/tests/session/session-debug-003.phpt b/tests/session/session-debug-003.phpt index feb5c89a2..dea263f3e 100644 --- a/tests/session/session-debug-003.phpt +++ b/tests/session/session-debug-003.phpt @@ -32,5 +32,7 @@ object(MongoDB\Driver\Session)#%d (%d) { NULL ["causalConsistency"]=> bool(false) + ["operationTime"]=> + NULL } ===DONE=== diff --git a/tests/session/session-getOperationTime-001.phpt b/tests/session/session-getOperationTime-001.phpt new file mode 100644 index 000000000..acb84b898 --- /dev/null +++ b/tests/session/session-getOperationTime-001.phpt @@ -0,0 +1,36 @@ +--TEST-- +MongoDB\Driver\Session::getOperationTime() +--SKIPIF-- + + +--FILE-- +startSession(); + +echo "Initial operation time:\n"; +var_dump($session->getOperationTime()); + +$command = new MongoDB\Driver\Command(['ping' => 1]); +$manager->executeCommand(DATABASE_NAME, $command, ['session' => $session]); + +echo "\nOperation time after command:\n"; +var_dump($session->getOperationTime()); + +?> +===DONE=== + +--EXPECTF-- +Initial operation time: +NULL + +Operation time after command: +object(MongoDB\BSON\Timestamp)#%d (%d) { + ["increment"]=> + string(%d) "%d" + ["timestamp"]=> + string(%d) "%d" +} +===DONE=== From e1b68f5db403e2136525afb1af3a2846a0460ff4 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Tue, 19 Dec 2017 17:58:36 -0500 Subject: [PATCH 2/3] PHPC-987: Causal consistency spec tests --- .../causal-consistency-001.phpt | 22 +++ .../causal-consistency-002.phpt | 31 ++++ .../causal-consistency-003.phpt | 111 +++++++++++++++ .../causal-consistency-004.phpt | 134 ++++++++++++++++++ .../causal-consistency-005.phpt | 100 +++++++++++++ .../causal-consistency-006.phpt | 115 +++++++++++++++ .../causal-consistency-007.phpt | 33 +++++ .../causal-consistency-008.phpt | 38 +++++ .../causal-consistency-009.phpt | 39 +++++ .../causal-consistency-010.phpt | 33 +++++ 10 files changed, 656 insertions(+) create mode 100644 tests/causal-consistency/causal-consistency-001.phpt create mode 100644 tests/causal-consistency/causal-consistency-002.phpt create mode 100644 tests/causal-consistency/causal-consistency-003.phpt create mode 100644 tests/causal-consistency/causal-consistency-004.phpt create mode 100644 tests/causal-consistency/causal-consistency-005.phpt create mode 100644 tests/causal-consistency/causal-consistency-006.phpt create mode 100644 tests/causal-consistency/causal-consistency-007.phpt create mode 100644 tests/causal-consistency/causal-consistency-008.phpt create mode 100644 tests/causal-consistency/causal-consistency-009.phpt create mode 100644 tests/causal-consistency/causal-consistency-010.phpt diff --git a/tests/causal-consistency/causal-consistency-001.phpt b/tests/causal-consistency/causal-consistency-001.phpt new file mode 100644 index 000000000..7dbd51800 --- /dev/null +++ b/tests/causal-consistency/causal-consistency-001.phpt @@ -0,0 +1,22 @@ +--TEST-- +Causal consistency: new session has no operation time +--SKIPIF-- + + +--FILE-- +startSession(); + +echo "Initial operation time:\n"; +var_dump($session->getOperationTime()); + +?> +===DONE=== + +--EXPECT-- +Initial operation time: +NULL +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-002.phpt b/tests/causal-consistency/causal-consistency-002.phpt new file mode 100644 index 000000000..b17cd55a9 --- /dev/null +++ b/tests/causal-consistency/causal-consistency-002.phpt @@ -0,0 +1,31 @@ +--TEST-- +Causal consistency: first read in session does not include afterClusterTime +--SKIPIF-- + + +--FILE-- +observe( + function() { + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $query = new MongoDB\Driver\Query([]); + $manager->executeQuery(NS, $query, ['session' => $session]); + }, + function(stdClass $command) + { + $hasAfterClusterTime = isset($command->readConcern->afterClusterTime); + printf("Read includes afterClusterTime: %s\n", ($hasAfterClusterTime ? 'yes' : 'no')); + } +); + +?> +===DONE=== + +--EXPECT-- +Read includes afterClusterTime: no +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-003.phpt b/tests/causal-consistency/causal-consistency-003.phpt new file mode 100644 index 000000000..ce99a15f1 --- /dev/null +++ b/tests/causal-consistency/causal-consistency-003.phpt @@ -0,0 +1,111 @@ +--TEST-- +Causal consistency: first read or write in session updates operationTime +--SKIPIF-- + + +--FILE-- +lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $bulk = new MongoDB\Driver\BulkWrite; + $bulk->insert(['x' => 1]); + $manager->executeBulkWrite(NS, $bulk, ['session' => $session]); + + printf("Session reports last seen operationTime: %s\n", ($session->getOperationTime() == $this->lastSeenOperationTime) ? 'yes' : 'no'); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function executeCommand() + { + $this->lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $command = new MongoDB\Driver\Command(['ping' => 1]); + $manager->executeCommand(DATABASE_NAME, $command, ['session' => $session]); + + printf("Session reports last seen operationTime: %s\n", ($session->getOperationTime() == $this->lastSeenOperationTime) ? 'yes' : 'no'); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function executeQuery() + { + $this->lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $query = new MongoDB\Driver\Query([]); + $manager->executeQuery(NS, $query, ['session' => $session]); + + printf("Session reports last seen operationTime: %s\n", ($session->getOperationTime() == $this->lastSeenOperationTime) ? 'yes' : 'no'); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event) + { + } + + public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event) + { + $reply = $event->getReply(); + $hasOperationTime = isset($reply->{'operationTime'}); + + printf("%s command reply includes operationTime: %s\n", $event->getCommandName(), $hasOperationTime ? 'yes' : 'no'); + + if ($hasOperationTime) { + $this->lastSeenOperationTime = $reply->operationTime; + } + } + + public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event) + { + } +} + +echo "Testing executeBulkWrite()\n"; +(new Test)->executeBulkWrite(); + +echo "\nTesting executeCommand()\n"; +(new Test)->executeCommand(); + +echo "\nTesting executeQuery()\n"; +(new Test)->executeQuery(); + +?> +===DONE=== + +--EXPECT-- +Testing executeBulkWrite() +insert command reply includes operationTime: yes +Session reports last seen operationTime: yes + +Testing executeCommand() +ping command reply includes operationTime: yes +Session reports last seen operationTime: yes + +Testing executeQuery() +find command reply includes operationTime: yes +Session reports last seen operationTime: yes +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-004.phpt b/tests/causal-consistency/causal-consistency-004.phpt new file mode 100644 index 000000000..2ad835114 --- /dev/null +++ b/tests/causal-consistency/causal-consistency-004.phpt @@ -0,0 +1,134 @@ +--TEST-- +Causal consistency: first read or write in session updates operationTime (even on error) +--SKIPIF-- + + +--FILE-- +lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $bulk = new MongoDB\Driver\BulkWrite; + $bulk->insert(['_id' => 1]); + $bulk->insert(['_id' => 1]); + + throws(function() use ($manager, $bulk, $session) { + $manager->executeBulkWrite(NS, $bulk, ['session' => $session]); + }, 'MongoDB\Driver\Exception\BulkWriteException'); + + printf("Session reports last seen operationTime: %s\n", ($session->getOperationTime() == $this->lastSeenOperationTime) ? 'yes' : 'no'); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function executeCommand() + { + $this->lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $command = new MongoDB\Driver\Command([ + 'aggregate' => COLLECTION_NAME, + 'pipeline' => [ + ['$unsupportedOperator' => 1], + ], + 'cursor' => new stdClass, + ]); + + throws(function() use ($manager, $command, $session) { + $manager->executeCommand(DATABASE_NAME, $command, ['session' => $session]); + }, 'MongoDB\Driver\Exception\RuntimeException'); + + /* We cannot access the server reply if an exception is thrown for a + * failed command (see: PHPC-1076). For the time being, just assert that + * the operationTime is not null. */ + printf("Session has non-null operationTime: %s\n", ($session->getOperationTime() !== null ? 'yes' : 'no')); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function executeQuery() + { + $this->lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $query = new MongoDB\Driver\Query(['$unsupportedOperator' => 1]); + + throws(function() use ($manager, $query, $session) { + $manager->executeQuery(NS, $query, ['session' => $session]); + }, 'MongoDB\Driver\Exception\RuntimeException'); + + /* We cannot access the server reply if an exception is thrown for a + * failed command (see: PHPC-1076). For the time being, just assert that + * the operationTime is not null. */ + printf("Session has non-null operationTime: %s\n", ($session->getOperationTime() !== null ? 'yes' : 'no')); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event) + { + } + + public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event) + { + $reply = $event->getReply(); + $hasOperationTime = isset($reply->operationTime); + + printf("%s command reply includes operationTime: %s\n", $event->getCommandName(), $hasOperationTime ? 'yes' : 'no'); + + if ($hasOperationTime) { + $this->lastSeenOperationTime = $reply->operationTime; + } + } + + public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event) + { + } +} + +echo "Testing executeBulkWrite()\n"; +(new Test)->executeBulkWrite(); + +echo "\nTesting executeCommand()\n"; +(new Test)->executeCommand(); + +echo "\nTesting executeQuery()\n"; +(new Test)->executeQuery(); + +?> +===DONE=== + +--EXPECT-- +Testing executeBulkWrite() +insert command reply includes operationTime: yes +OK: Got MongoDB\Driver\Exception\BulkWriteException +Session reports last seen operationTime: yes + +Testing executeCommand() +OK: Got MongoDB\Driver\Exception\RuntimeException +Session has non-null operationTime: yes + +Testing executeQuery() +OK: Got MongoDB\Driver\Exception\RuntimeException +Session has non-null operationTime: yes +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-005.phpt b/tests/causal-consistency/causal-consistency-005.phpt new file mode 100644 index 000000000..987c7befd --- /dev/null +++ b/tests/causal-consistency/causal-consistency-005.phpt @@ -0,0 +1,100 @@ +--TEST-- +Causal consistency: second read's afterClusterTime uses last reply's operationTime +--SKIPIF-- + + +--FILE-- +lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $query = new MongoDB\Driver\Query([]); + $manager->executeQuery(NS, $query, ['session' => $session]); + $manager->executeQuery(NS, $query, ['session' => $session]); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function executeReadAfterWrite() + { + $this->lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $bulk = new MongoDB\Driver\BulkWrite; + $bulk->insert(['x' => 1]); + $manager->executeBulkWrite(NS, $bulk, ['session' => $session]); + + $query = new MongoDB\Driver\Query([]); + $manager->executeQuery(NS, $query, ['session' => $session]); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event) + { + $command = $event->getCommand(); + $hasAfterClusterTime = isset($command->readConcern->afterClusterTime); + printf("%s command includes afterClusterTime: %s\n", $event->getCommandName(), ($hasAfterClusterTime ? 'yes' : 'no')); + + if ($hasAfterClusterTime && $this->lastSeenOperationTime !== null) { + printf("%s command uses last seen operationTime: %s\n", $event->getCommandName(), ($command->readConcern->afterClusterTime == $this->lastSeenOperationTime) ? 'yes' : 'no'); + } + } + + public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event) + { + $reply = $event->getReply(); + $hasOperationTime = isset($reply->operationTime); + + printf("%s command reply includes operationTime: %s\n", $event->getCommandName(), $hasOperationTime ? 'yes' : 'no'); + + if ($hasOperationTime) { + $this->lastSeenOperationTime = $reply->operationTime; + } + } + + public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event) + { + } +} + +echo "Testing read after read\n"; +(new Test)->executeReadAfterRead(); + +echo "\nTesting read after write\n"; +(new Test)->executeReadAfterWrite(); + +?> +===DONE=== + +--EXPECT-- +Testing read after read +find command includes afterClusterTime: no +find command reply includes operationTime: yes +find command includes afterClusterTime: yes +find command uses last seen operationTime: yes +find command reply includes operationTime: yes + +Testing read after write +insert command includes afterClusterTime: no +insert command reply includes operationTime: yes +find command includes afterClusterTime: yes +find command uses last seen operationTime: yes +find command reply includes operationTime: yes +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-006.phpt b/tests/causal-consistency/causal-consistency-006.phpt new file mode 100644 index 000000000..ce59879dd --- /dev/null +++ b/tests/causal-consistency/causal-consistency-006.phpt @@ -0,0 +1,115 @@ +--TEST-- +Causal consistency: second read's afterClusterTime uses last reply's operationTime (even on error) +--SKIPIF-- + + +--FILE-- +lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $query = new MongoDB\Driver\Query(['$unsupportedOperator' => 1]); + + throws(function() use ($manager, $query, $session) { + $manager->executeQuery(NS, $query, ['session' => $session]); + }, 'MongoDB\Driver\Exception\RuntimeException'); + + /* We cannot access the server reply if an exception is thrown for a + * failed command (see: PHPC-1076). For the time being, just assert that + * the operationTime is not null. */ + printf("Session has non-null operationTime: %s\n", ($session->getOperationTime() !== null ? 'yes' : 'no')); + + $query = new MongoDB\Driver\Query([]); + $manager->executeQuery(NS, $query, ['session' => $session]); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function executeReadAfterWriteError() + { + $this->lastSeenOperationTime = null; + + MongoDB\Driver\Monitoring\addSubscriber($this); + + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $bulk = new MongoDB\Driver\BulkWrite; + $bulk->insert(['_id' => 1]); + $bulk->insert(['_id' => 1]); + + throws(function() use ($manager, $bulk, $session) { + $manager->executeBulkWrite(NS, $bulk, ['session' => $session]); + }, 'MongoDB\Driver\Exception\BulkWriteException'); + + $query = new MongoDB\Driver\Query([]); + $manager->executeQuery(NS, $query, ['session' => $session]); + + MongoDB\Driver\Monitoring\removeSubscriber($this); + } + + public function commandStarted(MongoDB\Driver\Monitoring\CommandStartedEvent $event) + { + $command = $event->getCommand(); + $hasAfterClusterTime = isset($command->readConcern->afterClusterTime); + printf("%s command includes afterClusterTime: %s\n", $event->getCommandName(), ($hasAfterClusterTime ? 'yes' : 'no')); + + if ($hasAfterClusterTime && $this->lastSeenOperationTime !== null) { + printf("%s command uses last seen operationTime: %s\n", $event->getCommandName(), ($command->readConcern->afterClusterTime == $this->lastSeenOperationTime) ? 'yes' : 'no'); + } + } + + public function commandSucceeded(MongoDB\Driver\Monitoring\CommandSucceededEvent $event) + { + $reply = $event->getReply(); + $hasOperationTime = isset($reply->operationTime); + + printf("%s command reply includes operationTime: %s\n", $event->getCommandName(), $hasOperationTime ? 'yes' : 'no'); + + if ($hasOperationTime) { + $this->lastSeenOperationTime = $reply->operationTime; + } + } + + public function commandFailed(MongoDB\Driver\Monitoring\CommandFailedEvent $event) + { + } +} + +echo "\nTesting read after read error\n"; +(new Test)->executeReadAfterReadError(); + +echo "\nTesting read after write error\n"; +(new Test)->executeReadAfterWriteError(); + +?> +===DONE=== + +--EXPECT-- +Testing read after read error +find command includes afterClusterTime: no +OK: Got MongoDB\Driver\Exception\RuntimeException +Session has non-null operationTime: yes +find command includes afterClusterTime: yes +find command reply includes operationTime: yes + +Testing read after write error +insert command includes afterClusterTime: no +insert command reply includes operationTime: yes +OK: Got MongoDB\Driver\Exception\BulkWriteException +find command includes afterClusterTime: yes +find command uses last seen operationTime: yes +find command reply includes operationTime: yes +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-007.phpt b/tests/causal-consistency/causal-consistency-007.phpt new file mode 100644 index 000000000..9914db8a7 --- /dev/null +++ b/tests/causal-consistency/causal-consistency-007.phpt @@ -0,0 +1,33 @@ +--TEST-- +Causal consistency: reads in non-causally consistent session never include afterClusterTime +--SKIPIF-- + + +--FILE-- +observe( + function() { + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(['causalConsistency' => false]); + + $query = new MongoDB\Driver\Query([]); + $manager->executeQuery(NS, $query, ['session' => $session]); + $manager->executeQuery(NS, $query, ['session' => $session]); + }, + function(stdClass $command) + { + $hasAfterClusterTime = isset($command->readConcern->afterClusterTime); + printf("Read includes afterClusterTime: %s\n", ($hasAfterClusterTime ? 'yes' : 'no')); + } +); + +?> +===DONE=== + +--EXPECT-- +Read includes afterClusterTime: no +Read includes afterClusterTime: no +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-008.phpt b/tests/causal-consistency/causal-consistency-008.phpt new file mode 100644 index 000000000..48aece5bd --- /dev/null +++ b/tests/causal-consistency/causal-consistency-008.phpt @@ -0,0 +1,38 @@ +--TEST-- +Causal consistency: default read concern includes afterClusterTime but not level +--SKIPIF-- + + +--FILE-- +observe( + function() { + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $query = new MongoDB\Driver\Query([]); + $manager->executeQuery(NS, $query, ['session' => $session]); + $manager->executeQuery(NS, $query, ['session' => $session]); + }, + function(stdClass $command) + { + $hasAfterClusterTime = isset($command->readConcern->afterClusterTime); + printf("Read concern includes afterClusterTime: %s\n", ($hasAfterClusterTime ? 'yes' : 'no')); + + $hasLevel = isset($command->readConcern->level); + printf("Read concern includes level: %s\n", ($hasLevel ? 'yes' : 'no')); + } +); + +?> +===DONE=== + +--EXPECT-- +Read concern includes afterClusterTime: no +Read concern includes level: no +Read concern includes afterClusterTime: yes +Read concern includes level: no +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-009.phpt b/tests/causal-consistency/causal-consistency-009.phpt new file mode 100644 index 000000000..76e83fd67 --- /dev/null +++ b/tests/causal-consistency/causal-consistency-009.phpt @@ -0,0 +1,39 @@ +--TEST-- +Causal consistency: custom read concern merges afterClusterTime and level +--SKIPIF-- + + +--FILE-- +observe( + function() { + $manager = new MongoDB\Driver\Manager(REPLICASET); + $session = $manager->startSession(); + + $readConcern = new MongoDB\Driver\ReadConcern(MongoDB\Driver\ReadConcern::MAJORITY); + $query = new MongoDB\Driver\Query([], ['readConcern' => $readConcern]); + $manager->executeQuery(NS, $query, ['session' => $session]); + $manager->executeQuery(NS, $query, ['session' => $session]); + }, + function(stdClass $command) + { + $hasAfterClusterTime = isset($command->readConcern->afterClusterTime); + printf("Read concern includes afterClusterTime: %s\n", ($hasAfterClusterTime ? 'yes' : 'no')); + + $hasLevel = isset($command->readConcern->level); + printf("Read concern includes level: %s\n", ($hasLevel ? 'yes' : 'no')); + } +); + +?> +===DONE=== + +--EXPECT-- +Read concern includes afterClusterTime: no +Read concern includes level: yes +Read concern includes afterClusterTime: yes +Read concern includes level: yes +===DONE=== diff --git a/tests/causal-consistency/causal-consistency-010.phpt b/tests/causal-consistency/causal-consistency-010.phpt new file mode 100644 index 000000000..bd93739e7 --- /dev/null +++ b/tests/causal-consistency/causal-consistency-010.phpt @@ -0,0 +1,33 @@ +--TEST-- +Causal consistency: unacknowledged write does not update operationTime +--SKIPIF-- + + +--FILE-- +startSession(); + +echo "Initial operation time:\n"; +var_dump($session->getOperationTime()); + +$bulk = new MongoDB\Driver\BulkWrite; +$bulk->insert(['x' => 1]); +$writeConcern = new MongoDB\Driver\WriteConcern(0); +$manager->executeBulkWrite(NS, $bulk, ['session' => $session, 'writeConcern' => $writeConcern]); + +echo "\nOperation time after unacknowledged write:\n"; +var_dump($session->getOperationTime()); + +?> +===DONE=== + +--EXPECT-- +Initial operation time: +NULL + +Operation time after unacknowledged write: +NULL +===DONE=== From d99153bfb74822dfeb0578e9ffc8cd30a17517ee Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Wed, 20 Dec 2017 12:29:59 -0500 Subject: [PATCH 3/3] PHPC-980: Skip session spec test on Travis (pending 3.6) --- tests/session/session-003.phpt | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/session/session-003.phpt b/tests/session/session-003.phpt index fbcf244b2..ad5b6b59f 100644 --- a/tests/session/session-003.phpt +++ b/tests/session/session-003.phpt @@ -1,6 +1,7 @@ --TEST-- MongoDB\Driver\Session spec test: session cannot be used for different clients --SKIPIF-- + --FILE--