Documentation
[lhc/web/wiklou.git] / includes / db / DatabaseIbm_db2.php
index 2e6f8c1..ed37939 100644 (file)
@@ -13,7 +13,7 @@
  * This represents a column in a DB2 database
  * @ingroup Database
  */
-class IBM_DB2Field {
+class IBM_DB2Field implements Field {
        private $name = '';
        private $tablename = '';
        private $type = '';
@@ -75,7 +75,7 @@ SQL;
         * Can column be null?
         * @return bool true or false
         */
-       function nullable() { return $this->nullable; }
+       function isNullable() { return $this->nullable; }
        /**
         * How much can you fit in the column per row?
         * @return int length
@@ -114,9 +114,8 @@ class DatabaseIbm_db2 extends DatabaseBase {
        protected $mPHPError = false;
 
        protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
-       protected $mOut, $mOpened = false;
+       protected $mOpened = false;
 
-       protected $mFailFunction;
        protected $mTablePrefix;
        protected $mFlags;
        protected $mTrxLevel = 0;
@@ -145,7 +144,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
        public $mStmtOptions = array();
 
        /** Default schema */
-       const USE_GLOBAL = 'mediawiki';
+       const USE_GLOBAL = 'get from global';
 
        /** Option that applies to nothing */
        const NONE_OPTION = 0x00;
@@ -246,35 +245,21 @@ class DatabaseIbm_db2 extends DatabaseBase {
                return 'ibm_db2';
        }
 
-       ######################################
-       # Setup
-       ######################################
-
-
        /**
         *
         * @param $server String: hostname of database server
         * @param $user String: username
         * @param $password String: password
         * @param $dbName String: database name on the server
-        * @param $failFunction Callback (optional)
         * @param $flags Integer: database behaviour flags (optional, unused)
         * @param $schema String
         */
-       public function DatabaseIbm_db2( $server = false, $user = false,
+       public function __construct( $server = false, $user = false,
                                                        $password = false,
-                                                       $dbName = false, $failFunction = false, $flags = 0,
+                                                       $dbName = false, $flags = 0,
                                                        $schema = self::USE_GLOBAL )
        {
-
-               global $wgOut, $wgDBmwschema;
-               # Can't get a reference if it hasn't been set yet
-               if ( !isset( $wgOut ) ) {
-                       $wgOut = null;
-               }
-               $this->mOut =& $wgOut;
-               $this->mFailFunction = $failFunction;
-               $this->mFlags = DBO_TRX | $flags;
+               global $wgDBmwschema;
 
                if ( $schema == self::USE_GLOBAL ) {
                        $this->mSchema = $wgDBmwschema;
@@ -283,6 +268,10 @@ class DatabaseIbm_db2 extends DatabaseBase {
                }
 
                // configure the connection and statement objects
+               /*
+               $this->setDB2Option( 'cursor', 'DB2_SCROLLABLE',
+                       self::CONN_OPTION | self::STMT_OPTION );
+               */
                $this->setDB2Option( 'db2_attr_case', 'DB2_CASE_LOWER',
                        self::CONN_OPTION | self::STMT_OPTION );
                $this->setDB2Option( 'deferred_prepare', 'DB2_DEFERRED_PREPARE_ON',
@@ -290,7 +279,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
                $this->setDB2Option( 'rowcount', 'DB2_ROWCOUNT_PREFETCH_ON',
                        self::STMT_OPTION );
 
-               $this->open( $server, $user, $password, $dbName );
+               parent::__construct( $server, $user, $password, $dbName, DBO_TRX | $flags );
        }
 
        /**
@@ -336,26 +325,17 @@ class DatabaseIbm_db2 extends DatabaseBase {
         * @return a fresh connection
         */
        public function open( $server, $user, $password, $dbName ) {
-               // Load the port number
-               global $wgDBport;
                wfProfileIn( __METHOD__ );
 
-               // Load IBM DB2 driver if missing
+               # Load IBM DB2 driver if missing
                wfDl( 'ibm_db2' );
 
-               // Test for IBM DB2 support, to avoid suppressed fatal error
+               # Test for IBM DB2 support, to avoid suppressed fatal error
                if ( !function_exists( 'db2_connect' ) ) {
-                       $error = <<<ERROR
-DB2 functions missing, have you enabled the ibm_db2 extension for PHP?
-
-ERROR;
-                       $this->installPrint( $error );
-                       $this->reportConnectionError( $error );
+                       throw new DBConnectionError( $this, "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?" );
                }
 
-               if ( strlen( $user ) < 1 ) {
-                       return null;
-               }
+               global $wgDBport;
 
                // Close existing connection
                $this->close();
@@ -368,22 +348,26 @@ ERROR;
 
                $this->openUncataloged( $dbName, $user, $password, $server, $port );
 
-               // Apply connection config
-               db2_set_option( $this->mConn, $this->mConnOptions, 1 );
-               // Some MediaWiki code is still transaction-less (?).
-               // The strategy is to keep AutoCommit on for that code
-               //  but switch it off whenever a transaction is begun.
-               db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
-
                if ( !$this->mConn ) {
                        $this->installPrint( "DB connection error\n" );
                        $this->installPrint(
                                "Server: $server, Database: $dbName, User: $user, Password: "
                                . substr( $password, 0, 3 ) . "...\n" );
                        $this->installPrint( $this->lastError() . "\n" );
-                       return null;
+                       wfProfileOut( __METHOD__ );
+                       wfDebug( "DB connection error\n" );
+                       wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
+                       wfDebug( $this->lastError() . "\n" );
+                       throw new DBConnectionError( $this, $this->lastError() );
                }
 
+               // Apply connection config
+               db2_set_option( $this->mConn, $this->mConnOptions, 1 );
+               // Some MediaWiki code is still transaction-less (?).
+               // The strategy is to keep AutoCommit on for that code
+               //  but switch it off whenever a transaction is begun.
+               db2_autocommit( $this->mConn, DB2_AUTOCOMMIT_ON );
+
                $this->mOpened = true;
                $this->applySchema();
 
@@ -403,16 +387,8 @@ ERROR;
         */
        protected function openUncataloged( $dbName, $user, $password, $server, $port )
        {
-               $str = "DRIVER={IBM DB2 ODBC DRIVER};";
-               $str .= "DATABASE=$dbName;";
-               $str .= "HOSTNAME=$server;";
-               // port was formerly validated to not be 0
-               $str .= "PORT=$port;";
-               $str .= "PROTOCOL=TCPIP;";
-               $str .= "UID=$user;";
-               $str .= "PWD=$password;";
-
-               @$this->mConn = db2_pconnect( $str, $user, $password );
+               $dsn = "DRIVER={IBM DB2 ODBC DRIVER};DATABASE=$dbName;CHARSET=UTF-8;HOSTNAME=$server;PORT=$port;PROTOCOL=TCPIP;UID=$user;PWD=$password;";
+               @$this->mConn = db2_pconnect($dsn, "", "", array());
        }
 
        /**
@@ -431,24 +407,6 @@ ERROR;
                }
        }
 
-       /**
-        * Returns a fresh instance of this class
-        *
-        * @param $server String: hostname of database server
-        * @param $user String: username
-        * @param $password String
-        * @param $dbName String: database name on the server
-        * @param $failFunction Callback (optional)
-        * @param $flags Integer: database behaviour flags (optional, unused)
-        * @return DatabaseIbm_db2 object
-        */
-       static function newFromParams( $server, $user, $password, $dbName,
-               $failFunction = false, $flags = 0 )
-       {
-               return new DatabaseIbm_db2( $server, $user, $password, $dbName,
-                       $failFunction, $flags );
-       }
-
        /**
         * Retrieves the most current database error
         * Forces a database rollback
@@ -500,10 +458,16 @@ ERROR;
        /*private*/
        public function doQuery( $sql ) {
                $this->applySchema();
-
+               
+               // Needed to handle any UTF-8 encoding issues in the raw sql
+               // Note that we fully support prepared statements for DB2
+               // prepare() and execute() should be used instead of doQuery() whenever possible
+               $sql = utf8_decode($sql);
+               
                $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
                if( $ret == false ) {
                        $error = db2_stmt_errormsg();
+                       
                        $this->installPrint( "<pre>$sql</pre>" );
                        $this->installPrint( $error );
                        throw new DBUnexpectedError( $this, 'SQL error: '
@@ -528,10 +492,11 @@ ERROR;
         */
        public function tableExists( $table ) {
                $schema = $this->mSchema;
-               $sql = <<< EOF
-SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST
-WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema'
-EOF;
+               
+               $sql = "SELECT COUNT( * ) FROM SYSIBM.SYSTABLES ST WHERE ST.NAME = '" . 
+                       strtoupper( $table ) .
+                       "' AND ST.CREATOR = '" .
+                       strtoupper( $schema ) . "'";
                $res = $this->query( $sql );
                if ( !$res ) {
                        return false;
@@ -580,12 +545,15 @@ EOF;
                if ( $res instanceof ResultWrapper ) {
                        $res = $res->result;
                }
-               @$row = db2_fetch_array( $res );
-               if ( $this->lastErrno() ) {
-                       throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
-                               . htmlspecialchars( $this->lastError() ) );
+               if ( db2_num_rows( $res ) > 0) {
+                       @$row = db2_fetch_array( $res );
+                       if ( $this->lastErrno() ) {
+                               throw new DBUnexpectedError( $this, 'Error in fetchRow(): '
+                                       . htmlspecialchars( $this->lastError() ) );
+                       }
+                       return $row;
                }
-               return $row;
+               return false;
        }
 
        /**
@@ -611,7 +579,6 @@ EOF;
                        } else {
                                print '<li>Foreign keys done</li>';
                        }
-                       $res = null;
 
                        // TODO: populate interwiki links
 
@@ -758,7 +725,7 @@ EOF;
        function makeList( $a, $mode = LIST_COMMA ) {
                if ( !is_array( $a ) ) {
                        throw new DBUnexpectedError( $this,
-                               'DatabaseBase::makeList called with incorrect parameters' );
+                               'DatabaseIbm_db2::makeList called with incorrect parameters' );
                }
 
                // if this is for a prepared UPDATE statement
@@ -811,9 +778,10 @@ EOF;
         * Handle reserved keyword replacement in table names
         *
         * @param $name Object
+        * @param $name Boolean
         * @return String
         */
-       public function tableName( $name ) {
+       public function tableName( $name, $quoted = true ) {
                // we want maximum compatibility with MySQL schema
                return $name;
        }
@@ -929,9 +897,9 @@ EOF;
                } else {
                        $sql .= '( ?' . str_repeat( ',?', $key_count-1 ) . ' )';
                }
-               //$this->installPrint( "Preparing the following SQL:" );
-               //$this->installPrint( "$sql" );
-               //$this->installPrint( print_r( $args, true ));
+               $this->installPrint( "Preparing the following SQL:" );
+               $this->installPrint( "$sql" );
+               $this->installPrint( print_r( $args, true ));
                $stmt = $this->prepare( $sql );
 
                // start a transaction/enter transaction mode
@@ -963,7 +931,7 @@ EOF;
                                $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
                                db2_exec( $this->mConn, $overhead, $this->mStmtOptions );
 
-                               $this->execute( $stmt, $row );
+                               $res2 = $this->execute( $stmt, $row );
 
                                if ( !$res2 ) {
                                        $this->installPrint( 'Last error:' );
@@ -1004,18 +972,22 @@ EOF;
         */
        private function removeNullPrimaryKeys( $table, $args ) {
                $schema = $this->mSchema;
+               
                // find out the primary keys
-               $keyres = db2_primary_keys( $this->mConn, null, strtoupper( $schema ),
-                       strtoupper( $table )
-               );
+               $keyres = $this->doQuery( "SELECT NAME FROM SYSIBM.SYSCOLUMNS WHERE TBNAME = '" 
+                 . strtoupper( $table ) 
+                 . "' AND TBCREATOR = '" 
+                 . strtoupper( $schema ) 
+                 . "' AND KEYSEQ > 0" );
+               
                $keys = array();
                for (
-                       $row = $this->fetchObject( $keyres );
+                       $row = $this->fetchRow( $keyres );
                        $row != null;
-                       $row = $this->fetchObject( $keyres )
+                       $row = $this->fetchRow( $keyres )
                )
                {
-                       $keys[] = strtolower( $row->column_name );
+                       $keys[] = strtolower( $row[0] );
                }
                // remove primary keys
                foreach ( $args as $ai => $row ) {
@@ -1042,7 +1014,7 @@ EOF;
         *                 more of IGNORE, LOW_PRIORITY
         * @return Boolean
         */
-       public function update( $table, $values, $conds, $fname = 'Database::update',
+       public function update( $table, $values, $conds, $fname = 'DatabaseIbm_db2::update',
                $options = array() )
        {
                $table = $this->tableName( $table );
@@ -1067,10 +1039,10 @@ EOF;
         *
         * Use $conds == "*" to delete all rows
         */
-       public function delete( $table, $conds, $fname = 'Database::delete' ) {
+       public function delete( $table, $conds, $fname = 'DatabaseIbm_db2::delete' ) {
                if ( !$conds ) {
                        throw new DBUnexpectedError( $this,
-                       'Database::delete() called with no conditions' );
+                               'DatabaseIbm_db2::delete() called with no conditions' );
                }
                $table = $this->tableName( $table );
                $sql = "DELETE FROM $table";
@@ -1150,10 +1122,7 @@ EOF;
                        }
 
                        # Now insert the row
-                       $sql = "INSERT INTO $table ( "
-                               . $this->makeList( array_keys( $row ), LIST_NAMES )
-                               .' ) VALUES ( ' . $this->makeList( $row, LIST_COMMA ) . ' )';
-                       $this->query( $sql, $fname );
+                       $this->insert($table, $row);
                }
        }
 
@@ -1167,6 +1136,7 @@ EOF;
                if ( $res instanceof ResultWrapper ) {
                        $res = $res->result;
                }
+               
                if ( $this->mNumRows ) {
                        return $this->mNumRows;
                } else {
@@ -1254,11 +1224,9 @@ EOF;
                        $join_conds );
 
                // We must adjust for offset
-               if ( isset( $options['LIMIT'] ) ) {
-                       if ( isset ( $options['OFFSET'] ) ) {
-                               $limit = $options['LIMIT'];
-                               $offset = $options['OFFSET'];
-                       }
+               if ( isset( $options['LIMIT'] ) && isset ( $options['OFFSET'] ) ) {
+                       $limit = $options['LIMIT'];
+                       $offset = $options['OFFSET'];
                }
 
                // DB2 does not have a proper num_rows() function yet, so we must emulate
@@ -1470,7 +1438,7 @@ SQL;
         * @return Bool
         */
        public function indexUnique ( $table, $index,
-               $fname = 'Database::indexUnique' )
+               $fname = 'DatabaseIbm_db2::indexUnique' )
        {
                $table = $this->tableName( $table );
                $sql = <<<SQL
@@ -1525,7 +1493,7 @@ SQL;
        {
                if ( !$conds ) {
                        throw new DBUnexpectedError( $this,
-                       'Database::deleteJoin() called with empty $conds' );
+                               'DatabaseIbm_db2::deleteJoin() called with empty $conds' );
                }
 
                $delTable = $this->tableName( $delTable );
@@ -1533,7 +1501,7 @@ SQL;
                $sql = <<<SQL
 DELETE FROM $delTable
 WHERE $delVar IN (
-  SELECT $joinVar FROM $joinTable
+       SELECT $joinVar FROM $joinTable
 
 SQL;
                if ( $conds != '*' ) {