/** @var array List of row arrays */
protected $nextResult = [];
+ /** @var array|null */
+ protected $nextError = null;
+ /** @var array|null */
+ protected $lastError = null;
+
/**
* Array of tables to be considered as existing by tableExist()
* Use setExistingTables() to alter.
$this->nextResult = $res;
}
+ /**
+ * @param int $errno Error number
+ * @param string $error Error text
+ * @param array $options
+ * - wasKnownStatementRollbackError: Return value for wasKnownStatementRollbackError()
+ */
+ public function forceNextQueryError( $errno, $error, $options = [] ) {
+ $this->nextError = [ 'errno' => $errno, 'error' => $error ] + $options;
+ }
+
protected function addSql( $sql ) {
// clean up spaces before and after some words and the whole string
$this->lastSqls[] = trim( preg_replace(
return; // no $fname parameter
}
- if ( substr( $fname, 0, strlen( $this->testName ) ) !== $this->testName ) {
+ // Handle some internal calls from the Database class
+ $check = $fname;
+ if ( preg_match( '/^Wikimedia\\\\Rdbms\\\\Database::query \((.+)\)$/', $fname, $m ) ) {
+ $check = $m[1];
+ }
+
+ if ( substr( $check, 0, strlen( $this->testName ) ) !== $this->testName ) {
throw new MWException( 'function name does not start with test class. ' .
$fname . ' vs. ' . $this->testName . '. ' .
'Please provide __METHOD__ to database methods.' );
public function query( $sql, $fname = '', $tempIgnore = false ) {
$this->checkFunctionName( $fname );
- $this->addSql( $sql );
return parent::query( $sql, $fname, $tempIgnore );
}
}
function lastErrno() {
- return -1;
+ return $this->lastError ? $this->lastError['errno'] : -1;
}
function lastError() {
- return 'test';
+ return $this->lastError ? $this->lastError['error'] : 'test';
+ }
+
+ protected function wasKnownStatementRollbackError() {
+ return isset( $this->lastError['wasKnownStatementRollbackError'] )
+ ? $this->lastError['wasKnownStatementRollbackError']
+ : false;
}
function fieldInfo( $table, $field ) {
}
protected function doQuery( $sql ) {
+ $sql = preg_replace( '< /\* .+? \*/>', '', $sql );
+ $this->addSql( $sql );
+
+ if ( $this->nextError ) {
+ $this->lastError = $this->nextError;
+ $this->nextError = null;
+ return false;
+ }
+
$res = $this->nextResult;
$this->nextResult = [];
+ $this->lastError = null;
return new FakeResultWrapper( $res );
}
use Wikimedia\Rdbms\LikeMatch;
use Wikimedia\Rdbms\Database;
use Wikimedia\TestingAccessWrapper;
+use Wikimedia\Rdbms\DBTransactionStateError;
use Wikimedia\Rdbms\DBUnexpectedError;
/**
$this->assertEquals( 0, $this->database->trxLevel() );
}
+ /**
+ * @covers \Wikimedia\Rdbms\Database::query
+ */
+ public function testImplicitTransactionRollback() {
+ $doError = function ( $wasKnown = true ) {
+ $this->database->forceNextQueryError( 666, 'Evilness' );
+ try {
+ $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBError $e ) {
+ $this->assertSame( 666, $e->errno );
+ }
+ };
+
+ $this->database->setFlag( Database::DBO_TRX );
+
+ // Implicit transaction gets silently rolled back
+ $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
+ call_user_func( $doError, false );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->database->commit( __METHOD__, Database::FLUSHING_INTERNAL );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK; BEGIN; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+
+ // ... unless there were prior writes
+ $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ call_user_func( $doError, false );
+ try {
+ $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+ $this->fail( 'Expected exception not thrown' );
+ } catch ( DBTransactionStateError $e ) {
+ }
+ $this->database->rollback( __METHOD__, Database::FLUSHING_INTERNAL );
+ // phpcs:ignore
+ $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; DELETE FROM error WHERE 1; ROLLBACK' );
+ }
+
/**
* @covers \Wikimedia\Rdbms\Database::close
*/