rdbms: make implement IResultWrapper directly instead of via inheritence
[lhc/web/wiklou.git] / includes / db / DatabaseOracle.php
index 7f79ca1..a123d00 100644 (file)
  * @ingroup Database
  */
 
+use Wikimedia\AtEase\AtEase;
 use Wikimedia\Timestamp\ConvertibleTimestamp;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\DatabaseDomain;
 use Wikimedia\Rdbms\Blob;
 use Wikimedia\Rdbms\ResultWrapper;
+use Wikimedia\Rdbms\IResultWrapper;
 use Wikimedia\Rdbms\DBConnectionError;
 use Wikimedia\Rdbms\DBUnexpectedError;
 use Wikimedia\Rdbms\DBExpectedError;
@@ -52,19 +54,25 @@ class DatabaseOracle extends Database {
        /** @var array */
        private $mFieldInfoCache = [];
 
-       function __construct( array $p ) {
-               $p['tablePrefix'] = strtoupper( $p['tablePrefix'] );
-               parent::__construct( $p );
+       /** @var string[] Map of (reserved table name => alternate table name) */
+       private $keywordTableMap = [];
 
-               // @TODO: dependency inject
-               Hooks::run( 'DatabaseOraclePostInit', [ $this ] );
+       /**
+        * @see Database::__construct()
+        * @param array $params Additional parameters include:
+        *   - keywordTableMap : Map of reserved table names to alternative table names to use
+        */
+       function __construct( array $params ) {
+               $this->keywordTableMap = $params['keywordTableMap'] ?? [];
+               $params['tablePrefix'] = strtoupper( $params['tablePrefix'] );
+               parent::__construct( $params );
        }
 
        function __destruct() {
-               if ( $this->opened ) {
-                       Wikimedia\suppressWarnings();
+               if ( $this->conn ) {
+                       AtEase::suppressWarnings();
                        $this->close();
-                       Wikimedia\restoreWarnings();
+                       AtEase::restoreWarnings();
                }
        }
 
@@ -152,8 +160,6 @@ class DatabaseOracle extends Database {
                        throw new DBConnectionError( $this, $this->lastError() );
                }
 
-               $this->opened = true;
-
                # removed putenv calls because they interfere with the system globaly
                $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
                $this->doQuery( 'ALTER SESSION SET NLS_TIMESTAMP_TZ_FORMAT=\'DD-MM-YYYY HH24:MI:SS.FF6\'' );
@@ -172,7 +178,7 @@ class DatabaseOracle extends Database {
        }
 
        function execFlags() {
-               return $this->trxLevel ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
+               return $this->trxLevel() ? OCI_NO_AUTO_COMMIT : OCI_COMMIT_ON_SUCCESS;
        }
 
        /**
@@ -180,7 +186,6 @@ class DatabaseOracle extends Database {
         * @return bool|mixed|ORAResult
         */
        protected function doQuery( $sql ) {
-               wfDebug( "SQL: [$sql]\n" );
                if ( !mb_check_encoding( (string)$sql, 'UTF-8' ) ) {
                        throw new DBUnexpectedError( $this, "SQL encoding is invalid\n$sql" );
                }
@@ -245,62 +250,42 @@ class DatabaseOracle extends Database {
 
        /**
         * Frees resources associated with the LOB descriptor
-        * @param ResultWrapper|ORAResult $res
+        * @param IResultWrapper|ORAResult $res
         */
        function freeResult( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               $res->free();
+               ResultWrapper::unwrap( $res )->free();
        }
 
        /**
-        * @param ResultWrapper|ORAResult $res
-        * @return mixed
+        * @param IResultWrapper|ORAResult $res
+        * @return stdClass|bool
         */
        function fetchObject( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $res->fetchObject();
+               return ResultWrapper::unwrap( $res )->fetchObject();
        }
 
        /**
-        * @param ResultWrapper|ORAResult $res
-        * @return mixed
+        * @param IResultWrapper|ORAResult $res
+        * @return stdClass|bool
         */
        function fetchRow( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $res->fetchRow();
+               return ResultWrapper::unwrap( $res )->fetchRow();
        }
 
        /**
-        * @param ResultWrapper|ORAResult $res
+        * @param IResultWrapper|ORAResult $res
         * @return int
         */
        function numRows( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $res->numRows();
+               return ResultWrapper::unwrap( $res )->numRows();
        }
 
        /**
-        * @param ResultWrapper|ORAResult $res
+        * @param IResultWrapper|ORAResult $res
         * @return int
         */
        function numFields( $res ) {
-               if ( $res instanceof ResultWrapper ) {
-                       $res = $res->result;
-               }
-
-               return $res->numFields();
+               return ResultWrapper::unwrap( $res )->numFields();
        }
 
        function fieldName( $stmt, $n ) {
@@ -321,7 +306,7 @@ class DatabaseOracle extends Database {
                if ( $res instanceof ORAResult ) {
                        $res->seek( $row );
                } else {
-                       $res->result->seek( $row );
+                       ResultWrapper::unwrap( $res )->seek( $row );
                }
        }
 
@@ -465,7 +450,7 @@ class DatabaseOracle extends Database {
                $this->mLastResult = $stmt = oci_parse( $this->conn, $sql );
                if ( $stmt === false ) {
                        $e = oci_error( $this->conn );
-                       $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+                       $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
 
                        return false;
                }
@@ -491,7 +476,7 @@ class DatabaseOracle extends Database {
                                $val = $this->getVerifiedUTF8( $val );
                                if ( oci_bind_by_name( $stmt, ":$col", $val, -1, SQLT_CHR ) === false ) {
                                        $e = oci_error( $stmt );
-                                       $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+                                       $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
 
                                        return false;
                                }
@@ -525,7 +510,7 @@ class DatabaseOracle extends Database {
                if ( oci_execute( $stmt, $this->execFlags() ) === false ) {
                        $e = oci_error( $stmt );
                        if ( !$this->ignoreDupValOnIndex || $e['code'] != '1' ) {
-                               $this->reportQueryError( $e['message'], $e['code'], $sql, __METHOD__ );
+                               $this->reportQueryError( $e['message'], $e['code'], $sql, $fname );
 
                                return false;
                        } else {
@@ -543,7 +528,7 @@ class DatabaseOracle extends Database {
                        }
                }
 
-               if ( !$this->trxLevel ) {
+               if ( !$this->trxLevel() ) {
                        oci_commit( $this->conn );
                }
 
@@ -619,24 +604,21 @@ class DatabaseOracle extends Database {
                return parent::upsert( $table, $rows, $uniqueIndexes, $set, $fname );
        }
 
-       function tableName( $name, $format = 'quoted' ) {
-               /*
-               Replace reserved words with better ones
-               Using uppercase because that's the only way Oracle can handle
-               quoted tablenames
-               */
-               switch ( $name ) {
-                       case 'user':
-                               $name = 'MWUSER';
-                               break;
-                       case 'text':
-                               $name = 'PAGECONTENT';
-                               break;
-               }
+       public function tableName( $name, $format = 'quoted' ) {
+               // Replace reserved words with better ones
+               $name = $this->remappedTableName( $name );
 
                return strtoupper( parent::tableName( $name, $format ) );
        }
 
+       /**
+        * @param string $name
+        * @return string Value of $name or remapped name if $name is a reserved keyword
+        */
+       public function remappedTableName( $name ) {
+               return $this->keywordTableMap[$name] ?? $name;
+       }
+
        function tableNameInternal( $name ) {
                $name = $this->tableName( $name );
 
@@ -647,23 +629,31 @@ class DatabaseOracle extends Database {
         * Return sequence_name if table has a sequence
         *
         * @param string $table
-        * @return bool
+        * @return string[]|bool
         */
        private function getSequenceData( $table ) {
                if ( $this->sequenceData == null ) {
-                       $result = $this->doQuery( "SELECT lower(asq.sequence_name),
-                               lower(atc.table_name),
-                               lower(atc.column_name)
-                       FROM all_sequences asq, all_tab_columns atc
-                       WHERE decode(
-                                       atc.table_name,
-                                       '{$this->tablePrefix}MWUSER',
-                                       '{$this->tablePrefix}USER',
-                                       atc.table_name
-                               ) || '_' ||
-                               atc.column_name || '_SEQ' = '{$this->tablePrefix}' || asq.sequence_name
-                               AND asq.sequence_owner = upper('{$this->getDBname()}')
-                               AND atc.owner = upper('{$this->getDBname()}')" );
+                       $dbname = $this->currentDomain->getDatabase();
+                       $prefix = $this->currentDomain->getTablePrefix();
+                       // See https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions040.htm
+                       $decodeArgs = [ 'atc.table_name' ]; // the switch
+                       foreach ( $this->keywordTableMap as $reserved => $alternative ) {
+                               $search = strtoupper( $prefix . $alternative ); // case
+                               $replace = strtoupper( $prefix . $reserved ); // result
+                               $decodeArgs[] = $this->addQuotes( $search );
+                               $decodeArgs[] = $this->addQuotes( $replace );
+                       }
+                       $decodeArgs[] = [ 'atc.table_name' ]; // default
+                       $decodeArgs = implode( ', ', $decodeArgs );
+
+                       $result = $this->doQuery(
+                               "SELECT lower(asq.sequence_name), lower(atc.table_name), lower(atc.column_name)
+                               FROM all_sequences asq, all_tab_columns atc
+                               WHERE decode({$decodeArgs}) || '_' ||
+                               atc.column_name || '_SEQ' = '{$prefix}' || asq.sequence_name
+                               AND asq.sequence_owner = upper('{$dbname}')
+                               AND atc.owner = upper('{$dbname}')"
+                       );
 
                        while ( ( $row = $result->fetchRow() ) !== false ) {
                                $this->sequenceData[$row[1]] = [
@@ -717,13 +707,14 @@ class DatabaseOracle extends Database {
                $fname = __METHOD__
        ) {
                $temporary = $temporary ? 'TRUE' : 'FALSE';
+               $tablePrefix = $this->currentDomain->getTablePrefix();
 
                $newName = strtoupper( $newName );
                $oldName = strtoupper( $oldName );
 
-               $tabName = substr( $newName, strlen( $this->tablePrefix ) );
+               $tabName = substr( $newName, strlen( $tablePrefix ) );
                $oldPrefix = substr( $oldName, 0, strlen( $oldName ) - strlen( $tabName ) );
-               $newPrefix = strtoupper( $this->tablePrefix );
+               $newPrefix = strtoupper( $tablePrefix );
 
                return $this->doQuery( "BEGIN DUPLICATE_TABLE( '$tabName', " .
                        "'$oldPrefix', '$newPrefix', $temporary ); END;" );
@@ -931,26 +922,24 @@ class DatabaseOracle extends Database {
        }
 
        protected function doBegin( $fname = __METHOD__ ) {
-               $this->trxLevel = 1;
-               $this->doQuery( 'SET CONSTRAINTS ALL DEFERRED' );
+               $this->query( 'SET CONSTRAINTS ALL DEFERRED' );
        }
 
        protected function doCommit( $fname = __METHOD__ ) {
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        $ret = oci_commit( $this->conn );
                        if ( !$ret ) {
                                throw new DBUnexpectedError( $this, $this->lastError() );
                        }
-                       $this->trxLevel = 0;
-                       $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
+                       $this->query( 'SET CONSTRAINTS ALL IMMEDIATE' );
                }
        }
 
        protected function doRollback( $fname = __METHOD__ ) {
-               if ( $this->trxLevel ) {
+               if ( $this->trxLevel() ) {
                        oci_rollback( $this->conn );
-                       $this->trxLevel = 0;
-                       $this->doQuery( 'SET CONSTRAINTS ALL IMMEDIATE' );
+                       $ignoreErrors = true;
+                       $this->query( 'SET CONSTRAINTS ALL IMMEDIATE', $fname, $ignoreErrors );
                }
        }
 
@@ -1327,7 +1316,7 @@ class DatabaseOracle extends Database {
                        }
                }
 
-               if ( !$this->trxLevel ) {
+               if ( !$this->trxLevel() ) {
                        oci_commit( $this->conn );
                }