DatabaseIbm_db2.php 46 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796
  1. <?php
  2. /**
  3. * This script is the IBM DB2 database abstraction layer
  4. *
  5. * See maintenance/ibm_db2/README for development notes and other specific information
  6. * @ingroup Database
  7. * @file
  8. * @author leo.petr+mediawiki@gmail.com
  9. */
  10. /**
  11. * Utility class for generating blank objects
  12. * Intended as an equivalent to {} in Javascript
  13. * @ingroup Database
  14. */
  15. class BlankObject {
  16. }
  17. /**
  18. * This represents a column in a DB2 database
  19. * @ingroup Database
  20. */
  21. class IBM_DB2Field {
  22. private $name, $tablename, $type, $nullable, $max_length;
  23. /**
  24. * Builder method for the class
  25. * @param Object $db Database interface
  26. * @param string $table table name
  27. * @param string $field column name
  28. * @return IBM_DB2Field
  29. */
  30. static function fromText($db, $table, $field) {
  31. global $wgDBmwschema;
  32. $q = <<<END
  33. SELECT
  34. lcase(coltype) AS typname,
  35. nulls AS attnotnull, length AS attlen
  36. FROM sysibm.syscolumns
  37. WHERE tbcreator=%s AND tbname=%s AND name=%s;
  38. END;
  39. $res = $db->query(sprintf($q,
  40. $db->addQuotes($wgDBmwschema),
  41. $db->addQuotes($table),
  42. $db->addQuotes($field)));
  43. $row = $db->fetchObject($res);
  44. if (!$row)
  45. return null;
  46. $n = new IBM_DB2Field;
  47. $n->type = $row->typname;
  48. $n->nullable = ($row->attnotnull == 'N');
  49. $n->name = $field;
  50. $n->tablename = $table;
  51. $n->max_length = $row->attlen;
  52. return $n;
  53. }
  54. /**
  55. * Get column name
  56. * @return string column name
  57. */
  58. function name() { return $this->name; }
  59. /**
  60. * Get table name
  61. * @return string table name
  62. */
  63. function tableName() { return $this->tablename; }
  64. /**
  65. * Get column type
  66. * @return string column type
  67. */
  68. function type() { return $this->type; }
  69. /**
  70. * Can column be null?
  71. * @return bool true or false
  72. */
  73. function nullable() { return $this->nullable; }
  74. /**
  75. * How much can you fit in the column per row?
  76. * @return int length
  77. */
  78. function maxLength() { return $this->max_length; }
  79. }
  80. /**
  81. * Wrapper around binary large objects
  82. * @ingroup Database
  83. */
  84. class IBM_DB2Blob {
  85. private $mData;
  86. function __construct($data) {
  87. $this->mData = $data;
  88. }
  89. function getData() {
  90. return $this->mData;
  91. }
  92. }
  93. /**
  94. * Primary database interface
  95. * @ingroup Database
  96. */
  97. class DatabaseIbm_db2 extends Database {
  98. /*
  99. * Inherited members
  100. protected $mLastQuery = '';
  101. protected $mPHPError = false;
  102. protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
  103. protected $mOut, $mOpened = false;
  104. protected $mFailFunction;
  105. protected $mTablePrefix;
  106. protected $mFlags;
  107. protected $mTrxLevel = 0;
  108. protected $mErrorCount = 0;
  109. protected $mLBInfo = array();
  110. protected $mFakeSlaveLag = null, $mFakeMaster = false;
  111. *
  112. */
  113. /// Server port for uncataloged connections
  114. protected $mPort = NULL;
  115. /// Whether connection is cataloged
  116. protected $mCataloged = NULL;
  117. /// Schema for tables, stored procedures, triggers
  118. protected $mSchema = NULL;
  119. /// Whether the schema has been applied in this session
  120. protected $mSchemaSet = false;
  121. /// Result of last query
  122. protected $mLastResult = NULL;
  123. /// Number of rows affected by last INSERT/UPDATE/DELETE
  124. protected $mAffectedRows = NULL;
  125. /// Number of rows returned by last SELECT
  126. protected $mNumRows = NULL;
  127. const CATALOGED = "cataloged";
  128. const UNCATALOGED = "uncataloged";
  129. const USE_GLOBAL = "get from global";
  130. /// Last sequence value used for a primary key
  131. protected $mInsertId = NULL;
  132. /*
  133. * These can be safely inherited
  134. *
  135. * Getter/Setter: (18)
  136. * failFunction
  137. * setOutputPage
  138. * bufferResults
  139. * ignoreErrors
  140. * trxLevel
  141. * errorCount
  142. * getLBInfo
  143. * setLBInfo
  144. * lastQuery
  145. * isOpen
  146. * setFlag
  147. * clearFlag
  148. * getFlag
  149. * getProperty
  150. * getDBname
  151. * getServer
  152. * tableNameCallback
  153. * tablePrefix
  154. *
  155. * Administrative: (8)
  156. * debug
  157. * installErrorHandler
  158. * restoreErrorHandler
  159. * connectionErrorHandler
  160. * reportConnectionError
  161. * sourceFile
  162. * sourceStream
  163. * replaceVars
  164. *
  165. * Database: (5)
  166. * query
  167. * set
  168. * selectField
  169. * generalizeSQL
  170. * update
  171. * strreplace
  172. * deadlockLoop
  173. *
  174. * Prepared Statement: 6
  175. * prepare
  176. * freePrepared
  177. * execute
  178. * safeQuery
  179. * fillPrepared
  180. * fillPreparedArg
  181. *
  182. * Slave/Master: (4)
  183. * masterPosWait
  184. * getSlavePos
  185. * getMasterPos
  186. * getLag
  187. *
  188. * Generation: (9)
  189. * tableNames
  190. * tableNamesN
  191. * tableNamesWithUseIndexOrJOIN
  192. * escapeLike
  193. * delete
  194. * insertSelect
  195. * timestampOrNull
  196. * resultObject
  197. * aggregateValue
  198. * selectSQLText
  199. * selectRow
  200. * makeUpdateOptions
  201. *
  202. * Reflection: (1)
  203. * indexExists
  204. */
  205. /*
  206. * These need to be implemented TODO
  207. *
  208. * Administrative: 7 / 7
  209. * constructor [Done]
  210. * open [Done]
  211. * openCataloged [Done]
  212. * close [Done]
  213. * newFromParams [Done]
  214. * openUncataloged [Done]
  215. * setup_database [Done]
  216. *
  217. * Getter/Setter: 13 / 13
  218. * cascadingDeletes [Done]
  219. * cleanupTriggers [Done]
  220. * strictIPs [Done]
  221. * realTimestamps [Done]
  222. * impliciGroupby [Done]
  223. * implicitOrderby [Done]
  224. * searchableIPs [Done]
  225. * functionalIndexes [Done]
  226. * getWikiID [Done]
  227. * isOpen [Done]
  228. * getServerVersion [Done]
  229. * getSoftwareLink [Done]
  230. * getSearchEngine [Done]
  231. *
  232. * Database driver wrapper: 23 / 23
  233. * lastError [Done]
  234. * lastErrno [Done]
  235. * doQuery [Done]
  236. * tableExists [Done]
  237. * fetchObject [Done]
  238. * fetchRow [Done]
  239. * freeResult [Done]
  240. * numRows [Done]
  241. * numFields [Done]
  242. * fieldName [Done]
  243. * insertId [Done]
  244. * dataSeek [Done]
  245. * affectedRows [Done]
  246. * selectDB [Done]
  247. * strencode [Done]
  248. * conditional [Done]
  249. * wasDeadlock [Done]
  250. * ping [Done]
  251. * getStatus [Done]
  252. * setTimeout [Done]
  253. * lock [Done]
  254. * unlock [Done]
  255. * insert [Done]
  256. * select [Done]
  257. *
  258. * Slave/master: 2 / 2
  259. * setFakeSlaveLag [Done]
  260. * setFakeMaster [Done]
  261. *
  262. * Reflection: 6 / 6
  263. * fieldExists [Done]
  264. * indexInfo [Done]
  265. * fieldInfo [Done]
  266. * fieldType [Done]
  267. * indexUnique [Done]
  268. * textFieldSize [Done]
  269. *
  270. * Generation: 16 / 16
  271. * tableName [Done]
  272. * addQuotes [Done]
  273. * makeList [Done]
  274. * makeSelectOptions [Done]
  275. * estimateRowCount [Done]
  276. * nextSequenceValue [Done]
  277. * useIndexClause [Done]
  278. * replace [Done]
  279. * deleteJoin [Done]
  280. * lowPriorityOption [Done]
  281. * limitResult [Done]
  282. * limitResultForUpdate [Done]
  283. * timestamp [Done]
  284. * encodeBlob [Done]
  285. * decodeBlob [Done]
  286. * buildConcat [Done]
  287. */
  288. ######################################
  289. # Getters and Setters
  290. ######################################
  291. /**
  292. * Returns true if this database supports (and uses) cascading deletes
  293. */
  294. function cascadingDeletes() {
  295. return true;
  296. }
  297. /**
  298. * Returns true if this database supports (and uses) triggers (e.g. on the page table)
  299. */
  300. function cleanupTriggers() {
  301. return true;
  302. }
  303. /**
  304. * Returns true if this database is strict about what can be put into an IP field.
  305. * Specifically, it uses a NULL value instead of an empty string.
  306. */
  307. function strictIPs() {
  308. return true;
  309. }
  310. /**
  311. * Returns true if this database uses timestamps rather than integers
  312. */
  313. function realTimestamps() {
  314. return true;
  315. }
  316. /**
  317. * Returns true if this database does an implicit sort when doing GROUP BY
  318. */
  319. function implicitGroupby() {
  320. return false;
  321. }
  322. /**
  323. * Returns true if this database does an implicit order by when the column has an index
  324. * For example: SELECT page_title FROM page LIMIT 1
  325. */
  326. function implicitOrderby() {
  327. return false;
  328. }
  329. /**
  330. * Returns true if this database can do a native search on IP columns
  331. * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
  332. */
  333. function searchableIPs() {
  334. return true;
  335. }
  336. /**
  337. * Returns true if this database can use functional indexes
  338. */
  339. function functionalIndexes() {
  340. return true;
  341. }
  342. /**
  343. * Returns a unique string representing the wiki on the server
  344. */
  345. function getWikiID() {
  346. if( $this->mSchema ) {
  347. return "{$this->mDBname}-{$this->mSchema}";
  348. } else {
  349. return $this->mDBname;
  350. }
  351. }
  352. ######################################
  353. # Setup
  354. ######################################
  355. /**
  356. *
  357. * @param string $server hostname of database server
  358. * @param string $user username
  359. * @param string $password
  360. * @param string $dbName database name on the server
  361. * @param function $failFunction (optional)
  362. * @param integer $flags database behaviour flags (optional, unused)
  363. */
  364. public function DatabaseIbm_db2($server = false, $user = false, $password = false,
  365. $dbName = false, $failFunction = false, $flags = 0,
  366. $schema = self::USE_GLOBAL )
  367. {
  368. global $wgOut, $wgDBmwschema;
  369. # Can't get a reference if it hasn't been set yet
  370. if ( !isset( $wgOut ) ) {
  371. $wgOut = NULL;
  372. }
  373. $this->mOut =& $wgOut;
  374. $this->mFailFunction = $failFunction;
  375. $this->mFlags = DBO_TRX | $flags;
  376. if ( $schema == self::USE_GLOBAL ) {
  377. $this->mSchema = $wgDBmwschema;
  378. }
  379. else {
  380. $this->mSchema = $schema;
  381. }
  382. $this->open( $server, $user, $password, $dbName);
  383. }
  384. /**
  385. * Opens a database connection and returns it
  386. * Closes any existing connection
  387. * @return a fresh connection
  388. * @param string $server hostname
  389. * @param string $user
  390. * @param string $password
  391. * @param string $dbName database name
  392. */
  393. public function open( $server, $user, $password, $dbName )
  394. {
  395. // Load the port number
  396. global $wgDBport_db2, $wgDBcataloged;
  397. wfProfileIn( __METHOD__ );
  398. // Load IBM DB2 driver if missing
  399. if (!@extension_loaded('ibm_db2')) {
  400. @dl('ibm_db2.so');
  401. }
  402. // Test for IBM DB2 support, to avoid suppressed fatal error
  403. if ( !function_exists( 'db2_connect' ) ) {
  404. $error = "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?\n";
  405. wfDebug($error);
  406. $this->reportConnectionError($error);
  407. }
  408. if (!strlen($user)) { // Copied from Postgres
  409. return null;
  410. }
  411. // Close existing connection
  412. $this->close();
  413. // Cache conn info
  414. $this->mServer = $server;
  415. $this->mPort = $port = $wgDBport_db2;
  416. $this->mUser = $user;
  417. $this->mPassword = $password;
  418. $this->mDBname = $dbName;
  419. $this->mCataloged = $cataloged = $wgDBcataloged;
  420. if ( $cataloged == self::CATALOGED ) {
  421. $this->openCataloged($dbName, $user, $password);
  422. }
  423. elseif ( $cataloged == self::UNCATALOGED ) {
  424. $this->openUncataloged($dbName, $user, $password, $server, $port);
  425. }
  426. // Don't do this
  427. // Not all MediaWiki code is transactional
  428. // Rather, turn it off in the begin function and turn on after a commit
  429. // db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
  430. db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
  431. if ( $this->mConn == false ) {
  432. wfDebug( "DB connection error\n" );
  433. wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" );
  434. wfDebug( $this->lastError()."\n" );
  435. return null;
  436. }
  437. $this->mOpened = true;
  438. $this->applySchema();
  439. wfProfileOut( __METHOD__ );
  440. return $this->mConn;
  441. }
  442. /**
  443. * Opens a cataloged database connection, sets mConn
  444. */
  445. protected function openCataloged( $dbName, $user, $password )
  446. {
  447. @$this->mConn = db2_connect($dbName, $user, $password);
  448. }
  449. /**
  450. * Opens an uncataloged database connection, sets mConn
  451. */
  452. protected function openUncataloged( $dbName, $user, $password, $server, $port )
  453. {
  454. $str = "DRIVER={IBM DB2 ODBC DRIVER};";
  455. $str .= "DATABASE=$dbName;";
  456. $str .= "HOSTNAME=$server;";
  457. if ($port) $str .= "PORT=$port;";
  458. $str .= "PROTOCOL=TCPIP;";
  459. $str .= "UID=$user;";
  460. $str .= "PWD=$password;";
  461. @$this->mConn = db2_connect($str, $user, $password);
  462. }
  463. /**
  464. * Closes a database connection, if it is open
  465. * Returns success, true if already closed
  466. */
  467. public function close() {
  468. $this->mOpened = false;
  469. if ( $this->mConn ) {
  470. if ($this->trxLevel() > 0) {
  471. $this->commit();
  472. }
  473. return db2_close( $this->mConn );
  474. }
  475. else {
  476. return true;
  477. }
  478. }
  479. /**
  480. * Returns a fresh instance of this class
  481. * @static
  482. * @return
  483. * @param string $server hostname of database server
  484. * @param string $user username
  485. * @param string $password
  486. * @param string $dbName database name on the server
  487. * @param function $failFunction (optional)
  488. * @param integer $flags database behaviour flags (optional, unused)
  489. */
  490. static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0)
  491. {
  492. return new DatabaseIbm_db2( $server, $user, $password, $dbName, $failFunction, $flags );
  493. }
  494. /**
  495. * Retrieves the most current database error
  496. * Forces a database rollback
  497. */
  498. public function lastError() {
  499. if ($this->lastError2()) {
  500. $this->rollback();
  501. return true;
  502. }
  503. return false;
  504. }
  505. private function lastError2() {
  506. $connerr = db2_conn_errormsg();
  507. if ($connerr) return $connerr;
  508. $stmterr = db2_stmt_errormsg();
  509. if ($stmterr) return $stmterr;
  510. if ($this->mConn) return "No open connection.";
  511. if ($this->mOpened) return "No open connection allegedly.";
  512. return false;
  513. }
  514. /**
  515. * Get the last error number
  516. * Return 0 if no error
  517. * @return integer
  518. */
  519. public function lastErrno() {
  520. $connerr = db2_conn_error();
  521. if ($connerr) return $connerr;
  522. $stmterr = db2_stmt_error();
  523. if ($stmterr) return $stmterr;
  524. return 0;
  525. }
  526. /**
  527. * Is a database connection open?
  528. * @return
  529. */
  530. public function isOpen() { return $this->mOpened; }
  531. /**
  532. * The DBMS-dependent part of query()
  533. * @param $sql String: SQL query.
  534. * @return object Result object to feed to fetchObject, fetchRow, ...; or false on failure
  535. * @access private
  536. */
  537. /*private*/
  538. public function doQuery( $sql ) {
  539. //print "<li><pre>$sql</pre></li>";
  540. // Switch into the correct namespace
  541. $this->applySchema();
  542. $ret = db2_exec( $this->mConn, $sql );
  543. if( !$ret ) {
  544. print "<br><pre>";
  545. print $sql;
  546. print "</pre><br>";
  547. $error = db2_stmt_errormsg();
  548. throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( $error ) );
  549. }
  550. $this->mLastResult = $ret;
  551. $this->mAffectedRows = NULL; // Not calculated until asked for
  552. return $ret;
  553. }
  554. /**
  555. * @return string Version information from the database
  556. */
  557. public function getServerVersion() {
  558. $info = db2_server_info( $this->mConn );
  559. return $info->DBMS_VER;
  560. }
  561. /**
  562. * Queries whether a given table exists
  563. * @return boolean
  564. */
  565. public function tableExists( $table ) {
  566. $schema = $this->mSchema;
  567. $sql = <<< EOF
  568. SELECT COUNT(*) FROM SYSIBM.SYSTABLES ST
  569. WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema'
  570. EOF;
  571. $res = $this->query( $sql );
  572. if (!$res) return false;
  573. // If the table exists, there should be one of it
  574. @$row = $this->fetchRow($res);
  575. $count = $row[0];
  576. if ($count == '1' or $count == 1) {
  577. return true;
  578. }
  579. return false;
  580. }
  581. /**
  582. * Fetch the next row from the given result object, in object form.
  583. * Fields can be retrieved with $row->fieldname, with fields acting like
  584. * member variables.
  585. *
  586. * @param $res SQL result object as returned from Database::query(), etc.
  587. * @return DB2 row object
  588. * @throws DBUnexpectedError Thrown if the database returns an error
  589. */
  590. public function fetchObject( $res ) {
  591. if ( $res instanceof ResultWrapper ) {
  592. $res = $res->result;
  593. }
  594. @$row = db2_fetch_object( $res );
  595. if( $this->lastErrno() ) {
  596. throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
  597. }
  598. // Make field names lowercase for compatibility with MySQL
  599. if ($row)
  600. {
  601. $row2 = new BlankObject();
  602. foreach ($row as $key => $value)
  603. {
  604. $keyu = strtolower($key);
  605. $row2->$keyu = $value;
  606. }
  607. $row = $row2;
  608. }
  609. return $row;
  610. }
  611. /**
  612. * Fetch the next row from the given result object, in associative array
  613. * form. Fields are retrieved with $row['fieldname'].
  614. *
  615. * @param $res SQL result object as returned from Database::query(), etc.
  616. * @return DB2 row object
  617. * @throws DBUnexpectedError Thrown if the database returns an error
  618. */
  619. public function fetchRow( $res ) {
  620. if ( $res instanceof ResultWrapper ) {
  621. $res = $res->result;
  622. }
  623. @$row = db2_fetch_array( $res );
  624. if ( $this->lastErrno() ) {
  625. throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
  626. }
  627. return $row;
  628. }
  629. /**
  630. * Override if introduced to base Database class
  631. */
  632. public function initial_setup() {
  633. // do nothing
  634. }
  635. /**
  636. * Create tables, stored procedures, and so on
  637. */
  638. public function setup_database() {
  639. // Timeout was being changed earlier due to mysterious crashes
  640. // Changing it now may cause more problems than not changing it
  641. //set_time_limit(240);
  642. try {
  643. // TODO: switch to root login if available
  644. // Switch into the correct namespace
  645. $this->applySchema();
  646. $this->begin();
  647. $res = dbsource( "../maintenance/ibm_db2/tables.sql", $this);
  648. $res = null;
  649. // TODO: update mediawiki_version table
  650. // TODO: populate interwiki links
  651. $this->commit();
  652. }
  653. catch (MWException $mwe)
  654. {
  655. print "<br><pre>$mwe</pre><br>";
  656. }
  657. }
  658. /**
  659. * Escapes strings
  660. * Doesn't escape numbers
  661. * @param string s string to escape
  662. * @return escaped string
  663. */
  664. public function addQuotes( $s ) {
  665. //wfDebug("DB2::addQuotes($s)\n");
  666. if ( is_null( $s ) ) {
  667. return "NULL";
  668. } else if ($s instanceof Blob) {
  669. return "'".$s->fetch($s)."'";
  670. }
  671. $s = $this->strencode($s);
  672. if ( is_numeric($s) ) {
  673. return $s;
  674. }
  675. else {
  676. return "'$s'";
  677. }
  678. }
  679. /**
  680. * Escapes strings
  681. * Only escapes numbers going into non-numeric fields
  682. * @param string s string to escape
  683. * @return escaped string
  684. */
  685. public function addQuotesSmart( $table, $field, $s ) {
  686. if ( is_null( $s ) ) {
  687. return "NULL";
  688. } else if ($s instanceof Blob) {
  689. return "'".$s->fetch($s)."'";
  690. }
  691. $s = $this->strencode($s);
  692. if ( is_numeric($s) ) {
  693. // Check with the database if the column is actually numeric
  694. // This allows for numbers in titles, etc
  695. $res = $this->doQuery("SELECT $field FROM $table FETCH FIRST 1 ROWS ONLY");
  696. $type = db2_field_type($res, strtoupper($field));
  697. if ( $this->is_numeric_type( $type ) ) {
  698. //wfDebug("DB2: Numeric value going in a numeric column: $s in $type $field in $table\n");
  699. return $s;
  700. }
  701. else {
  702. wfDebug("DB2: Numeric in non-numeric: '$s' in $type $field in $table\n");
  703. return "'$s'";
  704. }
  705. }
  706. else {
  707. return "'$s'";
  708. }
  709. }
  710. /**
  711. * Verifies that a DB2 column/field type is numeric
  712. * @return bool true if numeric
  713. * @param string $type DB2 column type
  714. */
  715. public function is_numeric_type( $type ) {
  716. switch (strtoupper($type)) {
  717. case 'SMALLINT':
  718. case 'INTEGER':
  719. case 'INT':
  720. case 'BIGINT':
  721. case 'DECIMAL':
  722. case 'REAL':
  723. case 'DOUBLE':
  724. case 'DECFLOAT':
  725. return true;
  726. }
  727. return false;
  728. }
  729. /**
  730. * Alias for addQuotes()
  731. * @param string s string to escape
  732. * @return escaped string
  733. */
  734. public function strencode( $s ) {
  735. // Bloody useless function
  736. // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a.
  737. // But also necessary
  738. $s = db2_escape_string($s);
  739. // Wide characters are evil -- some of them look like '
  740. $s = utf8_encode($s);
  741. // Fix its stupidity
  742. $from = array("\\\\", "\\'", '\\n', '\\t', '\\"', '\\r');
  743. $to = array("\\", "''", "\n", "\t", '"', "\r");
  744. $s = str_replace($from, $to, $s); // DB2 expects '', not \' escaping
  745. return $s;
  746. }
  747. /**
  748. * Switch into the database schema
  749. */
  750. protected function applySchema() {
  751. if ( !($this->mSchemaSet) ) {
  752. $this->mSchemaSet = true;
  753. $this->begin();
  754. $this->doQuery("SET SCHEMA = $this->mSchema");
  755. $this->commit();
  756. }
  757. }
  758. /**
  759. * Start a transaction (mandatory)
  760. */
  761. public function begin() {
  762. // turn off auto-commit
  763. db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF);
  764. $this->mTrxLevel = 1;
  765. }
  766. /**
  767. * End a transaction
  768. * Must have a preceding begin()
  769. */
  770. public function commit() {
  771. db2_commit($this->mConn);
  772. // turn auto-commit back on
  773. db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
  774. $this->mTrxLevel = 0;
  775. }
  776. /**
  777. * Cancel a transaction
  778. */
  779. public function rollback() {
  780. db2_rollback($this->mConn);
  781. // turn auto-commit back on
  782. // not sure if this is appropriate
  783. db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON);
  784. $this->mTrxLevel = 0;
  785. }
  786. /**
  787. * Makes an encoded list of strings from an array
  788. * $mode:
  789. * LIST_COMMA - comma separated, no field names
  790. * LIST_AND - ANDed WHERE clause (without the WHERE)
  791. * LIST_OR - ORed WHERE clause (without the WHERE)
  792. * LIST_SET - comma separated with field names, like a SET clause
  793. * LIST_NAMES - comma separated field names
  794. */
  795. public function makeList( $a, $mode = LIST_COMMA ) {
  796. wfDebug("DB2::makeList()\n");
  797. if ( !is_array( $a ) ) {
  798. throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
  799. }
  800. $first = true;
  801. $list = '';
  802. foreach ( $a as $field => $value ) {
  803. if ( !$first ) {
  804. if ( $mode == LIST_AND ) {
  805. $list .= ' AND ';
  806. } elseif($mode == LIST_OR) {
  807. $list .= ' OR ';
  808. } else {
  809. $list .= ',';
  810. }
  811. } else {
  812. $first = false;
  813. }
  814. if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
  815. $list .= "($value)";
  816. } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
  817. $list .= "$value";
  818. } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
  819. if( count( $value ) == 0 ) {
  820. throw new MWException( __METHOD__.': empty input' );
  821. } elseif( count( $value ) == 1 ) {
  822. // Special-case single values, as IN isn't terribly efficient
  823. // Don't necessarily assume the single key is 0; we don't
  824. // enforce linear numeric ordering on other arrays here.
  825. $value = array_values( $value );
  826. $list .= $field." = ".$this->addQuotes( $value[0] );
  827. } else {
  828. $list .= $field." IN (".$this->makeList($value).") ";
  829. }
  830. } elseif( is_null($value) ) {
  831. if ( $mode == LIST_AND || $mode == LIST_OR ) {
  832. $list .= "$field IS ";
  833. } elseif ( $mode == LIST_SET ) {
  834. $list .= "$field = ";
  835. }
  836. $list .= 'NULL';
  837. } else {
  838. if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
  839. $list .= "$field = ";
  840. }
  841. if ( $mode == LIST_NAMES ) {
  842. $list .= $value;
  843. }
  844. // Leo: Can't insert quoted numbers into numeric columns
  845. // (?) Might cause other problems. May have to check column type before insertion.
  846. else if ( is_numeric($value) ) {
  847. $list .= $value;
  848. }
  849. else {
  850. $list .= $this->addQuotes( $value );
  851. }
  852. }
  853. }
  854. return $list;
  855. }
  856. /**
  857. * Makes an encoded list of strings from an array
  858. * Quotes numeric values being inserted into non-numeric fields
  859. * @return string
  860. * @param string $table name of the table
  861. * @param array $a list of values
  862. * @param $mode:
  863. * LIST_COMMA - comma separated, no field names
  864. * LIST_AND - ANDed WHERE clause (without the WHERE)
  865. * LIST_OR - ORed WHERE clause (without the WHERE)
  866. * LIST_SET - comma separated with field names, like a SET clause
  867. * LIST_NAMES - comma separated field names
  868. */
  869. public function makeListSmart( $table, $a, $mode = LIST_COMMA ) {
  870. if ( !is_array( $a ) ) {
  871. throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
  872. }
  873. $first = true;
  874. $list = '';
  875. foreach ( $a as $field => $value ) {
  876. if ( !$first ) {
  877. if ( $mode == LIST_AND ) {
  878. $list .= ' AND ';
  879. } elseif($mode == LIST_OR) {
  880. $list .= ' OR ';
  881. } else {
  882. $list .= ',';
  883. }
  884. } else {
  885. $first = false;
  886. }
  887. if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
  888. $list .= "($value)";
  889. } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
  890. $list .= "$value";
  891. } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
  892. if( count( $value ) == 0 ) {
  893. throw new MWException( __METHOD__.': empty input' );
  894. } elseif( count( $value ) == 1 ) {
  895. // Special-case single values, as IN isn't terribly efficient
  896. // Don't necessarily assume the single key is 0; we don't
  897. // enforce linear numeric ordering on other arrays here.
  898. $value = array_values( $value );
  899. $list .= $field." = ".$this->addQuotes( $value[0] );
  900. } else {
  901. $list .= $field." IN (".$this->makeList($value).") ";
  902. }
  903. } elseif( is_null($value) ) {
  904. if ( $mode == LIST_AND || $mode == LIST_OR ) {
  905. $list .= "$field IS ";
  906. } elseif ( $mode == LIST_SET ) {
  907. $list .= "$field = ";
  908. }
  909. $list .= 'NULL';
  910. } else {
  911. if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
  912. $list .= "$field = ";
  913. }
  914. if ( $mode == LIST_NAMES ) {
  915. $list .= $value;
  916. }
  917. else {
  918. $list .= $this->addQuotesSmart( $table, $field, $value );
  919. }
  920. }
  921. }
  922. return $list;
  923. }
  924. /**
  925. * Construct a LIMIT query with optional offset
  926. * This is used for query pages
  927. * $sql string SQL query we will append the limit too
  928. * $limit integer the SQL limit
  929. * $offset integer the SQL offset (default false)
  930. */
  931. public function limitResult($sql, $limit, $offset=false) {
  932. if( !is_numeric($limit) ) {
  933. throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
  934. }
  935. if( $offset ) {
  936. wfDebug("Offset parameter not supported in limitResult()\n");
  937. }
  938. // TODO implement proper offset handling
  939. // idea: get all the rows between 0 and offset, advance cursor to offset
  940. return "$sql FETCH FIRST $limit ROWS ONLY ";
  941. }
  942. /**
  943. * Handle reserved keyword replacement in table names
  944. * @return
  945. * @param $name Object
  946. */
  947. public function tableName( $name ) {
  948. # Replace reserved words with better ones
  949. switch( $name ) {
  950. case 'user':
  951. return 'mwuser';
  952. case 'text':
  953. return 'pagecontent';
  954. default:
  955. return $name;
  956. }
  957. }
  958. /**
  959. * Generates a timestamp in an insertable format
  960. * @return string timestamp value
  961. * @param timestamp $ts
  962. */
  963. public function timestamp( $ts=0 ) {
  964. // TS_MW cannot be easily distinguished from an integer
  965. return wfTimestamp(TS_DB2,$ts);
  966. }
  967. /**
  968. * Return the next in a sequence, save the value for retrieval via insertId()
  969. * @param string seqName Name of a defined sequence in the database
  970. * @return next value in that sequence
  971. */
  972. public function nextSequenceValue( $seqName ) {
  973. $safeseq = preg_replace( "/'/", "''", $seqName );
  974. $res = $this->query( "VALUES NEXTVAL FOR $safeseq" );
  975. $row = $this->fetchRow( $res );
  976. $this->mInsertId = $row[0];
  977. $this->freeResult( $res );
  978. return $this->mInsertId;
  979. }
  980. /**
  981. * This must be called after nextSequenceVal
  982. * @return Last sequence value used as a primary key
  983. */
  984. public function insertId() {
  985. return $this->mInsertId;
  986. }
  987. /**
  988. * INSERT wrapper, inserts an array into a table
  989. *
  990. * $args may be a single associative array, or an array of these with numeric keys,
  991. * for multi-row insert
  992. *
  993. * @param array $table String: Name of the table to insert to.
  994. * @param array $args Array: Items to insert into the table.
  995. * @param array $fname String: Name of the function, for profiling
  996. * @param mixed $options String or Array. Valid options: IGNORE
  997. *
  998. * @return bool Success of insert operation. IGNORE always returns true.
  999. */
  1000. public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) {
  1001. wfDebug("DB2::insert($table)\n");
  1002. if ( !count( $args ) ) {
  1003. return true;
  1004. }
  1005. $table = $this->tableName( $table );
  1006. if ( !is_array( $options ) )
  1007. $options = array( $options );
  1008. if ( isset( $args[0] ) && is_array( $args[0] ) ) {
  1009. }
  1010. else {
  1011. $args = array($args);
  1012. }
  1013. $keys = array_keys( $args[0] );
  1014. // If IGNORE is set, we use savepoints to emulate mysql's behavior
  1015. $ignore = in_array( 'IGNORE', $options ) ? 'mw' : '';
  1016. // Cache autocommit value at the start
  1017. $oldautocommit = db2_autocommit($this->mConn);
  1018. // If we are not in a transaction, we need to be for savepoint trickery
  1019. $didbegin = 0;
  1020. if (! $this->mTrxLevel) {
  1021. $this->begin();
  1022. $didbegin = 1;
  1023. }
  1024. if ( $ignore ) {
  1025. $olde = error_reporting( 0 );
  1026. // For future use, we may want to track the number of actual inserts
  1027. // Right now, insert (all writes) simply return true/false
  1028. $numrowsinserted = 0;
  1029. }
  1030. $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
  1031. if ( !$ignore ) {
  1032. $first = true;
  1033. foreach ( $args as $row ) {
  1034. if ( $first ) {
  1035. $first = false;
  1036. } else {
  1037. $sql .= ',';
  1038. }
  1039. $sql .= '(' . $this->makeListSmart( $table, $row ) . ')';
  1040. }
  1041. $res = (bool)$this->query( $sql, $fname, $ignore );
  1042. }
  1043. else {
  1044. $res = true;
  1045. $origsql = $sql;
  1046. foreach ( $args as $row ) {
  1047. $tempsql = $origsql;
  1048. $tempsql .= '(' . $this->makeListSmart( $table, $row ) . ')';
  1049. if ( $ignore ) {
  1050. db2_exec($this->mConn, "SAVEPOINT $ignore");
  1051. }
  1052. $tempres = (bool)$this->query( $tempsql, $fname, $ignore );
  1053. if ( $ignore ) {
  1054. $bar = db2_stmt_error();
  1055. if ($bar != false) {
  1056. db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore" );
  1057. }
  1058. else {
  1059. db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore" );
  1060. $numrowsinserted++;
  1061. }
  1062. }
  1063. // If any of them fail, we fail overall for this function call
  1064. // Note that this will be ignored if IGNORE is set
  1065. if (! $tempres)
  1066. $res = false;
  1067. }
  1068. }
  1069. if ($didbegin) {
  1070. $this->commit();
  1071. }
  1072. // if autocommit used to be on, it's ok to commit everything
  1073. else if ($oldautocommit)
  1074. {
  1075. $this->commit();
  1076. }
  1077. if ( $ignore ) {
  1078. $olde = error_reporting( $olde );
  1079. // Set the affected row count for the whole operation
  1080. $this->mAffectedRows = $numrowsinserted;
  1081. // IGNORE always returns true
  1082. return true;
  1083. }
  1084. return $res;
  1085. }
  1086. /**
  1087. * UPDATE wrapper, takes a condition array and a SET array
  1088. *
  1089. * @param string $table The table to UPDATE
  1090. * @param array $values An array of values to SET
  1091. * @param array $conds An array of conditions (WHERE). Use '*' to update all rows.
  1092. * @param string $fname The Class::Function calling this function
  1093. * (for the log)
  1094. * @param array $options An array of UPDATE options, can be one or
  1095. * more of IGNORE, LOW_PRIORITY
  1096. * @return bool
  1097. */
  1098. function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
  1099. $table = $this->tableName( $table );
  1100. $opts = $this->makeUpdateOptions( $options );
  1101. $sql = "UPDATE $opts $table SET " . $this->makeListSmart( $table, $values, LIST_SET );
  1102. if ( $conds != '*' ) {
  1103. $sql .= " WHERE " . $this->makeListSmart( $table, $conds, LIST_AND );
  1104. }
  1105. return $this->query( $sql, $fname );
  1106. }
  1107. /**
  1108. * DELETE query wrapper
  1109. *
  1110. * Use $conds == "*" to delete all rows
  1111. */
  1112. function delete( $table, $conds, $fname = 'Database::delete' ) {
  1113. if ( !$conds ) {
  1114. throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
  1115. }
  1116. $table = $this->tableName( $table );
  1117. $sql = "DELETE FROM $table";
  1118. if ( $conds != '*' ) {
  1119. $sql .= ' WHERE ' . $this->makeListSmart( $table, $conds, LIST_AND );
  1120. }
  1121. return $this->query( $sql, $fname );
  1122. }
  1123. /**
  1124. * Returns the number of rows affected by the last query or 0
  1125. * @return int the number of rows affected by the last query
  1126. */
  1127. public function affectedRows() {
  1128. if ( !is_null( $this->mAffectedRows ) ) {
  1129. // Forced result for simulated queries
  1130. return $this->mAffectedRows;
  1131. }
  1132. if( empty( $this->mLastResult ) )
  1133. return 0;
  1134. return db2_num_rows( $this->mLastResult );
  1135. }
  1136. /**
  1137. * USE INDEX clause
  1138. * DB2 doesn't have them and returns ""
  1139. * @param sting $index
  1140. */
  1141. public function useIndexClause( $index ) {
  1142. return "";
  1143. }
  1144. /**
  1145. * Simulates REPLACE with a DELETE followed by INSERT
  1146. * @param $table Object
  1147. * @param array $uniqueIndexes array consisting of indexes and arrays of indexes
  1148. * @param array $rows Rows to insert
  1149. * @param string $fname Name of the function for profiling
  1150. * @return nothing
  1151. */
  1152. function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) {
  1153. $table = $this->tableName( $table );
  1154. if (count($rows)==0) {
  1155. return;
  1156. }
  1157. # Single row case
  1158. if ( !is_array( reset( $rows ) ) ) {
  1159. $rows = array( $rows );
  1160. }
  1161. foreach( $rows as $row ) {
  1162. # Delete rows which collide
  1163. if ( $uniqueIndexes ) {
  1164. $sql = "DELETE FROM $table WHERE ";
  1165. $first = true;
  1166. foreach ( $uniqueIndexes as $index ) {
  1167. if ( $first ) {
  1168. $first = false;
  1169. $sql .= "(";
  1170. } else {
  1171. $sql .= ') OR (';
  1172. }
  1173. if ( is_array( $index ) ) {
  1174. $first2 = true;
  1175. foreach ( $index as $col ) {
  1176. if ( $first2 ) {
  1177. $first2 = false;
  1178. } else {
  1179. $sql .= ' AND ';
  1180. }
  1181. $sql .= $col.'=' . $this->addQuotes( $row[$col] );
  1182. }
  1183. } else {
  1184. $sql .= $index.'=' . $this->addQuotes( $row[$index] );
  1185. }
  1186. }
  1187. $sql .= ')';
  1188. $this->query( $sql, $fname );
  1189. }
  1190. # Now insert the row
  1191. $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' .
  1192. $this->makeList( $row, LIST_COMMA ) . ')';
  1193. $this->query( $sql, $fname );
  1194. }
  1195. }
  1196. /**
  1197. * Returns the number of rows in the result set
  1198. * Has to be called right after the corresponding select query
  1199. * @param Object $res result set
  1200. * @return int number of rows
  1201. */
  1202. public function numRows( $res ) {
  1203. if ( $res instanceof ResultWrapper ) {
  1204. $res = $res->result;
  1205. }
  1206. if ( $this->mNumRows ) {
  1207. return $this->mNumRows;
  1208. }
  1209. else {
  1210. return 0;
  1211. }
  1212. }
  1213. /**
  1214. * Moves the row pointer of the result set
  1215. * @param Object $res result set
  1216. * @param int $row row number
  1217. * @return success or failure
  1218. */
  1219. public function dataSeek( $res, $row ) {
  1220. if ( $res instanceof ResultWrapper ) {
  1221. $res = $res->result;
  1222. }
  1223. return db2_fetch_row( $res, $row );
  1224. }
  1225. ###
  1226. # Fix notices in Block.php
  1227. ###
  1228. /**
  1229. * Frees memory associated with a statement resource
  1230. * @param Object $res Statement resource to free
  1231. * @return bool success or failure
  1232. */
  1233. public function freeResult( $res ) {
  1234. if ( $res instanceof ResultWrapper ) {
  1235. $res = $res->result;
  1236. }
  1237. if ( !@db2_free_result( $res ) ) {
  1238. throw new DBUnexpectedError($this, "Unable to free DB2 result\n" );
  1239. }
  1240. }
  1241. /**
  1242. * Returns the number of columns in a resource
  1243. * @param Object $res Statement resource
  1244. * @return Number of fields/columns in resource
  1245. */
  1246. public function numFields( $res ) {
  1247. if ( $res instanceof ResultWrapper ) {
  1248. $res = $res->result;
  1249. }
  1250. return db2_num_fields( $res );
  1251. }
  1252. /**
  1253. * Returns the nth column name
  1254. * @param Object $res Statement resource
  1255. * @param int $n Index of field or column
  1256. * @return string name of nth column
  1257. */
  1258. public function fieldName( $res, $n ) {
  1259. if ( $res instanceof ResultWrapper ) {
  1260. $res = $res->result;
  1261. }
  1262. return db2_field_name( $res, $n );
  1263. }
  1264. /**
  1265. * SELECT wrapper
  1266. *
  1267. * @param mixed $table Array or string, table name(s) (prefix auto-added)
  1268. * @param mixed $vars Array or string, field name(s) to be retrieved
  1269. * @param mixed $conds Array or string, condition(s) for WHERE
  1270. * @param string $fname Calling function name (use __METHOD__) for logs/profiling
  1271. * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')),
  1272. * see Database::makeSelectOptions code for list of supported stuff
  1273. * @param array $join_conds Associative array of table join conditions (optional)
  1274. * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
  1275. * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
  1276. */
  1277. public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() )
  1278. {
  1279. $res = parent::select( $table, $vars, $conds, $fname, $options, $join_conds );
  1280. // We must adjust for offset
  1281. if ( isset( $options['LIMIT'] ) ) {
  1282. if ( isset ($options['OFFSET'] ) ) {
  1283. $limit = $options['LIMIT'];
  1284. $offset = $options['OFFSET'];
  1285. }
  1286. }
  1287. // DB2 does not have a proper num_rows() function yet, so we must emulate it
  1288. // DB2 9.5.3/9.5.4 and the corresponding ibm_db2 driver will introduce a working one
  1289. // Yay!
  1290. // we want the count
  1291. $vars2 = array('count(*) as num_rows');
  1292. // respecting just the limit option
  1293. $options2 = array();
  1294. if ( isset( $options['LIMIT'] ) ) $options2['LIMIT'] = $options['LIMIT'];
  1295. // but don't try to emulate for GROUP BY
  1296. if ( isset( $options['GROUP BY'] ) ) return $res;
  1297. $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds );
  1298. $obj = $this->fetchObject($res2);
  1299. $this->mNumRows = $obj->num_rows;
  1300. wfDebug("DatabaseIbm_db2::select: There are $this->mNumRows rows.\n");
  1301. return $res;
  1302. }
  1303. /**
  1304. * Handles ordering, grouping, and having options ('GROUP BY' => colname)
  1305. * Has limited support for per-column options (colnum => 'DISTINCT')
  1306. *
  1307. * @private
  1308. *
  1309. * @param array $options an associative array of options to be turned into
  1310. * an SQL query, valid keys are listed in the function.
  1311. * @return array
  1312. */
  1313. function makeSelectOptions( $options ) {
  1314. $preLimitTail = $postLimitTail = '';
  1315. $startOpts = '';
  1316. $noKeyOptions = array();
  1317. foreach ( $options as $key => $option ) {
  1318. if ( is_numeric( $key ) ) {
  1319. $noKeyOptions[$option] = true;
  1320. }
  1321. }
  1322. if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
  1323. if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
  1324. if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
  1325. if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
  1326. return array( $startOpts, '', $preLimitTail, $postLimitTail );
  1327. }
  1328. /**
  1329. * Returns link to IBM DB2 free download
  1330. * @return string wikitext of a link to the server software's web site
  1331. */
  1332. public function getSoftwareLink() {
  1333. return "[http://www.ibm.com/software/data/db2/express/?s_cmp=ECDDWW01&s_tact=MediaWiki IBM DB2]";
  1334. }
  1335. /**
  1336. * Does nothing
  1337. * @param object $db
  1338. * @return bool true
  1339. */
  1340. public function selectDB( $db ) {
  1341. return true;
  1342. }
  1343. /**
  1344. * Returns an SQL expression for a simple conditional.
  1345. * Uses CASE on DB2
  1346. *
  1347. * @param string $cond SQL expression which will result in a boolean value
  1348. * @param string $trueVal SQL expression to return if true
  1349. * @param string $falseVal SQL expression to return if false
  1350. * @return string SQL fragment
  1351. */
  1352. public function conditional( $cond, $trueVal, $falseVal ) {
  1353. return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) ";
  1354. }
  1355. ###
  1356. # Fix search crash
  1357. ###
  1358. /**
  1359. * Get search engine class. All subclasses of this
  1360. * need to implement this if they wish to use searching.
  1361. *
  1362. * @return string
  1363. */
  1364. public function getSearchEngine() {
  1365. return "SearchIBM_DB2";
  1366. }
  1367. ###
  1368. # Tuesday the 14th of October, 2008
  1369. ###
  1370. /**
  1371. * Did the last database access fail because of deadlock?
  1372. * @return bool
  1373. */
  1374. public function wasDeadlock() {
  1375. // get SQLSTATE
  1376. $err = $this->lastErrno();
  1377. switch($err) {
  1378. case '40001': // sql0911n, Deadlock or timeout, rollback
  1379. case '57011': // sql0904n, Resource unavailable, no rollback
  1380. case '57033': // sql0913n, Deadlock or timeout, no rollback
  1381. wfDebug("In a deadlock because of SQLSTATE $err");
  1382. return true;
  1383. }
  1384. return false;
  1385. }
  1386. /**
  1387. * Ping the server and try to reconnect if it there is no connection
  1388. * The connection may be closed and reopened while this happens
  1389. * @return bool whether the connection exists
  1390. */
  1391. public function ping() {
  1392. // db2_ping() doesn't exist
  1393. // Emulate
  1394. $this->close();
  1395. if ($this->mCataloged == NULL) {
  1396. return false;
  1397. }
  1398. else if ($this->mCataloged) {
  1399. $this->mConn = $this->openCataloged($this->mDBName, $this->mUser, $this->mPassword);
  1400. }
  1401. else if (!$this->mCataloged) {
  1402. $this->mConn = $this->openUncataloged($this->mDBName, $this->mUser, $this->mPassword, $this->mServer, $this->mPort);
  1403. }
  1404. return false;
  1405. }
  1406. ######################################
  1407. # Unimplemented and not applicable
  1408. ######################################
  1409. /**
  1410. * Not implemented
  1411. * @return string ''
  1412. * @deprecated
  1413. */
  1414. public function getStatus( $which ) { wfDebug('Not implemented for DB2: getStatus()'); return ''; }
  1415. /**
  1416. * Not implemented
  1417. * @deprecated
  1418. */
  1419. public function setTimeout( $timeout ) { wfDebug('Not implemented for DB2: setTimeout()'); }
  1420. /**
  1421. * Not implemented
  1422. * TODO
  1423. * @return bool true
  1424. */
  1425. public function lock( $lockName, $method ) { wfDebug('Not implemented for DB2: lock()'); return true; }
  1426. /**
  1427. * Not implemented
  1428. * TODO
  1429. * @return bool true
  1430. */
  1431. public function unlock( $lockName, $method ) { wfDebug('Not implemented for DB2: unlock()'); return true; }
  1432. /**
  1433. * Not implemented
  1434. * @deprecated
  1435. */
  1436. public function setFakeSlaveLag( $lag ) { wfDebug('Not implemented for DB2: setFakeSlaveLag()'); }
  1437. /**
  1438. * Not implemented
  1439. * @deprecated
  1440. */
  1441. public function setFakeMaster( $enabled ) { wfDebug('Not implemented for DB2: setFakeMaster()'); }
  1442. /**
  1443. * Not implemented
  1444. * @return string $sql
  1445. * @deprecated
  1446. */
  1447. public function limitResultForUpdate($sql, $num) { return $sql; }
  1448. /**
  1449. * No such option
  1450. * @return string ''
  1451. * @deprecated
  1452. */
  1453. public function lowPriorityOption() { return ''; }
  1454. ######################################
  1455. # Reflection
  1456. ######################################
  1457. /**
  1458. * Query whether a given column exists in the mediawiki schema
  1459. * @param string $table name of the table
  1460. * @param string $field name of the column
  1461. * @param string $fname function name for logging and profiling
  1462. */
  1463. public function fieldExists( $table, $field, $fname = 'DatabaseIbm_db2::fieldExists' ) {
  1464. $table = $this->tableName( $table );
  1465. $schema = $this->mSchema;
  1466. $etable = preg_replace("/'/", "''", $table);
  1467. $eschema = preg_replace("/'/", "''", $schema);
  1468. $ecol = preg_replace("/'/", "''", $field);
  1469. $sql = <<<SQL
  1470. SELECT 1 as fieldexists
  1471. FROM sysibm.syscolumns sc
  1472. WHERE sc.name='$ecol' AND sc.tbname='$etable' AND sc.tbcreator='$eschema'
  1473. SQL;
  1474. $res = $this->query( $sql, $fname );
  1475. $count = $res ? $this->numRows($res) : 0;
  1476. if ($res)
  1477. $this->freeResult( $res );
  1478. return $count;
  1479. }
  1480. /**
  1481. * Returns information about an index
  1482. * If errors are explicitly ignored, returns NULL on failure
  1483. * @param string $table table name
  1484. * @param string $index index name
  1485. * @param string
  1486. * @return object query row in object form
  1487. */
  1488. public function indexInfo( $table, $index, $fname = 'DatabaseIbm_db2::indexExists' ) {
  1489. $table = $this->tableName( $table );
  1490. $sql = <<<SQL
  1491. SELECT name as indexname
  1492. FROM sysibm.sysindexes si
  1493. WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
  1494. SQL;
  1495. $res = $this->query( $sql, $fname );
  1496. if ( !$res ) {
  1497. return NULL;
  1498. }
  1499. $row = $this->fetchObject( $res );
  1500. if ($row != NULL) return $row;
  1501. else return false;
  1502. }
  1503. /**
  1504. * Returns an information object on a table column
  1505. * @param string $table table name
  1506. * @param string $field column name
  1507. * @return IBM_DB2Field
  1508. */
  1509. public function fieldInfo( $table, $field ) {
  1510. return IBM_DB2Field::fromText($this, $table, $field);
  1511. }
  1512. /**
  1513. * db2_field_type() wrapper
  1514. * @param Object $res Result of executed statement
  1515. * @param mixed $index number or name of the column
  1516. * @return string column type
  1517. */
  1518. public function fieldType( $res, $index ) {
  1519. if ( $res instanceof ResultWrapper ) {
  1520. $res = $res->result;
  1521. }
  1522. return db2_field_type( $res, $index );
  1523. }
  1524. /**
  1525. * Verifies that an index was created as unique
  1526. * @param string $table table name
  1527. * @param string $index index name
  1528. * @param string $fnam function name for profiling
  1529. * @return bool
  1530. */
  1531. public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) {
  1532. $table = $this->tableName( $table );
  1533. $sql = <<<SQL
  1534. SELECT si.name as indexname
  1535. FROM sysibm.sysindexes si
  1536. WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema'
  1537. AND si.uniquerule IN ('U', 'P')
  1538. SQL;
  1539. $res = $this->query( $sql, $fname );
  1540. if ( !$res ) {
  1541. return null;
  1542. }
  1543. if ($this->fetchObject( $res )) {
  1544. return true;
  1545. }
  1546. return false;
  1547. }
  1548. /**
  1549. * Returns the size of a text field, or -1 for "unlimited"
  1550. * @param string $table table name
  1551. * @param string $field column name
  1552. * @return int length or -1 for unlimited
  1553. */
  1554. public function textFieldSize( $table, $field ) {
  1555. $table = $this->tableName( $table );
  1556. $sql = <<<SQL
  1557. SELECT length as size
  1558. FROM sysibm.syscolumns sc
  1559. WHERE sc.name='$field' AND sc.tbname='$table' AND sc.tbcreator='$this->mSchema'
  1560. SQL;
  1561. $res = $this->query($sql);
  1562. $row = $this->fetchObject($res);
  1563. $size = $row->size;
  1564. $this->freeResult( $res );
  1565. return $size;
  1566. }
  1567. /**
  1568. * DELETE where the condition is a join
  1569. * @param string $delTable deleting from this table
  1570. * @param string $joinTable using data from this table
  1571. * @param string $delVar variable in deleteable table
  1572. * @param string $joinVar variable in data table
  1573. * @param array $conds conditionals for join table
  1574. * @param string $fname function name for profiling
  1575. */
  1576. public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) {
  1577. if ( !$conds ) {
  1578. throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' );
  1579. }
  1580. $delTable = $this->tableName( $delTable );
  1581. $joinTable = $this->tableName( $joinTable );
  1582. $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable ";
  1583. if ( $conds != '*' ) {
  1584. $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND );
  1585. }
  1586. $sql .= ')';
  1587. $this->query( $sql, $fname );
  1588. }
  1589. /**
  1590. * Estimate rows in dataset
  1591. * Returns estimated count, based on COUNT(*) output
  1592. * Takes same arguments as Database::select()
  1593. * @param string $table table name
  1594. * @param array $vars unused
  1595. * @param array $conds filters on the table
  1596. * @param string $fname function name for profiling
  1597. * @param array $options options for select
  1598. * @return int row count
  1599. */
  1600. public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
  1601. $rows = 0;
  1602. $res = $this->select ($table, 'COUNT(*) as mwrowcount', $conds, $fname, $options );
  1603. if ($res) {
  1604. $row = $this->fetchRow($res);
  1605. $rows = (isset($row['mwrowcount'])) ? $row['mwrowcount'] : 0;
  1606. }
  1607. $this->freeResult($res);
  1608. return $rows;
  1609. }
  1610. /**
  1611. * Description is left as an exercise for the reader
  1612. * @param mixed $b data to be encoded
  1613. * @return IBM_DB2Blob
  1614. */
  1615. public function encodeBlob($b) {
  1616. return new IBM_DB2Blob($b);
  1617. }
  1618. /**
  1619. * Description is left as an exercise for the reader
  1620. * @param IBM_DB2Blob $b data to be decoded
  1621. * @return mixed
  1622. */
  1623. public function decodeBlob($b) {
  1624. return $b->getData();
  1625. }
  1626. /**
  1627. * Convert into a list of string being concatenated
  1628. * @param array $stringList strings that need to be joined together by the SQL engine
  1629. * @return string joined by the concatenation operator
  1630. */
  1631. public function buildConcat( $stringList ) {
  1632. // || is equivalent to CONCAT
  1633. // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz'
  1634. return implode( ' || ', $stringList );
  1635. }
  1636. /**
  1637. * Generates the SQL required to convert a DB2 timestamp into a Unix epoch
  1638. * @param string $column name of timestamp column
  1639. * @return string SQL code
  1640. */
  1641. public function extractUnixEpoch( $column ) {
  1642. // TODO
  1643. // see SpecialAncientpages
  1644. }
  1645. }
  1646. ?>