Merge "maintenance: Script to rename titles for Unicode uppercasing changes"
[lhc/web/wiklou.git] / includes / libs / rdbms / database / resultwrapper / ResultWrapper.php
index a9befc2..3e50967 100644 (file)
@@ -4,50 +4,67 @@ namespace Wikimedia\Rdbms;
 
 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;
-       /** @var stdClass|null */
-       protected $currentRow = null;
+       /** @var stdClass|bool|null */
+       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;
                }
        }
 
@@ -63,29 +80,16 @@ class ResultWrapper implements IResultWrapper {
                return $this->getDB()->fetchRow( $this );
        }
 
-       public function seek( $row ) {
-               $this->getDB()->dataSeek( $this, $row );
+       public function seek( $pos ) {
+               $this->getDB()->dataSeek( $this, $pos );
+               $this->pos = $pos;
        }
 
        public function free() {
-               if ( $this->db ) {
-                       $this->db = null;
-               }
+               $this->db = null;
                $this->result = null;
        }
 
-       /**
-        * @return IDatabase
-        * @throws RuntimeException
-        */
-       private function getDB() {
-               if ( !$this->db ) {
-                       throw new RuntimeException( static::class . ' needs a DB handle for iteration.' );
-               }
-
-               return $this->db;
-       }
-
        function rewind() {
                if ( $this->numRows() ) {
                        $this->getDB()->dataSeek( $this, 0 );
@@ -95,8 +99,8 @@ class ResultWrapper implements IResultWrapper {
        }
 
        function current() {
-               if ( is_null( $this->currentRow ) ) {
-                       $this->next();
+               if ( $this->currentRow === null ) {
+                       $this->currentRow = $this->fetchObject();
                }
 
                return $this->currentRow;
@@ -116,6 +120,18 @@ class ResultWrapper implements IResultWrapper {
        function valid() {
                return $this->current() !== false;
        }
+
+       /**
+        * @return IDatabase
+        * @throws RuntimeException
+        */
+       private function getDB() {
+               if ( !$this->db ) {
+                       throw new RuntimeException( "Database handle was already freed" );
+               }
+
+               return $this->db;
+       }
 }
 
 /**