summaryrefslogtreecommitdiff
path: root/includes/db/DatabaseIbm_db2.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/db/DatabaseIbm_db2.php')
-rw-r--r--includes/db/DatabaseIbm_db2.php908
1 files changed, 478 insertions, 430 deletions
diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php
index fcd0bc2d..9b62af82 100644
--- a/includes/db/DatabaseIbm_db2.php
+++ b/includes/db/DatabaseIbm_db2.php
@@ -9,37 +9,33 @@
*/
/**
- * Utility class for generating blank objects
- * Intended as an equivalent to {} in Javascript
- * @ingroup Database
- */
-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 string $table table name
- * @param string $field column name
+ * @param $db DatabaseIbm_db2: Database interface
+ * @param $table String: table name
+ * @param $field String: column name
* @return IBM_DB2Field
*/
static function fromText($db, $table, $field) {
global $wgDBmwschema;
- $q = <<<END
+ $q = <<<SQL
SELECT
lcase(coltype) AS typname,
nulls AS attnotnull, length AS attlen
FROM sysibm.syscolumns
WHERE tbcreator=%s AND tbname=%s AND name=%s;
-END;
+SQL;
$res = $db->query(sprintf($q,
$db->addQuotes($wgDBmwschema),
$db->addQuotes($table),
@@ -89,20 +85,25 @@ END;
class IBM_DB2Blob {
private $mData;
- function __construct($data) {
+ public function __construct($data) {
$this->mData = $data;
}
- function getData() {
+ public function getData() {
return $this->mData;
}
+
+ public function __toString()
+ {
+ return $this->mData;
+ }
}
/**
* Primary database interface
* @ingroup Database
*/
-class DatabaseIbm_db2 extends Database {
+class DatabaseIbm_db2 extends DatabaseBase {
/*
* Inherited members
protected $mLastQuery = '';
@@ -122,27 +123,42 @@ class DatabaseIbm_db2 extends Database {
*/
/// Server port for uncataloged connections
- protected $mPort = NULL;
+ protected $mPort = null;
/// Whether connection is cataloged
- protected $mCataloged = NULL;
+ protected $mCataloged = null;
/// Schema for tables, stored procedures, triggers
- protected $mSchema = NULL;
+ protected $mSchema = null;
/// Whether the schema has been applied in this session
protected $mSchemaSet = false;
/// Result of last query
- protected $mLastResult = NULL;
+ protected $mLastResult = null;
/// Number of rows affected by last INSERT/UPDATE/DELETE
- protected $mAffectedRows = NULL;
+ protected $mAffectedRows = null;
/// Number of rows returned by last SELECT
- protected $mNumRows = NULL;
+ 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;
+ protected $mInsertId = null;
/*
* These can be safely inherited
@@ -219,7 +235,7 @@ class DatabaseIbm_db2 extends Database {
*/
/*
- * These need to be implemented TODO
+ * These have been implemented
*
* Administrative: 7 / 7
* constructor [Done]
@@ -375,7 +391,10 @@ class DatabaseIbm_db2 extends Database {
return $this->mDBname;
}
}
-
+
+ function getType() {
+ return 'ibm_db2';
+ }
######################################
# Setup
@@ -384,12 +403,13 @@ class DatabaseIbm_db2 extends Database {
/**
*
- * @param string $server hostname of database server
- * @param string $user username
- * @param string $password
- * @param string $dbName database name on the server
- * @param function $failFunction (optional)
- * @param integer $flags database behaviour flags (optional, unused)
+ * @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, $password = false,
$dbName = false, $failFunction = false, $flags = 0,
@@ -399,7 +419,7 @@ class DatabaseIbm_db2 extends Database {
global $wgOut, $wgDBmwschema;
# Can't get a reference if it hasn't been set yet
if ( !isset( $wgOut ) ) {
- $wgOut = NULL;
+ $wgOut = null;
}
$this->mOut =& $wgOut;
$this->mFailFunction = $failFunction;
@@ -412,17 +432,50 @@ class DatabaseIbm_db2 extends Database {
$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 $name String: name of the option in the options array
+ * @param $const String: name of the constant holding the right option value
+ * @param $type Integer: 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
* @return a fresh connection
- * @param string $server hostname
- * @param string $user
- * @param string $password
- * @param string $dbName database name
+ * @param $server String: hostname
+ * @param $user String
+ * @param $password String
+ * @param $dbName String: database name
*/
public function open( $server, $user, $password, $dbName )
{
@@ -437,7 +490,7 @@ class DatabaseIbm_db2 extends Database {
// 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 Database {
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;
}
@@ -524,14 +577,14 @@ class DatabaseIbm_db2 extends Database {
/**
* Returns a fresh instance of this class
- * @static
- * @return
- * @param string $server hostname of database server
- * @param string $user username
- * @param string $password
- * @param string $dbName database name on the server
- * @param function $failFunction (optional)
- * @param integer $flags database behaviour flags (optional, unused)
+ *
+ * @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)
{
@@ -543,20 +596,16 @@ class DatabaseIbm_db2 extends Database {
* 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 Database {
// 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;
@@ -601,7 +650,7 @@ class DatabaseIbm_db2 extends Database {
throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( $error ) );
}
$this->mLastResult = $ret;
- $this->mAffectedRows = NULL; // Not calculated until asked for
+ $this->mAffectedRows = null; // Not calculated until asked for
return $ret;
}
@@ -653,17 +702,6 @@ EOF;
if( $this->lastErrno() ) {
throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
}
- // Make field names lowercase for compatibility with MySQL
- if ($row)
- {
- $row2 = new BlankObject();
- foreach ($row as $key => $value)
- {
- $keyu = strtolower($key);
- $row2->$keyu = $value;
- }
- $row = $row2;
- }
return $row;
}
@@ -707,14 +745,26 @@ EOF;
$this->applySchema();
$this->begin();
- $res = dbsource( "../maintenance/ibm_db2/tables.sql", $this);
+ $res = $this->sourceFile( "../maintenance/ibm_db2/tables.sql" );
+ if ($res !== true) {
+ print " <b>FAILED</b>: " . htmlspecialchars( $res ) . "</li>";
+ } else {
+ print " done</li>";
+ }
$res = null;
// TODO: update mediawiki_version table
// 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)
{
@@ -725,15 +775,17 @@ EOF;
/**
* Escapes strings
* Doesn't escape numbers
- * @param string s string to escape
+ * @param $s String: string to escape
* @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) {
return "'".$s->fetch($s)."'";
+ } else if ($s instanceof IBM_DB2Blob) {
+ return "'".$this->decodeBlob($s)."'";
}
$s = $this->strencode($s);
if ( is_numeric($s) ) {
@@ -745,41 +797,9 @@ 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
- * @param string $type DB2 column type
+ * @param $type String: DB2 column type
*/
public function is_numeric_type( $type ) {
switch (strtoupper($type)) {
@@ -798,7 +818,7 @@ EOF;
/**
* Alias for addQuotes()
- * @param string s string to escape
+ * @param $s String: string to escape
* @return escaped string
*/
public function strencode( $s ) {
@@ -830,7 +850,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 +860,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 +870,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 +888,6 @@ EOF;
* LIST_NAMES - comma separated field names
*/
public function makeList( $a, $mode = LIST_COMMA ) {
- wfDebug("DB2::makeList()\n");
if ( !is_array( $a ) ) {
throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
}
@@ -931,88 +950,18 @@ EOF;
}
/**
- * 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
- * $sql string SQL query we will append the limit too
- * $limit integer the SQL limit
- * $offset integer the SQL offset (default false)
+ * @param $sql string SQL query we will append the limit too
+ * @param $limit integer the SQL limit
+ * @param $offset integer the SQL offset (default false)
*/
public function limitResult($sql, $limit, $offset=false) {
if( !is_numeric($limit) ) {
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,20 +975,22 @@ 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;
}
/**
* Generates a timestamp in an insertable format
* @return string timestamp value
- * @param timestamp $ts
+ * @param $ts timestamp
*/
public function timestamp( $ts=0 ) {
// TS_MW cannot be easily distinguished from an integer
@@ -1048,16 +999,21 @@ EOF;
/**
* Return the next in a sequence, save the value for retrieval via insertId()
- * @param string seqName Name of a defined sequence in the database
+ * @param $seqName String: name of a defined sequence in the database
* @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;
}
/**
@@ -1069,139 +1025,180 @@ EOF;
}
/**
+ * Updates the mInsertId property with the value of the last insert into a generated column
+ * @param $table String: sanitized table name
+ * @param $primaryKey Mixed: string name of the primary key or a bool if this call is a do-nothing
+ * @param $stmt Resource: prepared statement resource
+ * of the SELECT primary_key FROM FINAL TABLE ( INSERT ... ) form
+ */
+ private function calcInsertId($table, $primaryKey, $stmt) {
+ if ($primaryKey) {
+ $id_row = $this->fetchRow($stmt);
+ $this->mInsertId = $id_row[0];
+ }
+ }
+
+ /**
* INSERT wrapper, inserts an array into a table
*
* $args may be a single associative array, or an array of these with numeric keys,
* for multi-row insert
*
- * @param array $table String: Name of the table to insert to.
- * @param array $args Array: Items to insert into the table.
- * @param array $fname String: Name of the function, for profiling
- * @param mixed $options String or Array. Valid options: IGNORE
+ * @param $table String: Name of the table to insert to.
+ * @param $args Array: Items to insert into the table.
+ * @param $fname String: Name of the function, for profiling
+ * @param $options String or Array. Valid options: IGNORE
*
* @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");
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
+ list($args, $primaryKeys) = $this->removeNullPrimaryKeys($table, $args);
+ // if there's only one primary key
+ // we'll be able to read its value after insertion
+ $primaryKey = false;
+ if (count($primaryKeys) == 1) {
+ $primaryKey = $primaryKeys[0];
+ }
+
+ // 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);
+ // assume success
+ $res = true;
// If we are not in a transaction, we need to be for savepoint trickery
$didbegin = 0;
if (! $this->mTrxLevel) {
$this->begin();
$didbegin = 1;
}
- if ( $ignore ) {
- $olde = error_reporting( 0 );
- // For future use, we may want to track the number of actual inserts
- // Right now, insert (all writes) simply return true/false
- $numrowsinserted = 0;
- }
$sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
+ switch($key_count) {
+ //case 0 impossible
+ case 1:
+ $sql .= '(?)';
+ break;
+ default:
+ $sql .= '(?' . str_repeat(',?', $key_count-1) . ')';
+ }
+ // add logic to read back the new primary key value
+ if ($primaryKey) {
+ $sql = "SELECT $primaryKey FROM FINAL TABLE($sql)";
+ }
+ $stmt = $this->prepare($sql);
+
+ // start a transaction/enter transaction mode
+ $this->begin();
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
+ $res = $res & $this->execute($stmt, $row);
+ // get the last inserted value into a generated column
+ $this->calcInsertId($table, $primaryKey, $stmt);
}
- $res = (bool)$this->query( $sql, $fname, $ignore );
}
else {
+ $olde = error_reporting( 0 );
+ // For future use, we may want to track the number of actual inserts
+ // Right now, insert (all writes) simply return true/false
+ $numrowsinserted = 0;
+
+ // always return true
$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);
+
+ $res2 = $this->execute($stmt, $row);
+ // get the last inserted value into a generated column
+ $this->calcInsertId($table, $primaryKey, $stmt);
+
+ $errNum = $this->lastErrno();
+ if ($errNum) {
+ db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore", $this->mStmtOptions );
}
-
- $tempres = (bool)$this->query( $tempsql, $fname, $ignore );
-
- if ( $ignore ) {
- $bar = db2_stmt_error();
- if ($bar != false) {
- db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore" );
- }
- else {
- db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore" );
- $numrowsinserted++;
- }
+ else {
+ 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();
- }
-
- if ( $ignore ) {
+
$olde = error_reporting( $olde );
// Set the affected row count for the whole operation
$this->mAffectedRows = $numrowsinserted;
-
- // IGNORE always returns true
- return true;
}
+ // commit either way
+ $this->commit();
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 $table String: name of the table
+ * @param $args Array of hashes of column names with values
+ * @return Array: tuple containing filtered array of columns, array of primary keys
+ */
+ 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 array($args, $keys);
+ }
+
+ /**
* UPDATE wrapper, takes a condition array and a SET array
*
- * @param string $table The table to UPDATE
- * @param array $values An array of values to SET
- * @param array $conds An array of conditions (WHERE). Use '*' to update all rows.
- * @param string $fname The Class::Function calling this function
- * (for the log)
- * @param array $options An array of UPDATE options, can be one or
- * more of IGNORE, LOW_PRIORITY
- * @return bool
- */
- function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
+ * @param $table String: The table to UPDATE
+ * @param $values An array of values to SET
+ * @param $conds An array of conditions (WHERE). Use '*' to update all rows.
+ * @param $fname String: The Class::Function calling this function
+ * (for the log)
+ * @param $options An array of UPDATE options, can be one or
+ * more of IGNORE, LOW_PRIORITY
+ * @return Boolean
+ */
+ 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,21 +1208,21 @@ 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 );
}
/**
* Returns the number of rows affected by the last query or 0
- * @return int the number of rows affected by the last query
+ * @return Integer: the number of rows affected by the last query
*/
public function affectedRows() {
if ( !is_null( $this->mAffectedRows ) ) {
@@ -1238,20 +1235,11 @@ EOF;
}
/**
- * USE INDEX clause
- * DB2 doesn't have them and returns ""
- * @param sting $index
- */
- public function useIndexClause( $index ) {
- return "";
- }
-
- /**
* Simulates REPLACE with a DELETE followed by INSERT
* @param $table Object
- * @param array $uniqueIndexes array consisting of indexes and arrays of indexes
- * @param array $rows Rows to insert
- * @param string $fname Name of the function for profiling
+ * @param $uniqueIndexes Array consisting of indexes and arrays of indexes
+ * @param $rows Array: rows to insert
+ * @param $fname String: name of the function for profiling
* @return nothing
*/
function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) {
@@ -1306,8 +1294,8 @@ 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
- * @return int number of rows
+ * @param $res Object result set
+ * @return Integer: number of rows
*/
public function numRows( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -1323,8 +1311,8 @@ EOF;
/**
* Moves the row pointer of the result set
- * @param Object $res result set
- * @param int $row row number
+ * @param $res Object: result set
+ * @param $row Integer: row number
* @return success or failure
*/
public function dataSeek( $res, $row ) {
@@ -1340,8 +1328,8 @@ EOF;
/**
* Frees memory associated with a statement resource
- * @param Object $res Statement resource to free
- * @return bool success or failure
+ * @param $res Object: statement resource to free
+ * @return Boolean success or failure
*/
public function freeResult( $res ) {
if ( $res instanceof ResultWrapper ) {
@@ -1354,7 +1342,7 @@ EOF;
/**
* Returns the number of columns in a resource
- * @param Object $res Statement resource
+ * @param $res Object: statement resource
* @return Number of fields/columns in resource
*/
public function numFields( $res ) {
@@ -1366,9 +1354,9 @@ EOF;
/**
* Returns the nth column name
- * @param Object $res Statement resource
- * @param int $n Index of field or column
- * @return string name of nth column
+ * @param $res Object: statement resource
+ * @param $n Integer: Index of field or column
+ * @return String name of nth column
*/
public function fieldName( $res, $n ) {
if ( $res instanceof ResultWrapper ) {
@@ -1380,15 +1368,15 @@ EOF;
/**
* SELECT wrapper
*
- * @param mixed $table Array or string, table name(s) (prefix auto-added)
- * @param mixed $vars Array or string, field name(s) to be retrieved
- * @param mixed $conds Array or string, condition(s) for WHERE
- * @param string $fname Calling function name (use __METHOD__) for logs/profiling
- * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
- * see Database::makeSelectOptions code for list of supported stuff
- * @param array $join_conds Associative array of table join conditions (optional)
- * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
- * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
+ * @param $table Array or string, table name(s) (prefix auto-added)
+ * @param $vars Array or string, field name(s) to be retrieved
+ * @param $conds Array or string, condition(s) for WHERE
+ * @param $fname String: calling function name (use __METHOD__) for logs/profiling
+ * @param $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
+ * see Database::makeSelectOptions code for list of supported stuff
+ * @param $join_conds Associative array of table join conditions (optional)
+ * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
+ * @return Mixed: database result resource (feed to Database::fetchObject or whatever), or false on failure
*/
public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
{
@@ -1419,7 +1407,6 @@ EOF;
$obj = $this->fetchObject($res2);
$this->mNumRows = $obj->num_rows;
- wfDebug("DatabaseIbm_db2::select: There are $this->mNumRows rows.\n");
return $res;
}
@@ -1430,9 +1417,9 @@ EOF;
*
* @private
*
- * @param array $options an associative array of options to be turned into
+ * @param $options Associative array of options to be turned into
* an SQL query, valid keys are listed in the function.
- * @return array
+ * @return Array
*/
function makeSelectOptions( $options ) {
$preLimitTail = $postLimitTail = '';
@@ -1463,46 +1450,18 @@ EOF;
}
/**
- * Does nothing
- * @param object $db
- * @return bool true
- */
- public function selectDB( $db ) {
- return true;
- }
-
- /**
- * Returns an SQL expression for a simple conditional.
- * Uses CASE on DB2
- *
- * @param string $cond SQL expression which will result in a boolean value
- * @param string $trueVal SQL expression to return if true
- * @param string $falseVal SQL expression to return if false
- * @return string SQL fragment
- */
- public function conditional( $cond, $trueVal, $falseVal ) {
- return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
- }
-
- ###
- # Fix search crash
- ###
- /**
* Get search engine class. All subclasses of this
* need to implement this if they wish to use searching.
*
- * @return string
+ * @return String
*/
public function getSearchEngine() {
return "SearchIBM_DB2";
}
-
- ###
- # Tuesday the 14th of October, 2008
- ###
+
/**
* Did the last database access fail because of deadlock?
- * @return bool
+ * @return Boolean
*/
public function wasDeadlock() {
// get SQLSTATE
@@ -1511,7 +1470,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;
@@ -1520,13 +1479,13 @@ EOF;
/**
* Ping the server and try to reconnect if it there is no connection
* The connection may be closed and reopened while this happens
- * @return bool whether the connection exists
+ * @return Boolean: whether the connection exists
*/
public function ping() {
// db2_ping() doesn't exist
// Emulate
$this->close();
- if ($this->mCataloged == NULL) {
+ if ($this->mCataloged == null) {
return false;
}
else if ($this->mCataloged) {
@@ -1545,46 +1504,34 @@ EOF;
* @return string ''
* @deprecated
*/
- public function getStatus( $which ) { wfDebug('Not implemented for DB2: getStatus()'); return ''; }
- /**
- * Not implemented
- * @deprecated
- */
- public function setTimeout( $timeout ) { wfDebug('Not implemented for DB2: setTimeout()'); }
+ public function getStatus( $which="%" ) { $this->installPrint('Not implemented for DB2: getStatus()'); return ''; }
/**
* Not implemented
* TODO
* @return bool true
*/
- public function lock( $lockName, $method ) { wfDebug('Not implemented for DB2: lock()'); return true; }
- /**
- * Not implemented
- * TODO
- * @return bool true
- */
- public function unlock( $lockName, $method ) { wfDebug('Not implemented for DB2: unlock()'); return true; }
/**
* 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; }
+
/**
- * No such option
- * @return string ''
- * @deprecated
+ * Only useful with fake prepare like in base Database class
+ * @return string
*/
- public function lowPriorityOption() { return ''; }
+ public function fillPreparedArg( $matches ) { $this->installPrint('Not useful for DB2: fillPreparedArg()'); return ''; }
######################################
# Reflection
@@ -1592,9 +1539,9 @@ EOF;
/**
* Query whether a given column exists in the mediawiki schema
- * @param string $table name of the table
- * @param string $field name of the column
- * @param string $fname function name for logging and profiling
+ * @param $table String: name of the table
+ * @param $field String: name of the column
+ * @param $fname String: function name for logging and profiling
*/
public function fieldExists( $table, $field, $fname = 'DatabaseIbm_db2::fieldExists' ) {
$table = $this->tableName( $table );
@@ -1617,10 +1564,10 @@ SQL;
/**
* Returns information about an index
* If errors are explicitly ignored, returns NULL on failure
- * @param string $table table name
- * @param string $index index name
- * @param string
- * @return object query row in object form
+ * @param $table String: table name
+ * @param $index String: index name
+ * @param $fname String: function name for logging and profiling
+ * @return Object query row in object form
*/
public function indexInfo( $table, $index, $fname = 'DatabaseIbm_db2::indexExists' ) {
$table = $this->tableName( $table );
@@ -1631,17 +1578,17 @@ WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
SQL;
$res = $this->query( $sql, $fname );
if ( !$res ) {
- return NULL;
+ return null;
}
$row = $this->fetchObject( $res );
- if ($row != NULL) return $row;
+ if ($row != null) return $row;
else return false;
}
/**
* Returns an information object on a table column
- * @param string $table table name
- * @param string $field column name
+ * @param $table String: table name
+ * @param $field String: column name
* @return IBM_DB2Field
*/
public function fieldInfo( $table, $field ) {
@@ -1650,9 +1597,9 @@ SQL;
/**
* db2_field_type() wrapper
- * @param Object $res Result of executed statement
- * @param mixed $index number or name of the column
- * @return string column type
+ * @param $res Object: result of executed statement
+ * @param $index Mixed: number or name of the column
+ * @return String column type
*/
public function fieldType( $res, $index ) {
if ( $res instanceof ResultWrapper ) {
@@ -1663,10 +1610,10 @@ SQL;
/**
* Verifies that an index was created as unique
- * @param string $table table name
- * @param string $index index name
- * @param string $fnam function name for profiling
- * @return bool
+ * @param $table String: table name
+ * @param $index String: index name
+ * @param $fname function name for profiling
+ * @return Bool
*/
public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
$table = $this->tableName( $table );
@@ -1689,9 +1636,9 @@ SQL;
/**
* Returns the size of a text field, or -1 for "unlimited"
- * @param string $table table name
- * @param string $field column name
- * @return int length or -1 for unlimited
+ * @param $table String: table name
+ * @param $field String: column name
+ * @return Integer: length or -1 for unlimited
*/
public function textFieldSize( $table, $field ) {
$table = $this->tableName( $table );
@@ -1709,12 +1656,12 @@ SQL;
/**
* DELETE where the condition is a join
- * @param string $delTable deleting from this table
- * @param string $joinTable using data from this table
- * @param string $delVar variable in deleteable table
- * @param string $joinVar variable in data table
- * @param array $conds conditionals for join table
- * @param string $fname function name for profiling
+ * @param $delTable String: deleting from this table
+ * @param $joinTable String: using data from this table
+ * @param $delVar String: variable in deleteable table
+ * @param $joinVar String: variable in data table
+ * @param $conds Array: conditionals for join table
+ * @param $fname String: function name for profiling
*/
public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) {
if ( !$conds ) {
@@ -1731,32 +1678,10 @@ SQL;
$this->query( $sql, $fname );
}
-
- /**
- * Estimate rows in dataset
- * Returns estimated count, based on COUNT(*) output
- * Takes same arguments as Database::select()
- * @param string $table table name
- * @param array $vars unused
- * @param array $conds filters on the table
- * @param string $fname function name for profiling
- * @param array $options options for select
- * @return int row count
- */
- public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
- $rows = 0;
- $res = $this->select ($table, 'COUNT(*) as mwrowcount', $conds, $fname, $options );
- if ($res) {
- $row = $this->fetchRow($res);
- $rows = (isset($row['mwrowcount'])) ? $row['mwrowcount'] : 0;
- }
- $this->freeResult($res);
- return $rows;
- }
-
+
/**
* Description is left as an exercise for the reader
- * @param mixed $b data to be encoded
+ * @param $b Mixed: data to be encoded
* @return IBM_DB2Blob
*/
public function encodeBlob($b) {
@@ -1765,7 +1690,7 @@ SQL;
/**
* Description is left as an exercise for the reader
- * @param IBM_DB2Blob $b data to be decoded
+ * @param $b IBM_DB2Blob: data to be decoded
* @return mixed
*/
public function decodeBlob($b) {
@@ -1774,8 +1699,8 @@ SQL;
/**
* Convert into a list of string being concatenated
- * @param array $stringList strings that need to be joined together by the SQL engine
- * @return string joined by the concatenation operator
+ * @param $stringList Array: strings that need to be joined together by the SQL engine
+ * @return String: joined by the concatenation operator
*/
public function buildConcat( $stringList ) {
// || is equivalent to CONCAT
@@ -1785,12 +1710,135 @@ SQL;
/**
* Generates the SQL required to convert a DB2 timestamp into a Unix epoch
- * @param string $column name of timestamp column
- * @return string SQL code
+ * @param $column String: name of timestamp column
+ * @return String: SQL code
*/
public function extractUnixEpoch( $column ) {
// 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 $sql String: SQL statement with appropriate markers
+ * @param $func String: Name of the function, for profiling
+ * @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 Boolean success or failure
+ */
+ public function freePrepared( $prepared ) {
+ return db2_free_stmt($prepared);
+ }
+
+ /**
+ * Execute a prepared query with the various arguments
+ * @param $prepared String: the prepared sql
+ * @param $args Mixed: 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 $preparedQuery String: a 'preparable' SQL statement
+ * @param $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 $field String
+ * @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 $fieldLeft String
+ * @param $fieldRight String
+ * @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 $fieldLeft String
+ * @param $fieldRight String
+ * @return String
+ */
+ function bitOr($fieldLeft, $fieldRight) {
+ return 'BITOR('.$fieldLeft.', '.$fieldRight.')';
+ }
}
-?> \ No newline at end of file