Commit 39f59b20 authored by Derick Rethans's avatar Derick Rethans

PHPLIB-368: Operations executed within transaction should not inherit read/write concern

parent 6ecf84a8
...@@ -15,7 +15,7 @@ cache: ...@@ -15,7 +15,7 @@ cache:
env: env:
global: global:
- DRIVER_VERSION=1.5.2 - DRIVER_VERSION=1.6.0alpha1
- SERVER_VERSION=4.0.1 - SERVER_VERSION=4.0.1
- DEPLOYMENT=STANDALONE - DEPLOYMENT=STANDALONE
......
...@@ -203,10 +203,13 @@ class Collection ...@@ -203,10 +203,13 @@ class Collection
/* A "majority" read concern is not compatible with the $out stage, so /* A "majority" read concern is not compatible with the $out stage, so
* avoid providing the Collection's read concern if it would conflict. * avoid providing the Collection's read concern if it would conflict.
*
* A read concern is also not compatible with transactions.
*/ */
if ( ! isset($options['readConcern']) && if ( ! isset($options['readConcern']) &&
! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && ! ($hasOutStage && $this->readConcern->getLevel() === ReadConcern::MAJORITY) &&
\MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) &&
! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -214,7 +217,10 @@ class Collection ...@@ -214,7 +217,10 @@ class Collection
$options['typeMap'] = $this->typeMap; $options['typeMap'] = $this->typeMap;
} }
if ($hasOutStage && ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { if ($hasOutStage &&
! isset($options['writeConcern']) &&
\MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) &&
! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -236,7 +242,7 @@ class Collection ...@@ -236,7 +242,7 @@ class Collection
*/ */
public function bulkWrite(array $operations, array $options = []) public function bulkWrite(array $operations, array $options = [])
{ {
if ( ! isset($options['writeConcern'])) { if ( ! isset($options['writeConcern']) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -268,7 +274,7 @@ class Collection ...@@ -268,7 +274,7 @@ class Collection
$server = $this->manager->selectServer($options['readPreference']); $server = $this->manager->selectServer($options['readPreference']);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) && ! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -297,7 +303,7 @@ class Collection ...@@ -297,7 +303,7 @@ class Collection
$server = $this->manager->selectServer($options['readPreference']); $server = $this->manager->selectServer($options['readPreference']);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) && ! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -359,7 +365,7 @@ class Collection ...@@ -359,7 +365,7 @@ class Collection
{ {
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -382,7 +388,7 @@ class Collection ...@@ -382,7 +388,7 @@ class Collection
*/ */
public function deleteMany($filter, array $options = []) public function deleteMany($filter, array $options = [])
{ {
if ( ! isset($options['writeConcern'])) { if ( ! isset($options['writeConcern']) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -406,7 +412,7 @@ class Collection ...@@ -406,7 +412,7 @@ class Collection
*/ */
public function deleteOne($filter, array $options = []) public function deleteOne($filter, array $options = [])
{ {
if ( ! isset($options['writeConcern'])) { if ( ! isset($options['writeConcern']) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -437,7 +443,7 @@ class Collection ...@@ -437,7 +443,7 @@ class Collection
$server = $this->manager->selectServer($options['readPreference']); $server = $this->manager->selectServer($options['readPreference']);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) && ! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -464,7 +470,7 @@ class Collection ...@@ -464,7 +470,7 @@ class Collection
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -498,7 +504,7 @@ class Collection ...@@ -498,7 +504,7 @@ class Collection
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -525,7 +531,7 @@ class Collection ...@@ -525,7 +531,7 @@ class Collection
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -553,7 +559,7 @@ class Collection ...@@ -553,7 +559,7 @@ class Collection
$server = $this->manager->selectServer($options['readPreference']); $server = $this->manager->selectServer($options['readPreference']);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) && ! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -611,7 +617,7 @@ class Collection ...@@ -611,7 +617,7 @@ class Collection
$server = $this->manager->selectServer($options['readPreference']); $server = $this->manager->selectServer($options['readPreference']);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) && ! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -644,7 +650,7 @@ class Collection ...@@ -644,7 +650,7 @@ class Collection
$server = $this->manager->selectServer($options['readPreference']); $server = $this->manager->selectServer($options['readPreference']);
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) && ! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -676,7 +682,7 @@ class Collection ...@@ -676,7 +682,7 @@ class Collection
{ {
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -713,7 +719,7 @@ class Collection ...@@ -713,7 +719,7 @@ class Collection
{ {
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -750,7 +756,7 @@ class Collection ...@@ -750,7 +756,7 @@ class Collection
{ {
$server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY)); $server = $this->manager->selectServer(new ReadPreference(ReadPreference::RP_PRIMARY));
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern)) { if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForFindAndModifyWriteConcern) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -859,7 +865,7 @@ class Collection ...@@ -859,7 +865,7 @@ class Collection
*/ */
public function insertMany(array $documents, array $options = []) public function insertMany(array $documents, array $options = [])
{ {
if ( ! isset($options['writeConcern'])) { if ( ! isset($options['writeConcern']) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -882,7 +888,7 @@ class Collection ...@@ -882,7 +888,7 @@ class Collection
*/ */
public function insertOne($document, array $options = []) public function insertOne($document, array $options = [])
{ {
if ( ! isset($options['writeConcern'])) { if ( ! isset($options['writeConcern']) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -941,8 +947,10 @@ class Collection ...@@ -941,8 +947,10 @@ class Collection
/* A "majority" read concern is not compatible with inline output, so /* A "majority" read concern is not compatible with inline output, so
* avoid providing the Collection's read concern if it would conflict. * avoid providing the Collection's read concern if it would conflict.
*
* A read concern is also not compatible with transactions.
*/ */
if ( ! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { if ( ! isset($options['readConcern']) && ! ($hasOutputCollection && $this->readConcern->getLevel() === ReadConcern::MAJORITY) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) && ! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -950,7 +958,7 @@ class Collection ...@@ -950,7 +958,7 @@ class Collection
$options['typeMap'] = $this->typeMap; $options['typeMap'] = $this->typeMap;
} }
if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern)) { if ( ! isset($options['writeConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForWritableCommandWriteConcern) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -974,7 +982,7 @@ class Collection ...@@ -974,7 +982,7 @@ class Collection
*/ */
public function replaceOne($filter, $replacement, array $options = []) public function replaceOne($filter, $replacement, array $options = [])
{ {
if ( ! isset($options['writeConcern'])) { if ( ! isset($options['writeConcern']) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -999,7 +1007,7 @@ class Collection ...@@ -999,7 +1007,7 @@ class Collection
*/ */
public function updateMany($filter, $update, array $options = []) public function updateMany($filter, $update, array $options = [])
{ {
if ( ! isset($options['writeConcern'])) { if ( ! isset($options['writeConcern']) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -1024,7 +1032,7 @@ class Collection ...@@ -1024,7 +1032,7 @@ class Collection
*/ */
public function updateOne($filter, $update, array $options = []) public function updateOne($filter, $update, array $options = [])
{ {
if ( ! isset($options['writeConcern'])) { if ( ! isset($options['writeConcern']) && ! $this->isInTransaction($options)) {
$options['writeConcern'] = $this->writeConcern; $options['writeConcern'] = $this->writeConcern;
} }
...@@ -1058,7 +1066,7 @@ class Collection ...@@ -1058,7 +1066,7 @@ class Collection
* related to change streams being unsupported instead of an * related to change streams being unsupported instead of an
* UnsupportedException regarding use of the "readConcern" option from * UnsupportedException regarding use of the "readConcern" option from
* the Aggregate operation class. */ * the Aggregate operation class. */
if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern)) { if ( ! isset($options['readConcern']) && \MongoDB\server_supports_feature($server, self::$wireVersionForReadConcern) && ! $this->isInTransaction($options)) {
$options['readConcern'] = $this->readConcern; $options['readConcern'] = $this->readConcern;
} }
...@@ -1090,4 +1098,18 @@ class Collection ...@@ -1090,4 +1098,18 @@ class Collection
return new Collection($this->manager, $this->databaseName, $this->collectionName, $options); return new Collection($this->manager, $this->databaseName, $this->collectionName, $options);
} }
/**
* Returns whether we are currently in a transaction
*
* @param array $options Command options
* @return bool
*/
private function isInTransaction(array $options)
{
if (isset($options['session']) && $options['session'] instanceof \MongoDB\Driver\Session && $options['session']->isInTransaction()) {
return true;
}
return false;
}
} }
...@@ -59,6 +59,16 @@ class UnsupportedException extends RuntimeException ...@@ -59,6 +59,16 @@ class UnsupportedException extends RuntimeException
return new static('Read concern is not supported by the server executing this command'); return new static('Read concern is not supported by the server executing this command');
} }
/**
* Thrown when a readConcern is used with a read operation in a transaction.
*
* @return self
*/
public static function readConcernNotSupportedInTransaction()
{
return new static('The "readConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.');
}
/** /**
* Thrown when a command's writeConcern option is not supported by a server. * Thrown when a command's writeConcern option is not supported by a server.
* *
...@@ -68,4 +78,14 @@ class UnsupportedException extends RuntimeException ...@@ -68,4 +78,14 @@ class UnsupportedException extends RuntimeException
{ {
return new static('Write concern is not supported by the server executing this command'); return new static('Write concern is not supported by the server executing this command');
} }
/**
* Thrown when a writeConcern is used with a write operation in a transaction.
*
* @return self
*/
public static function writeConcernNotSupportedInTransaction()
{
return new static('The "writeConcern" option cannot be specified within a transaction. Instead, specify it when starting the transaction.');
}
} }
...@@ -252,6 +252,17 @@ class Aggregate implements Executable ...@@ -252,6 +252,17 @@ class Aggregate implements Executable
throw UnsupportedException::writeConcernNotSupported(); throw UnsupportedException::writeConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction) {
if (isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
if (isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
}
$hasExplain = ! empty($this->options['explain']); $hasExplain = ! empty($this->options['explain']);
$hasOutStage = \MongoDB\is_last_pipeline_operator_out($this->pipeline); $hasOutStage = \MongoDB\is_last_pipeline_operator_out($this->pipeline);
......
...@@ -322,6 +322,11 @@ class BulkWrite implements Executable ...@@ -322,6 +322,11 @@ class BulkWrite implements Executable
throw UnsupportedException::collationNotSupported(); throw UnsupportedException::collationNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$options = ['ordered' => $this->options['ordered']]; $options = ['ordered' => $this->options['ordered']];
if ( if (
......
...@@ -151,6 +151,11 @@ class Count implements Executable, Explainable ...@@ -151,6 +151,11 @@ class Count implements Executable, Explainable
throw UnsupportedException::readConcernNotSupported(); throw UnsupportedException::readConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions()); $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray()); $result = current($cursor->toArray());
......
...@@ -151,6 +151,11 @@ class CountDocuments implements Executable ...@@ -151,6 +151,11 @@ class CountDocuments implements Executable
throw UnsupportedException::readConcernNotSupported(); throw UnsupportedException::readConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions()); $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$allResults = $cursor->toArray(); $allResults = $cursor->toArray();
......
...@@ -139,6 +139,11 @@ class CreateIndexes implements Executable ...@@ -139,6 +139,11 @@ class CreateIndexes implements Executable
throw UnsupportedException::writeConcernNotSupported(); throw UnsupportedException::writeConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$this->executeCommand($server); $this->executeCommand($server);
return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes); return array_map(function(IndexInput $index) { return (string) $index; }, $this->indexes);
......
...@@ -117,6 +117,11 @@ class Delete implements Executable, Explainable ...@@ -117,6 +117,11 @@ class Delete implements Executable, Explainable
throw UnsupportedException::collationNotSupported(); throw UnsupportedException::collationNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$bulk = new Bulk(); $bulk = new Bulk();
$bulk->delete($this->filter, $this->createDeleteOptions()); $bulk->delete($this->filter, $this->createDeleteOptions());
......
...@@ -133,6 +133,11 @@ class Distinct implements Executable, Explainable ...@@ -133,6 +133,11 @@ class Distinct implements Executable, Explainable
throw UnsupportedException::readConcernNotSupported(); throw UnsupportedException::readConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions()); $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray()); $result = current($cursor->toArray());
......
...@@ -102,6 +102,11 @@ class DropCollection implements Executable ...@@ -102,6 +102,11 @@ class DropCollection implements Executable
throw UnsupportedException::writeConcernNotSupported(); throw UnsupportedException::writeConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$command = new Command(['drop' => $this->collectionName]); $command = new Command(['drop' => $this->collectionName]);
try { try {
......
...@@ -116,6 +116,11 @@ class DropIndexes implements Executable ...@@ -116,6 +116,11 @@ class DropIndexes implements Executable
throw UnsupportedException::writeConcernNotSupported(); throw UnsupportedException::writeConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions()); $cursor = $server->executeWriteCommand($this->databaseName, $this->createCommand(), $this->createOptions());
if (isset($this->options['typeMap'])) { if (isset($this->options['typeMap'])) {
......
...@@ -114,6 +114,11 @@ class EstimatedDocumentCount implements Executable, Explainable ...@@ -114,6 +114,11 @@ class EstimatedDocumentCount implements Executable, Explainable
throw UnsupportedException::readConcernNotSupported(); throw UnsupportedException::readConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
$cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions()); $cursor = $server->executeReadCommand($this->databaseName, new Command($this->createCommandDocument()), $this->createOptions());
$result = current($cursor->toArray()); $result = current($cursor->toArray());
......
...@@ -296,6 +296,11 @@ class Find implements Executable, Explainable ...@@ -296,6 +296,11 @@ class Find implements Executable, Explainable
throw UnsupportedException::readConcernNotSupported(); throw UnsupportedException::readConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
$cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, new Query($this->filter, $this->createQueryOptions()), $this->createExecuteOptions()); $cursor = $server->executeQuery($this->databaseName . '.' . $this->collectionName, new Query($this->filter, $this->createQueryOptions()), $this->createExecuteOptions());
if (isset($this->options['typeMap'])) { if (isset($this->options['typeMap'])) {
......
...@@ -210,6 +210,11 @@ class FindAndModify implements Executable, Explainable ...@@ -210,6 +210,11 @@ class FindAndModify implements Executable, Explainable
throw UnsupportedException::writeConcernNotSupported(); throw UnsupportedException::writeConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$cursor = $server->executeWriteCommand($this->databaseName, new Command($this->createCommandDocument($server)), $this->createOptions()); $cursor = $server->executeWriteCommand($this->databaseName, new Command($this->createCommandDocument($server)), $this->createOptions());
$result = current($cursor->toArray()); $result = current($cursor->toArray());
......
...@@ -24,6 +24,7 @@ use MongoDB\Driver\Session; ...@@ -24,6 +24,7 @@ use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern; use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/** /**
* Operation for inserting multiple documents with the insert command. * Operation for inserting multiple documents with the insert command.
...@@ -126,6 +127,11 @@ class InsertMany implements Executable ...@@ -126,6 +127,11 @@ class InsertMany implements Executable
*/ */
public function execute(Server $server) public function execute(Server $server)
{ {
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$options = ['ordered' => $this->options['ordered']]; $options = ['ordered' => $this->options['ordered']];
if ( if (
......
...@@ -24,6 +24,7 @@ use MongoDB\Driver\Session; ...@@ -24,6 +24,7 @@ use MongoDB\Driver\Session;
use MongoDB\Driver\WriteConcern; use MongoDB\Driver\WriteConcern;
use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException;
use MongoDB\Exception\InvalidArgumentException; use MongoDB\Exception\InvalidArgumentException;
use MongoDB\Exception\UnsupportedException;
/** /**
* Operation for inserting a single document with the insert command. * Operation for inserting a single document with the insert command.
...@@ -104,6 +105,11 @@ class InsertOne implements Executable ...@@ -104,6 +105,11 @@ class InsertOne implements Executable
{ {
$options = []; $options = [];
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if (isset($this->options['writeConcern']) && $inTransaction) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
if ( if (
! empty($this->options['bypassDocumentValidation']) && ! empty($this->options['bypassDocumentValidation']) &&
\MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation) \MongoDB\server_supports_feature($server, self::$wireVersionForDocumentLevelValidation)
......
...@@ -248,6 +248,16 @@ class MapReduce implements Executable ...@@ -248,6 +248,16 @@ class MapReduce implements Executable
throw UnsupportedException::writeConcernNotSupported(); throw UnsupportedException::writeConcernNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction) {
if (isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
if (isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
}
$hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($this->out); $hasOutputCollection = ! \MongoDB\is_mapreduce_output_inline($this->out);
$command = $this->createCommand($server); $command = $this->createCommand($server);
......
...@@ -167,6 +167,11 @@ class Update implements Executable, Explainable ...@@ -167,6 +167,11 @@ class Update implements Executable, Explainable
throw UnsupportedException::collationNotSupported(); throw UnsupportedException::collationNotSupported();
} }
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['writeConcern'])) {
throw UnsupportedException::writeConcernNotSupportedInTransaction();
}
$bulkOptions = []; $bulkOptions = [];
if ( if (
......
...@@ -213,6 +213,11 @@ class Watch implements Executable, /* @internal */ CommandSubscriber ...@@ -213,6 +213,11 @@ class Watch implements Executable, /* @internal */ CommandSubscriber
*/ */
public function execute(Server $server) public function execute(Server $server)
{ {
$inTransaction = isset($this->options['session']) && $this->options['session']->isInTransaction();
if ($inTransaction && isset($this->options['readConcern'])) {
throw UnsupportedException::readConcernNotSupportedInTransaction();
}
return new ChangeStream($this->executeAggregate($server), $this->resumeCallable); return new ChangeStream($this->executeAggregate($server), $this->resumeCallable);
} }
......
...@@ -311,6 +311,353 @@ class CollectionFunctionalTest extends FunctionalTestCase ...@@ -311,6 +311,353 @@ class CollectionFunctionalTest extends FunctionalTestCase
$this->assertNotEmpty($result->getCounts()); $this->assertNotEmpty($result->getCounts());
} }
public function collectionMethodClosures()
{
return [
[
function($collection, $session, $options = []) {
$collection->aggregate(
[['$match' => ['_id' => ['$lt' => 3]]]],
['session' => $session] + $options
);
}, 'rw'
],
[
function($collection, $session, $options = []) {
$collection->bulkWrite(
[['insertOne' => [['test' => 'foo']]]],
['session' => $session] + $options
);
}, 'w'
],
/* Disabled, as count command can't be used in transactions
[
function($collection, $session, $options = []) {
$collection->count(
[],
['session' => $session] + $options
);
}, 'r'
],
*/
[
function($collection, $session, $options = []) {
$collection->countDocuments(
[],
['session' => $session] + $options
);
}, 'r'
],
/* Disabled, as it's illegal to use createIndex command in transactions
[
function($collection, $session, $options = []) {
$collection->createIndex(
['test' => 1],
['session' => $session] + $options
);
}, 'w'
],
*/
[
function($collection, $session, $options = []) {
$collection->deleteMany(
['test' => 'foo'],
['session' => $session] + $options
);
}, 'w'
],
[
function($collection, $session, $options = []) {
$collection->deleteOne(
['test' => 'foo'],
['session' => $session] + $options
);
}, 'w'
],
[
function($collection, $session, $options = []) {
$collection->distinct(
'_id',
[],
['session' => $session] + $options
);
}, 'r'
],
/* Disabled, as it's illegal to use drop command in transactions
[
function($collection, $session, $options = []) {
$collection->drop(
['session' => $session] + $options
);
}, 'w'
],
*/
/* Disabled, as it's illegal to use dropIndexes command in transactions
[
function($collection, $session, $options = []) {
$collection->dropIndex(
'_id_1',
['session' => $session] + $options
);
}, 'w'
], */
/* Disabled, as it's illegal to use dropIndexes command in transactions
[
function($collection, $session, $options = []) {
$collection->dropIndexes(
['session' => $session] + $options
);
}, 'w'
],
*/
/* Disabled, as count command can't be used in transactions
[
function($collection, $session, $options = []) {
$collection->estimatedDocumentCount(
['session' => $session] + $options
);
}, 'r'
],
*/
[
function($collection, $session, $options = []) {
$collection->find(
['test' => 'foo'],
['session' => $session] + $options
);
}, 'r'
],
[
function($collection, $session, $options = []) {
$collection->findOne(
['test' => 'foo'],
['session' => $session] + $options
);
}, 'r'
],
[
function($collection, $session, $options = []) {
$collection->findOneAndDelete(
['test' => 'foo'],
['session' => $session] + $options
);
}, 'w'
],
[
function($collection, $session, $options = []) {
$collection->findOneAndReplace(
['test' => 'foo'],
[],
['session' => $session] + $options
);
}, 'w'
],
[
function($collection, $session, $options = []) {
$collection->findOneAndUpdate(
['test' => 'foo'],
['$set' => ['updated' => 1]],
['session' => $session] + $options
);
}, 'w'
],
[
function($collection, $session, $options = []) {
$collection->insertMany(
[
['test' => 'foo'],
['test' => 'bar'],
],
['session' => $session] + $options
);
}, 'w'
],
[
function($collection, $session, $options = []) {
$collection->insertOne(
['test' => 'foo'],
['session' => $session] + $options
);
}, 'w'
],
/* Disabled, as it's illegal to use listIndexes command in transactions
[
function($collection, $session, $options = []) {
$collection->listIndexes(
['session' => $session] + $options
);
}, 'r'
],
*/
/* Disabled, as it's illegal to use mapReduce command in transactions
[
function($collection, $session, $options = []) {
$collection->mapReduce(
new \MongoDB\BSON\Javascript('function() { emit(this.state, this.pop); }'),
new \MongoDB\BSON\Javascript('function(key, values) { return Array.sum(values) }'),
['inline' => 1],
['session' => $session] + $options
);
}, 'rw'
],
*/
[
function($collection, $session, $options = []) {
$collection->replaceOne(
['test' => 'foo'],
[],
['session' => $session] + $options
);
}, 'w'
],
[
function($collection, $session, $options = []) {
$collection->updateMany(
['test' => 'foo'],
['$set' => ['updated' => 1]],
['session' => $session] + $options
);
}, 'w'
],
[
function($collection, $session, $options = []) {
$collection->updateOne(
['test' => 'foo'],
['$set' => ['updated' => 1]],
['session' => $session] + $options
);
}, 'w'
],
];
}
public function collectionReadMethodClosures()
{
return array_filter(
$this->collectionMethodClosures(),
function($rw) {
if (strchr($rw[1], 'r') !== false) {
return true;
}
}
);
}
public function collectionWriteMethodClosures()
{
return array_filter(
$this->collectionMethodClosures(),
function($rw) {
if (strchr($rw[1], 'w') !== false) {
return true;
}
}
);
}
/**
* @dataProvider collectionMethodClosures
*/
public function testMethodDoesNotInheritReadWriteConcernInTranasaction(\Closure $method)
{
$this->skipIfTransactionsAreNotSupported();
$this->createCollection();
$session = $this->manager->startSession();
$session->startTransaction();
$collection = $this->collection->withOptions([
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
'writeConcern' => new WriteConcern(1)
]);
(new CommandObserver)->observe(
function() use ($method, $collection, $session) {
$method->call($this, $collection, $session);
},
function(array $event) {
$this->assertObjectNotHasAttribute('writeConcern', $event['started']->getCommand());
$this->assertObjectNotHasAttribute('readConcern', $event['started']->getCommand());
}
);
}
/**
* @dataProvider collectionWriteMethodClosures
* @expectedException MongoDB\Exception\UnsupportedException
* @expectedExceptionMessage "writeConcern" option cannot be specified within a transaction
*/
public function testMethodInTransactionWithWriteConcernOption($method)
{
$this->skipIfTransactionsAreNotSupported();
$this->createCollection();
$session = $this->manager->startSession();
$session->startTransaction();
try {
$method->call(
$this, $this->collection, $session,
[
'writeConcern' => new WriteConcern(1),
]
);
} finally {
$session->endSession();
}
}
/**
* @dataProvider collectionReadMethodClosures
* @expectedException MongoDB\Exception\UnsupportedException
* @expectedExceptionMessage "readConcern" option cannot be specified within a transaction
*/
public function testMethodInTransactionWithReadConcernOption($method)
{
$this->skipIfTransactionsAreNotSupported();
$this->createCollection();
$session = $this->manager->startSession();
$session->startTransaction();
try {
$method->call(
$this, $this->collection, $session,
[
'readConcern' => new ReadConcern(ReadConcern::LOCAL),
]
);
} finally {
$session->endSession();
}
}
/** /**
* Create data fixtures. * Create data fixtures.
* *
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment