* @param IResultWrapper|ORAResult $res
*/
function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- $res->free();
+ ResultWrapper::unwrap( $res )->free();
}
/**
* @return stdClass|bool
*/
function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $res->fetchObject();
+ return ResultWrapper::unwrap( $res )->fetchObject();
}
/**
* @return stdClass|bool
*/
function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $res->fetchRow();
+ return ResultWrapper::unwrap( $res )->fetchRow();
}
/**
* @return int
*/
function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $res->numRows();
+ return ResultWrapper::unwrap( $res )->numRows();
}
/**
* @return int
*/
function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $res->numFields();
+ return ResultWrapper::unwrap( $res )->numFields();
}
function fieldName( $stmt, $n ) {
if ( $res instanceof ORAResult ) {
$res->seek( $row );
} else {
- $res->result->seek( $row );
+ ResultWrapper::unwrap( $res )->seek( $row );
}
}
* For SELECT queries, this returns either:
* - a) A driver-specific value/resource, only on success. This can be iterated
* over by calling fetchObject()/fetchRow() until there are no more rows.
- * Alternatively, the result can be passed to resultObject() to obtain a
- * ResultWrapper instance which can then be iterated over via "foreach".
+ * Alternatively, the result can be passed to resultObject() to obtain an
+ * IResultWrapper instance which can then be iterated over via "foreach".
* - b) False, on any query failure
*
* For non-SELECT queries, this returns either:
abstract protected function fetchAffectedRowCount();
/**
- * Take the result from a query, and wrap it in a ResultWrapper if
- * necessary. Boolean values are passed through as is, to indicate success
- * of write queries or failure.
+ * Take a query result and wrap it in an iterable result wrapper if necessary.
+ * Booleans are passed through as-is to indicate success/failure of write queries.
*
* Once upon a time, Database::query() returned a bare MySQL result
* resource, and it was necessary to call this function to convert it to
*/
protected function resultObject( $result ) {
if ( !$result ) {
- return false;
- } elseif ( $result instanceof ResultWrapper ) {
+ return false; // failed query
+ } elseif ( $result instanceof IResultWrapper ) {
return $result;
} elseif ( $result === true ) {
- // Successful write query
- return $result;
+ return $result; // succesful write query
} else {
return new ResultWrapper( $this, $result );
}
}
public function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- sqlsrv_free_stmt( $res );
+ sqlsrv_free_stmt( ResultWrapper::unwrap( $res ) );
}
/**
* @return int
*/
public function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
+ $res = ResultWrapper::unwrap( $res );
$ret = sqlsrv_num_rows( $res );
-
if ( $ret === false ) {
// we cannot get an amount of rows from this cursor type
// has_rows returns bool true/false if the result has rows
* @return int
*/
public function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return sqlsrv_num_fields( $res );
+ return sqlsrv_num_fields( ResultWrapper::unwrap( $res ) );
}
/**
* @return int
*/
public function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return sqlsrv_field_metadata( $res )[$n]['Name'];
+ return sqlsrv_field_metadata( ResultWrapper::unwrap( $res ) )[$n]['Name'];
}
/**
}
$this->scrollableCursor = true;
- if ( $ret instanceof ResultWrapper && !is_null( $identity ) ) {
+ if ( $ret instanceof IResultWrapper && !is_null( $identity ) ) {
// Then we want to get the identity column value we were assigned and save it off
$row = $ret->fetchObject();
if ( is_object( $row ) ) {
* @throws DBUnexpectedError
*/
public function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
Wikimedia\suppressWarnings();
- $ok = $this->mysqlFreeResult( $res );
+ $ok = $this->mysqlFreeResult( ResultWrapper::unwrap( $res ) );
Wikimedia\restoreWarnings();
if ( !$ok ) {
throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
* @throws DBUnexpectedError
*/
public function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
Wikimedia\suppressWarnings();
- $row = $this->mysqlFetchObject( $res );
+ $row = $this->mysqlFetchObject( ResultWrapper::unwrap( $res ) );
Wikimedia\restoreWarnings();
$errno = $this->lastErrno();
* @throws DBUnexpectedError
*/
public function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
Wikimedia\suppressWarnings();
- $row = $this->mysqlFetchArray( $res );
+ $row = $this->mysqlFetchArray( ResultWrapper::unwrap( $res ) );
Wikimedia\restoreWarnings();
$errno = $this->lastErrno();
* @return int
*/
function numRows( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
+ if ( is_bool( $res ) ) {
+ $n = 0;
+ } else {
+ Wikimedia\suppressWarnings();
+ $n = $this->mysqlNumRows( ResultWrapper::unwrap( $res ) );
+ Wikimedia\restoreWarnings();
}
- Wikimedia\suppressWarnings();
- $n = !is_bool( $res ) ? $this->mysqlNumRows( $res ) : 0;
- Wikimedia\restoreWarnings();
// Unfortunately, mysql_num_rows does not reset the last errno.
// We are not checking for any errors here, since
* @return int
*/
public function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $this->mysqlNumFields( $res );
+ return $this->mysqlNumFields( ResultWrapper::unwrap( $res ) );
}
/**
* @return string
*/
public function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $this->mysqlFieldName( $res, $n );
+ return $this->mysqlFieldName( ResultWrapper::unwrap( $res ), $n );
}
/**
* @return string
*/
public function fieldType( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $this->mysqlFieldType( $res, $n );
+ return $this->mysqlFieldType( ResultWrapper::unwrap( $res ), $n );
}
/**
* @return bool
*/
public function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return $this->mysqlDataSeek( $res, $row );
+ return $this->mysqlDataSeek( ResultWrapper::unwrap( $res ), $row );
}
/**
if ( !$res ) {
return false;
}
- $n = $this->mysqlNumFields( $res->result );
+ $n = $this->mysqlNumFields( ResultWrapper::unwrap( $res ) );
for ( $i = 0; $i < $n; $i++ ) {
- $meta = $this->mysqlFetchField( $res->result, $i );
+ $meta = $this->mysqlFetchField( ResultWrapper::unwrap( $res ), $i );
if ( $field == $meta->name ) {
return new MySQLField( $meta );
}
}
public function freeResult( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
Wikimedia\suppressWarnings();
- $ok = pg_free_result( $res );
+ $ok = pg_free_result( ResultWrapper::unwrap( $res ) );
Wikimedia\restoreWarnings();
if ( !$ok ) {
throw new DBUnexpectedError( $this, "Unable to free Postgres result\n" );
}
public function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
Wikimedia\suppressWarnings();
- $row = pg_fetch_object( $res );
+ $row = pg_fetch_object( ResultWrapper::unwrap( $res ) );
Wikimedia\restoreWarnings();
# @todo FIXME: HACK HACK HACK HACK debug
}
public function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
Wikimedia\suppressWarnings();
- $row = pg_fetch_array( $res );
+ $row = pg_fetch_array( ResultWrapper::unwrap( $res ) );
Wikimedia\restoreWarnings();
$conn = $this->getBindingHandle();
return 0;
}
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
Wikimedia\suppressWarnings();
- $n = pg_num_rows( $res );
+ $n = pg_num_rows( ResultWrapper::unwrap( $res ) );
Wikimedia\restoreWarnings();
$conn = $this->getBindingHandle();
}
public function numFields( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return pg_num_fields( $res );
+ return pg_num_fields( ResultWrapper::unwrap( $res ) );
}
public function fieldName( $res, $n ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return pg_field_name( $res, $n );
+ return pg_field_name( ResultWrapper::unwrap( $res ), $n );
}
public function insertId() {
}
public function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return pg_result_seek( $res, $row );
+ return pg_result_seek( ResultWrapper::unwrap( $res ), $row );
}
public function lastError() {
* @return string
*/
public function fieldType( $res, $index ) {
- if ( $res instanceof ResultWrapper ) {
- $res = $res->result;
- }
-
- return pg_field_type( $res, $index );
+ return pg_field_type( ResultWrapper::unwrap( $res ), $index );
}
public function encodeBlob( $b ) {
return false;
}
- $r = $res instanceof ResultWrapper ? $res->result : $res;
- $this->lastAffectedRowCount = $r->rowCount();
- $res = new ResultWrapper( $this, $r->fetchAll() );
+ $resource = ResultWrapper::unwrap( $res );
+ $this->lastAffectedRowCount = $resource->rowCount();
+ $res = new ResultWrapper( $this, $resource->fetchAll() );
return $res;
}
*/
function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
- $res->result = null;
- } else {
- $res = null;
+ $res->free();
}
}
* @return stdClass|bool
*/
function fetchObject( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $r =& $res->result;
- } else {
- $r =& $res;
- }
+ $resource =& ResultWrapper::unwrap( $res );
- $cur = current( $r );
+ $cur = current( $resource );
if ( is_array( $cur ) ) {
- next( $r );
+ next( $resource );
$obj = new stdClass;
foreach ( $cur as $k => $v ) {
if ( !is_numeric( $k ) ) {
* @return array|bool
*/
function fetchRow( $res ) {
- if ( $res instanceof ResultWrapper ) {
- $r =& $res->result;
- } else {
- $r =& $res;
- }
- $cur = current( $r );
+ $resource =& ResultWrapper::unwrap( $res );
+ $cur = current( $resource );
if ( is_array( $cur ) ) {
- next( $r );
+ next( $resource );
return $cur;
}
*/
function numRows( $res ) {
// false does not implement Countable
- $r = $res instanceof ResultWrapper ? $res->result : $res;
+ $resource = ResultWrapper::unwrap( $res );
- return is_array( $r ) ? count( $r ) : 0;
+ return is_array( $resource ) ? count( $resource ) : 0;
}
/**
* @return int
*/
function numFields( $res ) {
- $r = $res instanceof ResultWrapper ? $res->result : $res;
- if ( is_array( $r ) && count( $r ) > 0 ) {
+ $resource = ResultWrapper::unwrap( $res );
+ if ( is_array( $resource ) && count( $resource ) > 0 ) {
// The size of the result array is twice the number of fields. (T67578)
- return count( $r[0] ) / 2;
+ return count( $resource[0] ) / 2;
} else {
// If the result is empty return 0
return 0;
* @return bool
*/
function fieldName( $res, $n ) {
- $r = $res instanceof ResultWrapper ? $res->result : $res;
- if ( is_array( $r ) ) {
- $keys = array_keys( $r[0] );
+ $resource = ResultWrapper::unwrap( $res );
+ if ( is_array( $resource ) ) {
+ $keys = array_keys( $resource[0] );
return $keys[$n];
}
* @param int $row
*/
function dataSeek( $res, $row ) {
- if ( $res instanceof ResultWrapper ) {
- $r =& $res->result;
- } else {
- $r =& $res;
- }
- reset( $r );
+ $resource =& ResultWrapper::unwrap( $res );
+ reset( $resource );
if ( $row > 0 ) {
for ( $i = 0; $i < $row; $i++ ) {
- next( $r );
+ next( $resource );
}
}
}
* Overloads the relevant methods of the real ResultsWrapper so it
* doesn't go anywhere near an actual database.
*/
-class FakeResultWrapper extends ResultWrapper {
- /** @var stdClass[]|array[] $result */
+class FakeResultWrapper implements IResultWrapper {
+ /** @var stdClass[]|array[] */
+ protected $result;
+
+ /** @var int */
+ protected $pos = 0;
/**
- * @param stdClass[]|array[] $rows
+ * @param stdClass[]|array[]|FakeResultWrapper $result
*/
- function __construct( array $rows ) {
- parent::__construct( null, $rows );
+ public function __construct( $result ) {
+ if ( $result instanceof self ) {
+ $this->result = $result->result;
+ } else {
+ $this->result = $result;
+ }
}
- function numRows() {
+ public function numRows() {
return count( $this->result );
}
- function fetchObject() {
+ public function fetchObject() {
$current = $this->current();
$this->next();
return $current;
}
- function fetchRow() {
+ public function fetchRow() {
$row = $this->valid() ? $this->result[$this->pos] : false;
$this->next();
return is_object( $row ) ? get_object_vars( $row ) : $row;
}
- function seek( $pos ) {
+ public function seek( $pos ) {
$this->pos = $pos;
}
- function free() {
+ public function free() {
$this->result = null;
}
- function rewind() {
+ public function rewind() {
$this->pos = 0;
}
- function current() {
+ public function current() {
$row = $this->valid() ? $this->result[$this->pos] : false;
return is_array( $row ) ? (object)$row : $row;
}
- function key() {
+ public function key() {
return $this->pos;
}
- function next() {
+ public function next() {
$this->pos++;
return $this->current();
}
- function valid() {
+ public function valid() {
return array_key_exists( $this->pos, $this->result );
}
}
use stdClass;
use RuntimeException;
+use InvalidArgumentException;
/**
* Result wrapper for grabbing data queried from an IDatabase object
*
+ * Only IDatabase-related classes should construct these. Other code may
+ * use the FakeResultWrapper class for convenience or compatibility shims.
+ *
* Note that using the Iterator methods in combination with the non-Iterator
- * DB result iteration functions may cause rows to be skipped or repeated.
+ * IDatabase result iteration functions may cause rows to be skipped or repeated.
*
* By default, this will use the iteration methods of the IDatabase handle if provided.
* Subclasses can override methods to make it solely work on the result resource instead.
- * If no database is provided, and the subclass does not override the DB iteration methods,
- * then a RuntimeException will be thrown when iteration is attempted.
- *
- * The result resource field should not be accessed from non-Database related classes.
- * It is database class specific and is stored here to associate iterators with queries.
*
* @ingroup Database
*/
class ResultWrapper implements IResultWrapper {
- /** @var resource|array|null Optional underlying result handle for subclass usage */
- public $result;
-
- /** @var IDatabase|null */
+ /** @var IDatabase */
protected $db;
+ /** @var mixed|null RDBMS driver-specific result resource */
+ protected $result;
/** @var int */
protected $pos = 0;
protected $currentRow;
/**
- * Create a row iterator from a result resource and an optional Database object
- *
- * Only Database-related classes should construct ResultWrapper. Other code may
- * use the FakeResultWrapper subclass for convenience or compatibility shims, however.
- *
- * @param IDatabase|null $db Optional database handle
- * @param ResultWrapper|array|resource $result Optional underlying result handle
+ * @param IDatabase $db Database handle that the result comes from
+ * @param self|mixed $result RDBMS driver-specific result resource
*/
- public function __construct( IDatabase $db = null, $result ) {
+ public function __construct( IDatabase $db, $result ) {
$this->db = $db;
- if ( $result instanceof ResultWrapper ) {
+ if ( $result instanceof self ) {
$this->result = $result->result;
- } else {
+ } elseif ( $result !== null ) {
$this->result = $result;
+ } else {
+ throw new InvalidArgumentException( "Null result resource provided" );
+ }
+ }
+
+ /**
+ * Get the underlying RDBMS driver-specific result resource
+ *
+ * The result resource field should not be accessed from non-Database related classes.
+ * It is database class specific and is stored here to associate iterators with queries.
+ *
+ * @param self|mixed &$res
+ * @return mixed
+ * @since 1.34
+ */
+ public static function &unwrap( &$res ) {
+ if ( $res instanceof self ) {
+ if ( $res->result === null ) {
+ throw new RuntimeException( "The result resource was already freed" );
+ }
+
+ return $res->result;
+ } else {
+ return $res;
}
}
*/
private function getDB() {
if ( !$this->db ) {
- throw new RuntimeException( static::class . ' needs a DB handle for iteration.' );
+ throw new RuntimeException( "Database handle was already freed" );
}
return $this->db;
*/
function reallyDoQuery( $offset, $limit, $order ) {
$asc = ( $order === self::QUERY_ASCENDING );
- $result = new FakeResultWrapper( [] );
$messageNames = $this->getAllMessages( $order );
$statuses = self::getCustomisedStatuses( $messageNames, $this->langcode, $this->foreign );
+ $rows = [];
$count = 0;
foreach ( $messageNames as $key ) {
$customised = isset( $statuses['pages'][$key] );
) {
$actual = $this->msg( $key )->inLanguage( $this->lang )->plain();
$default = $this->msg( $key )->inLanguage( $this->lang )->useDatabase( false )->plain();
- $result->result[] = [
+ $rows[] = [
'am_title' => $key,
'am_actual' => $actual,
'am_default' => $default,
}
}
- return $result;
+ return new FakeResultWrapper( $rows );
}
protected function getStartBody() {
use Wikimedia\Rdbms\DBConnRef;
use Wikimedia\Rdbms\FakeResultWrapper;
use Wikimedia\Rdbms\ILoadBalancer;
-use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\IResultWrapper;
/**
* @covers Wikimedia\Rdbms\DBConnRef
$lb = $this->getLoadBalancerMock();
$ref = new DBConnRef( $lb, $this->getDatabaseMock(), DB_MASTER );
- $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ $this->assertInstanceOf( IResultWrapper::class, $ref->select( 'whatever', '*' ) );
}
public function testConstruct_params() {
DB_MASTER
);
- $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ $this->assertInstanceOf( IResultWrapper::class, $ref->select( 'whatever', '*' ) );
$this->assertEquals( DB_MASTER, $ref->getReferenceRole() );
$ref2 = new DBConnRef(
private function innerMethodForTestDestruct( ILoadBalancer $lb ) {
$ref = $lb->getConnectionRef( DB_REPLICA );
- $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ $this->assertInstanceOf( IResultWrapper::class, $ref->select( 'whatever', '*' ) );
}
public function testConstruct_failure() {
public function testSelect() {
// select should get passed through normally
$ref = $this->getDBConnRef();
- $this->assertInstanceOf( ResultWrapper::class, $ref->select( 'whatever', '*' ) );
+ $this->assertInstanceOf( IResultWrapper::class, $ref->select( 'whatever', '*' ) );
}
public function testToString() {