config/index.php:
[lhc/web/wiklou.git] / includes / db / DatabaseIbm_db2.php
index 59b3024..3e05244 100644 (file)
 class BlankObject {
 }
 
+
 /**
  * This represents a column in a DB2 database
  * @ingroup Database
  */
 class IBM_DB2Field {
-       private $name, $tablename, $type, $nullable, $max_length;
+       private $name = '';
+       private $tablename = '';
+       private $type = '';
+       private $nullable = false;
+       private $max_length = 0;
 
        /**
         * Builder method for the class 
-        * @param Object $db Database interface
+        * @param DatabaseIbm_db2 $db Database interface
         * @param string $table table name
         * @param string $field column name
         * @return IBM_DB2Field
@@ -136,11 +141,26 @@ class DatabaseIbm_db2 extends DatabaseBase {
        /// Number of rows returned by last SELECT
        protected $mNumRows = NULL;
        
+       /// Connection config options - see constructor
+       public $mConnOptions = array();
+       /// Statement config options -- see constructor
+       public $mStmtOptions = array();
+       
        
        const CATALOGED = "cataloged";
        const UNCATALOGED = "uncataloged";
        const USE_GLOBAL = "get from global";
        
+       const NONE_OPTION = 0x00;
+       const CONN_OPTION = 0x01;
+       const STMT_OPTION = 0x02;
+       
+       const REGULAR_MODE = 'regular';
+       const INSTALL_MODE = 'install';
+       
+       // Whether this is regular operation or the initial installation
+       protected $mMode = self::REGULAR_MODE;
+       
        /// Last sequence value used for a primary key
        protected $mInsertId = NULL;
        
@@ -219,7 +239,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
         */
        
        /*
-        * These need to be implemented TODO
+        * These have been implemented
         * 
         * Administrative: 7 / 7
         * constructor [Done]
@@ -412,9 +432,42 @@ class DatabaseIbm_db2 extends DatabaseBase {
                        $this->mSchema = $schema;
                }
                
+               // configure the connection and statement objects
+               $this->setDB2Option('db2_attr_case', 'DB2_CASE_LOWER', self::CONN_OPTION | self::STMT_OPTION);
+               $this->setDB2Option('deferred_prepare', 'DB2_DEFERRED_PREPARE_ON', self::STMT_OPTION);
+               $this->setDB2Option('rowcount', 'DB2_ROWCOUNT_PREFETCH_ON', self::STMT_OPTION);
+               
                $this->open( $server, $user, $password, $dbName);
        }
        
+       /**
+        * Enables options only if the ibm_db2 extension version supports them
+        * @param string $name Name of the option in the options array
+        * @param string $const Name of the constant holding the right option value
+        * @param int $type Whether this is a Connection or Statement otion
+        */
+       private function setDB2Option($name, $const, $type) {
+               if (defined($const)) {
+                       if ($type & self::CONN_OPTION) $this->mConnOptions[$name] = constant($const);
+                       if ($type & self::STMT_OPTION) $this->mStmtOptions[$name] = constant($const);
+               }
+               else {
+                       $this->installPrint("$const is not defined. ibm_db2 version is likely too low.");
+               }
+       }
+       
+       /**
+        * Outputs debug information in the appropriate place
+        * @param string $string The relevant debug message
+        */
+       private function installPrint($string) {
+               wfDebug("$string");
+               if ($this->mMode == self::INSTALL_MODE) {
+                       print "<li>$string</li>";
+                       flush();
+               } 
+       }
+       
        /**
         * Opens a database connection and returns it
         * Closes any existing connection
@@ -437,7 +490,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
                // Test for IBM DB2 support, to avoid suppressed fatal error
                if ( !function_exists( 'db2_connect' ) ) {
                        $error = "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?\n";
-                       wfDebug($error);
+                       $this->installPrint($error);
                        $this->reportConnectionError($error);
                }
 
@@ -461,16 +514,16 @@ class DatabaseIbm_db2 extends DatabaseBase {
                elseif ( $cataloged == self::UNCATALOGED ) {
                        $this->openUncataloged($dbName, $user, $password, $server, $port);
                }
-               // Don't do this
+               // Apply connection config
+               db2_set_option($this->mConn, $this->mConnOptions, 1);
                // Not all MediaWiki code is transactional
-               // Rather, turn it off in the begin function and turn on after a commit
-               // db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
+               // Rather, turn autocommit off in the begin function and turn on after a commit
                db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
 
                if ( $this->mConn == false ) {
-                       wfDebug( "DB connection error\n" );
-                       wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
-                       wfDebug( $this->lastError()."\n" );
+                       $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;
                }
 
@@ -543,20 +596,16 @@ class DatabaseIbm_db2 extends DatabaseBase {
         * Forces a database rollback
         */
        public function lastError() {
-               if ($this->lastError2()) {
-                       $this->rollback();
-                       return true;
-               }
-               return false;
-       }
-       
-       private function lastError2() {
                $connerr = db2_conn_errormsg();
-               if ($connerr) return $connerr;
+               if ($connerr) {
+                       //$this->rollback();
+                       return $connerr;
+               }
                $stmterr = db2_stmt_errormsg();
-               if ($stmterr) return $stmterr;
-               if ($this->mConn) return "No open connection.";
-               if ($this->mOpened) return "No open connection allegedly.";
+               if ($stmterr) {
+                       //$this->rollback();
+                       return $stmterr;
+               }
                
                return false;
        }
@@ -592,7 +641,7 @@ class DatabaseIbm_db2 extends DatabaseBase {
                // Switch into the correct namespace
                $this->applySchema();
                
-               $ret = db2_exec( $this->mConn, $sql );
+               $ret = db2_exec( $this->mConn, $sql, $this->mStmtOptions );
                if( !$ret ) {
                        print "<br><pre>";
                        print $sql;
@@ -714,7 +763,14 @@ EOF;
                        
                        // TODO: populate interwiki links
                        
-                       $this->commit();
+                       if ($this->lastError()) {
+                               print "<li>Errors encountered during table creation -- rolled back</li>\n";
+                               print "<li>Please install again</li>\n";
+                               $this->rollback();
+                       }
+                       else {                  
+                               $this->commit();
+                       }
                }
                catch (MWException $mwe)
                {
@@ -729,7 +785,7 @@ EOF;
         * @return escaped string
         */
        public function addQuotes( $s ) {
-               //wfDebug("DB2::addQuotes($s)\n");
+               //$this->installPrint("DB2::addQuotes($s)\n");
                if ( is_null( $s ) ) {
                        return "NULL";
                } else if ($s instanceof Blob) {
@@ -744,38 +800,6 @@ EOF;
                }
        }
        
-       /**
-        * Escapes strings
-        * Only escapes numbers going into non-numeric fields
-        * @param string s string to escape
-        * @return escaped string
-        */
-       public function addQuotesSmart( $table, $field, $s ) {
-               if ( is_null( $s ) ) {
-                       return "NULL";
-               } else if ($s instanceof Blob) {
-                       return "'".$s->fetch($s)."'";
-               }
-               $s = $this->strencode($s);
-               if ( is_numeric($s) ) {
-                       // Check with the database if the column is actually numeric
-                       // This allows for numbers in titles, etc
-                       $res = $this->doQuery("SELECT $field FROM $table FETCH FIRST 1 ROWS ONLY");
-                       $type = db2_field_type($res, strtoupper($field));
-                       if ( $this->is_numeric_type( $type ) ) {
-                               //wfDebug("DB2: Numeric value going in a numeric column: $s in $type $field in $table\n");
-                               return $s;
-                       }
-                       else {
-                               wfDebug("DB2: Numeric in non-numeric: '$s' in $type $field in $table\n");
-                               return "'$s'";
-                       }
-               }
-               else {
-                       return "'$s'";
-               }
-       }
-       
        /**
         * Verifies that a DB2 column/field type is numeric
         * @return bool true if numeric
@@ -830,7 +854,7 @@ EOF;
        /**
         * Start a transaction (mandatory)
         */
-       public function begin() {
+       public function begin( $fname = 'DatabaseIbm_db2::begin' ) {
                // turn off auto-commit
                db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
                $this->mTrxLevel = 1;
@@ -840,7 +864,7 @@ EOF;
         * End a transaction
         * Must have a preceding begin()
         */
-       public function commit() {
+       public function commit( $fname = 'DatabaseIbm_db2::commit' ) {
                db2_commit($this->mConn);
                // turn auto-commit back on
                db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
@@ -850,7 +874,7 @@ EOF;
        /**
         * Cancel a transaction
         */
-       public function rollback() {
+       public function rollback( $fname = 'DatabaseIbm_db2::rollback' ) {
                db2_rollback($this->mConn);
                // turn auto-commit back on
                // not sure if this is appropriate
@@ -868,7 +892,7 @@ EOF;
         *        LIST_NAMES         - comma separated field names
         */
        public function makeList( $a, $mode = LIST_COMMA ) {
-               wfDebug("DB2::makeList()\n");
+               $this->installPrint("DB2::makeList()\n");
                if ( !is_array( $a ) ) {
                        throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
                }
@@ -930,76 +954,6 @@ EOF;
                return $list;
        }
        
-       /**
-        * Makes an encoded list of strings from an array
-        * Quotes numeric values being inserted into non-numeric fields
-        * @return string
-        * @param string $table name of the table
-        * @param array $a list of values
-        * @param $mode:
-        *        LIST_COMMA         - comma separated, no field names
-        *        LIST_AND           - ANDed WHERE clause (without the WHERE)
-        *        LIST_OR            - ORed WHERE clause (without the WHERE)
-        *        LIST_SET           - comma separated with field names, like a SET clause
-        *        LIST_NAMES         - comma separated field names
-        */
-       public function makeListSmart( $table, $a, $mode = LIST_COMMA ) {
-               if ( !is_array( $a ) ) {
-                       throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
-               }
-
-               $first = true;
-               $list = '';
-               foreach ( $a as $field => $value ) {
-                       if ( !$first ) {
-                               if ( $mode == LIST_AND ) {
-                                       $list .= ' AND ';
-                               } elseif($mode == LIST_OR) {
-                                       $list .= ' OR ';
-                               } else {
-                                       $list .= ',';
-                               }
-                       } else {
-                               $first = false;
-                       }
-                       if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
-                               $list .= "($value)";
-                       } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
-                               $list .= "$value";
-                       } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
-                               if( count( $value ) == 0 ) {
-                                       throw new MWException( __METHOD__.': empty input' );
-                               } elseif( count( $value ) == 1 ) {
-                                       // Special-case single values, as IN isn't terribly efficient
-                                       // Don't necessarily assume the single key is 0; we don't
-                                       // enforce linear numeric ordering on other arrays here.
-                                       $value = array_values( $value );
-                                       $list .= $field." = ".$this->addQuotes( $value[0] );
-                               } else {
-                                       $list .= $field." IN (".$this->makeList($value).") ";
-                               }
-                       } elseif( is_null($value) ) {
-                               if ( $mode == LIST_AND || $mode == LIST_OR ) {
-                                       $list .= "$field IS ";
-                               } elseif ( $mode == LIST_SET ) {
-                                       $list .= "$field = ";
-                               }
-                               $list .= 'NULL';
-                       } else {
-                               if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
-                                       $list .= "$field = ";
-                               }
-                               if ( $mode == LIST_NAMES ) {
-                                       $list .= $value;
-                               }
-                               else {
-                                       $list .= $this->addQuotesSmart( $table, $field, $value );
-                               }
-                       }
-               }
-               return $list;
-       }
-       
        /**
         * Construct a LIMIT query with optional offset
         * This is used for query pages
@@ -1012,7 +966,7 @@ EOF;
                        throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
                }
                if( $offset ) {
-                       wfDebug("Offset parameter not supported in limitResult()\n");
+                       $this->installPrint("Offset parameter not supported in limitResult()\n");
                }
                // TODO implement proper offset handling
                // idea: get all the rows between 0 and offset, advance cursor to offset
@@ -1026,14 +980,16 @@ EOF;
         */
        public function tableName( $name ) {
                # Replace reserved words with better ones
-               switch( $name ) {
-                       case 'user':
-                               return 'mwuser';
-                       case 'text':
-                               return 'pagecontent';
-                       default:
-                               return $name;
-               }
+//             switch( $name ) {
+//                     case 'user':
+//                             return 'mwuser';
+//                     case 'text':
+//                             return 'pagecontent';
+//                     default:
+//                             return $name;
+//             }
+               // we want maximum compatibility with MySQL schema
+               return $name;
        }
        
        /**
@@ -1052,12 +1008,17 @@ EOF;
         * @return next value in that sequence
         */
        public function nextSequenceValue( $seqName ) {
+               // Not using sequences in the primary schema to allow for easy third-party migration scripts
+               // Emulating MySQL behaviour of using NULL to signal that sequences aren't used
+               /*
                $safeseq = preg_replace( "/'/", "''", $seqName );
                $res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
                $row = $this->fetchRow( $res );
                $this->mInsertId = $row[0];
                $this->freeResult( $res );
                return $this->mInsertId;
+               */
+               return NULL;
        }
        
        /**
@@ -1082,28 +1043,27 @@ EOF;
         * @return bool Success of insert operation. IGNORE always returns true.
         */
        public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) {
-               wfDebug("DB2::insert($table)\n");
+               $this->installPrint("DB2::insert($table)\n");
                if ( !count( $args ) ) {
                        return true;
                }
-
+               // get database-specific table name (not used)
                $table = $this->tableName( $table );
-
-               if ( !is_array( $options ) )
-                       $options = array( $options );
-
-               if ( isset( $args[0] ) && is_array( $args[0] ) ) {
-               }
-               else {
+               // format options as an array
+               if ( !is_array( $options ) ) $options = array( $options );
+               // format args as an array of arrays
+               if ( !( isset( $args[0] ) && is_array( $args[0] ) ) ) {
                        $args = array($args);
                }
+               // prevent insertion of NULL into primary key columns
+               $args = $this->removeNullPrimaryKeys($table, $args);
+               
+               // get column names
                $keys = array_keys( $args[0] );
+               $key_count = count($keys);
 
                // If IGNORE is set, we use savepoints to emulate mysql's behavior
                $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
-               
-               // Cache autocommit value at the start
-               $oldautocommit = db2_autocommit($this->mConn);
 
                // If we are not in a transaction, we need to be for savepoint trickery
                $didbegin = 0;
@@ -1119,58 +1079,50 @@ EOF;
                }
 
                $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
+               switch($key_count) {
+                       //case 0 impossible
+                       case 1:
+                               $sql .= '(?)';
+                               break;
+                       default:
+                               $sql .= '(?' . str_repeat(',?', $key_count-1) . ')';
+               }
+               $stmt = $this->prepare($sql);
 
                if ( !$ignore ) {
                        $first = true;
                        foreach ( $args as $row ) {
-                               if ( $first ) {
-                                       $first = false;
-                               } else {
-                                       $sql .= ',';
-                               }
-                               $sql .= '(' . $this->makeListSmart( $table, $row ) . ')';
+                               // insert each row into the database
+                               $this->execute($stmt, $row);
                        }
-                       $res = (bool)$this->query( $sql, $fname, $ignore );
                }
                else {
+                       // we must have autocommit turned off -- transaction mode on
+                       $this->begin();
+                       
                        $res = true;
-                       $origsql = $sql;
                        foreach ( $args as $row ) {
-                               $tempsql = $origsql;
-                               $tempsql .= '(' . $this->makeListSmart( $table, $row ) . ')';
-
                                if ( $ignore ) {
-                                       db2_exec($this->mConn, "SAVEPOINT $ignore");
+                                       $overhead = "SAVEPOINT $ignore ON ROLLBACK RETAIN CURSORS";
+                                       db2_exec($this->mConn, $overhead, $this->mStmtOptions);
                                }
-
-                               $tempres = (bool)$this->query( $tempsql, $fname, $ignore );
-
+                               
+                               $this->execute($sql, $row);
                                if ( $ignore ) {
-                                       $bar = db2_stmt_error();
-                                       if ($bar != false) {
-                                               db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore" );
+                                       $bar = $this->lastError();
+                                       if (!$bar) {
+                                               db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore", $this->mStmtOptions );
                                        }
                                        else {
-                                               db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore" );
+                                               db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore", $this->mStmtOptions );
                                                $numrowsinserted++;
                                        }
                                }
-
-                               // If any of them fail, we fail overall for this function call
-                               // Note that this will be ignored if IGNORE is set
-                               if (! $tempres)
-                                       $res = false;
                        }
                }
 
-               if ($didbegin) {
-                       $this->commit();
-               }
-               // if autocommit used to be on, it's ok to commit everything
-               else if ($oldautocommit)
-               {
-                       $this->commit();
-               }
+               // commit either way
+               $this->commit();
                
                if ( $ignore ) {
                        $olde = error_reporting( $olde );
@@ -1184,6 +1136,35 @@ EOF;
                return $res;
        }
        
+       /**
+        * Given a table name and a hash of columns with values
+        * Removes primary key columns from the hash where the value is NULL
+        * 
+        * @param string $table Name of the table
+        * @param array $args Array of hashes of column names with values
+        * @return array Filtered array of hashes
+        */
+       private function removeNullPrimaryKeys($table, $args) {
+               $schema = $this->mSchema;
+               // find out the primary keys
+               $keyres = db2_primary_keys($this->mConn, null, strtoupper($schema), strtoupper($table));
+               $keys = array();
+               for ($row = $this->fetchObject($keyres); $row != null; $row = $this->fetchRow($keyres)) {
+                       $keys[] = strtolower($row->column_name);
+               }
+               // remove primary keys
+               foreach ($args as $ai => $row) {
+                       foreach ($keys as $ki => $key) {
+                               if ($row[$key] == NULL) {
+                                       unset($row[$key]);
+                               }
+                       }
+                       $args[$ai] = $row;
+               }
+               // return modified hash
+               return $args;
+       }
+       
        /**
         * UPDATE wrapper, takes a condition array and a SET array
         *
@@ -1196,12 +1177,12 @@ EOF;
         *                        more of IGNORE, LOW_PRIORITY
         * @return bool
         */
-       function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
+       public function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
                $table = $this->tableName( $table );
                $opts = $this->makeUpdateOptions( $options );
-               $sql = "UPDATE $opts $table SET " . $this->makeListSmart( $table, $values, LIST_SET );
+               $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
                if ( $conds != '*' ) {
-                       $sql .= " WHERE " . $this->makeListSmart( $table, $conds, LIST_AND );
+                       $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
                }
                return $this->query( $sql, $fname );
        }
@@ -1211,14 +1192,14 @@ EOF;
         *
         * Use $conds == "*" to delete all rows
         */
-       function delete( $table, $conds, $fname = 'Database::delete' ) {
+       public function delete( $table, $conds, $fname = 'Database::delete' ) {
                if ( !$conds ) {
                        throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
                }
                $table = $this->tableName( $table );
                $sql = "DELETE FROM $table";
                if ( $conds != '*' ) {
-                       $sql .= ' WHERE ' . $this->makeListSmart( $table, $conds, LIST_AND );
+                       $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
                }
                return $this->query( $sql, $fname );
        }
@@ -1297,7 +1278,7 @@ EOF;
        /**
         * Returns the number of rows in the result set
         * Has to be called right after the corresponding select query
-        * @param Object $res result set
+        * @param object $res result set
         * @return int number of rows
         */
        public function numRows( $res ) {
@@ -1314,7 +1295,7 @@ EOF;
        
        /**
         * Moves the row pointer of the result set
-        * @param Object $res result set
+        * @param object $res result set
         * @param int $row row number
         * @return success or failure
         */
@@ -1331,7 +1312,7 @@ EOF;
        
        /**
         * Frees memory associated with a statement resource
-        * @param Object $res Statement resource to free
+        * @param object $res Statement resource to free
         * @return bool success or failure
         */
        public function freeResult( $res ) {
@@ -1345,7 +1326,7 @@ EOF;
        
        /**
         * Returns the number of columns in a resource
-        * @param Object $res Statement resource
+        * @param object $res Statement resource
         * @return Number of fields/columns in resource
         */
        public function numFields( $res ) {
@@ -1357,7 +1338,7 @@ EOF;
        
        /**
         * Returns the nth column name
-        * @param Object $res Statement resource
+        * @param object $res Statement resource
         * @param int $n Index of field or column
         * @return string name of nth column
         */
@@ -1410,7 +1391,7 @@ EOF;
                $obj = $this->fetchObject($res2);
                $this->mNumRows = $obj->num_rows;
                
-               wfDebug("DatabaseIbm_db2::select: There are $this->mNumRows rows.\n");
+               $this->installPrint("DatabaseIbm_db2::select: There are $this->mNumRows rows.\n");
                
                return $res;
        }
@@ -1487,7 +1468,7 @@ EOF;
                        case '40001':   // sql0911n, Deadlock or timeout, rollback
                        case '57011':   // sql0904n, Resource unavailable, no rollback
                        case '57033':   // sql0913n, Deadlock or timeout, no rollback
-                       wfDebug("In a deadlock because of SQLSTATE $err");
+                       $this->installPrint("In a deadlock because of SQLSTATE $err");
                        return true;
                }
                return false;
@@ -1521,7 +1502,7 @@ EOF;
         * @return string ''
         * @deprecated
         */
-       public function getStatus( $which ) { wfDebug('Not implemented for DB2: getStatus()'); return ''; }
+       public function getStatus( $which="%" ) { $this->installPrint('Not implemented for DB2: getStatus()'); return ''; }
        /**
         * Not implemented
         * TODO
@@ -1531,18 +1512,24 @@ EOF;
         * Not implemented
         * @deprecated
         */
-       public function setFakeSlaveLag( $lag ) { wfDebug('Not implemented for DB2: setFakeSlaveLag()'); }
+       public function setFakeSlaveLag( $lag ) { $this->installPrint('Not implemented for DB2: setFakeSlaveLag()'); }
        /**
         * Not implemented
         * @deprecated
         */
-       public function setFakeMaster( $enabled ) { wfDebug('Not implemented for DB2: setFakeMaster()'); }
+       public function setFakeMaster( $enabled = true ) { $this->installPrint('Not implemented for DB2: setFakeMaster()'); }
        /**
         * Not implemented
         * @return string $sql
         * @deprecated
         */ 
-       public function limitResultForUpdate($sql, $num) { return $sql; }
+       public function limitResultForUpdate($sql, $num) { $this->installPrint('Not implemented for DB2: limitResultForUpdate()'); return $sql; }
+       
+       /**
+        * Only useful with fake prepare like in base Database class
+        * @return      string
+        */
+       public function fillPreparedArg( $matches ) { $this->installPrint('Not useful for DB2: fillPreparedArg()'); return ''; }
        
        ######################################
        # Reflection
@@ -1608,7 +1595,7 @@ SQL;
        
        /**
         * db2_field_type() wrapper
-        * @param Object $res Result of executed statement
+        * @param object $res Result of executed statement
         * @param mixed $index number or name of the column
         * @return string column type
         */
@@ -1750,4 +1737,127 @@ SQL;
                // TODO
                // see SpecialAncientpages
        }
+       
+       ######################################
+       # Prepared statements
+       ######################################
+       
+       /**
+        * Intended to be compatible with the PEAR::DB wrapper functions.
+        * http://pear.php.net/manual/en/package.database.db.intro-execute.php
+        *
+        * ? = scalar value, quoted as necessary
+        * ! = raw SQL bit (a function for instance)
+        * & = filename; reads the file and inserts as a blob
+        *     (we don't use this though...)
+        * @param string $sql SQL statement with appropriate markers
+        * @return resource a prepared DB2 SQL statement
+        */
+       public function prepare( $sql, $func = 'DB2::prepare' ) {
+               $stmt = db2_prepare($this->mConn, $sql, $this->mStmtOptions);
+               return $stmt;
+       }
+
+       /**
+        * Frees resources associated with a prepared statement
+        * @return bool success or failure
+        */
+       public function freePrepared( $prepared ) {
+               return db2_free_stmt($prepared);
+       }
+
+       /**
+        * Execute a prepared query with the various arguments
+        * @param       string          $prepared       the prepared sql
+        * @param       mixed           $args           Either an array here, or put scalars as varargs
+        * @return      resource                                Results object
+        */
+       public function execute( $prepared, $args = null ) {
+               if( !is_array( $args ) ) {
+                       # Pull the var args
+                       $args = func_get_args();
+                       array_shift( $args );
+               }
+               $res = db2_execute($prepared, $args);
+               return $res;
+       }
+
+       /**
+        * Prepare & execute an SQL statement, quoting and inserting arguments
+        * in the appropriate places.
+        * @param $query String
+        * @param $args ...
+        */
+       public function safeQuery( $query, $args = null ) {
+               // copied verbatim from Database.php
+               $prepared = $this->prepare( $query, 'DB2::safeQuery' );
+               if( !is_array( $args ) ) {
+                       # Pull the var args
+                       $args = func_get_args();
+                       array_shift( $args );
+               }
+               $retval = $this->execute( $prepared, $args );
+               $this->freePrepared( $prepared );
+               return $retval;
+       }
+
+       /**
+        * For faking prepared SQL statements on DBs that don't support
+        * it directly.
+        * @param       resource        $preparedQuery  String: a 'preparable' SQL statement
+        * @param       array           $args                   Array of arguments to fill it with
+        * @return      string                                          executable statement
+        */
+       public function fillPrepared( $preparedQuery, $args ) {
+               reset( $args );
+               $this->preparedArgs =& $args;
+               
+               foreach ($args as $i => $arg) {
+                       db2_bind_param($preparedQuery, $i+1, $args[$i]);
+               }
+               
+               return $preparedQuery;
+       }
+       
+       /**
+        * Switches module between regular and install modes
+        */
+       public function setMode($mode) {
+               $old =  $this->mMode;
+               $this->mMode = $mode;
+               return $old;
+       }
+       
+       /**
+        * Bitwise negation of a column or value in SQL
+        * Same as (~field) in C
+        * @param       string  $field
+        * @return      string
+        */
+       function bitNot($field) {
+               //expecting bit-fields smaller than 4bytes
+               return 'BITNOT('.$bitField.')';
+       }
+
+       /**
+        * Bitwise AND of two columns or values in SQL
+        * Same as (fieldLeft & fieldRight) in C
+        * @param       string  $fieldLeft
+        * @param       string  $fieldRight
+        * @return      string
+        */
+       function bitAnd($fieldLeft, $fieldRight) {
+               return 'BITAND('.$fieldLeft.', '.$fieldRight.')';
+       }
+
+       /**
+        * Bitwise OR of two columns or values in SQL
+        * Same as (fieldLeft | fieldRight) in C
+        * @param       string  $fieldLeft
+        * @param       string  $fieldRight
+        * @return      string
+        */
+       function bitOr($fieldLeft, $fieldRight) {
+               return 'BITOR('.$fieldLeft.', '.$fieldRight.')';
+       }
 }