Database.php 80 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856
  1. <?php
  2. /**
  3. * @defgroup Database Database
  4. *
  5. * @file
  6. * @ingroup Database
  7. * This file deals with MySQL interface functions
  8. * and query specifics/optimisations
  9. */
  10. /** Number of times to re-try an operation in case of deadlock */
  11. define( 'DEADLOCK_TRIES', 4 );
  12. /** Minimum time to wait before retry, in microseconds */
  13. define( 'DEADLOCK_DELAY_MIN', 500000 );
  14. /** Maximum time to wait before retry */
  15. define( 'DEADLOCK_DELAY_MAX', 1500000 );
  16. /**
  17. * Database abstraction object
  18. * @ingroup Database
  19. */
  20. class Database {
  21. #------------------------------------------------------------------------------
  22. # Variables
  23. #------------------------------------------------------------------------------
  24. protected $mLastQuery = '';
  25. protected $mDoneWrites = false;
  26. protected $mPHPError = false;
  27. protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname;
  28. protected $mOpened = false;
  29. protected $mFailFunction;
  30. protected $mTablePrefix;
  31. protected $mFlags;
  32. protected $mTrxLevel = 0;
  33. protected $mErrorCount = 0;
  34. protected $mLBInfo = array();
  35. protected $mFakeSlaveLag = null, $mFakeMaster = false;
  36. #------------------------------------------------------------------------------
  37. # Accessors
  38. #------------------------------------------------------------------------------
  39. # These optionally set a variable and return the previous state
  40. /**
  41. * Fail function, takes a Database as a parameter
  42. * Set to false for default, 1 for ignore errors
  43. */
  44. function failFunction( $function = NULL ) {
  45. return wfSetVar( $this->mFailFunction, $function );
  46. }
  47. /**
  48. * Output page, used for reporting errors
  49. * FALSE means discard output
  50. */
  51. function setOutputPage( $out ) {
  52. wfDeprecated( __METHOD__ );
  53. }
  54. /**
  55. * Boolean, controls output of large amounts of debug information
  56. */
  57. function debug( $debug = NULL ) {
  58. return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
  59. }
  60. /**
  61. * Turns buffering of SQL result sets on (true) or off (false).
  62. * Default is "on" and it should not be changed without good reasons.
  63. */
  64. function bufferResults( $buffer = NULL ) {
  65. if ( is_null( $buffer ) ) {
  66. return !(bool)( $this->mFlags & DBO_NOBUFFER );
  67. } else {
  68. return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
  69. }
  70. }
  71. /**
  72. * Turns on (false) or off (true) the automatic generation and sending
  73. * of a "we're sorry, but there has been a database error" page on
  74. * database errors. Default is on (false). When turned off, the
  75. * code should use lastErrno() and lastError() to handle the
  76. * situation as appropriate.
  77. */
  78. function ignoreErrors( $ignoreErrors = NULL ) {
  79. return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
  80. }
  81. /**
  82. * The current depth of nested transactions
  83. * @param $level Integer: , default NULL.
  84. */
  85. function trxLevel( $level = NULL ) {
  86. return wfSetVar( $this->mTrxLevel, $level );
  87. }
  88. /**
  89. * Number of errors logged, only useful when errors are ignored
  90. */
  91. function errorCount( $count = NULL ) {
  92. return wfSetVar( $this->mErrorCount, $count );
  93. }
  94. function tablePrefix( $prefix = null ) {
  95. return wfSetVar( $this->mTablePrefix, $prefix );
  96. }
  97. /**
  98. * Properties passed down from the server info array of the load balancer
  99. */
  100. function getLBInfo( $name = NULL ) {
  101. if ( is_null( $name ) ) {
  102. return $this->mLBInfo;
  103. } else {
  104. if ( array_key_exists( $name, $this->mLBInfo ) ) {
  105. return $this->mLBInfo[$name];
  106. } else {
  107. return NULL;
  108. }
  109. }
  110. }
  111. function setLBInfo( $name, $value = NULL ) {
  112. if ( is_null( $value ) ) {
  113. $this->mLBInfo = $name;
  114. } else {
  115. $this->mLBInfo[$name] = $value;
  116. }
  117. }
  118. /**
  119. * Set lag time in seconds for a fake slave
  120. */
  121. function setFakeSlaveLag( $lag ) {
  122. $this->mFakeSlaveLag = $lag;
  123. }
  124. /**
  125. * Make this connection a fake master
  126. */
  127. function setFakeMaster( $enabled = true ) {
  128. $this->mFakeMaster = $enabled;
  129. }
  130. /**
  131. * Returns true if this database supports (and uses) cascading deletes
  132. */
  133. function cascadingDeletes() {
  134. return false;
  135. }
  136. /**
  137. * Returns true if this database supports (and uses) triggers (e.g. on the page table)
  138. */
  139. function cleanupTriggers() {
  140. return false;
  141. }
  142. /**
  143. * Returns true if this database is strict about what can be put into an IP field.
  144. * Specifically, it uses a NULL value instead of an empty string.
  145. */
  146. function strictIPs() {
  147. return false;
  148. }
  149. /**
  150. * Returns true if this database uses timestamps rather than integers
  151. */
  152. function realTimestamps() {
  153. return false;
  154. }
  155. /**
  156. * Returns true if this database does an implicit sort when doing GROUP BY
  157. */
  158. function implicitGroupby() {
  159. return true;
  160. }
  161. /**
  162. * Returns true if this database does an implicit order by when the column has an index
  163. * For example: SELECT page_title FROM page LIMIT 1
  164. */
  165. function implicitOrderby() {
  166. return true;
  167. }
  168. /**
  169. * Returns true if this database can do a native search on IP columns
  170. * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32';
  171. */
  172. function searchableIPs() {
  173. return false;
  174. }
  175. /**
  176. * Returns true if this database can use functional indexes
  177. */
  178. function functionalIndexes() {
  179. return false;
  180. }
  181. /**
  182. * Return the last query that went through Database::query()
  183. * @return String
  184. */
  185. function lastQuery() { return $this->mLastQuery; }
  186. /**
  187. * Returns true if the connection may have been used for write queries.
  188. * Should return true if unsure.
  189. */
  190. function doneWrites() { return $this->mDoneWrites; }
  191. /**
  192. * Is a connection to the database open?
  193. * @return Boolean
  194. */
  195. function isOpen() { return $this->mOpened; }
  196. function setFlag( $flag ) {
  197. $this->mFlags |= $flag;
  198. }
  199. function clearFlag( $flag ) {
  200. $this->mFlags &= ~$flag;
  201. }
  202. function getFlag( $flag ) {
  203. return !!($this->mFlags & $flag);
  204. }
  205. /**
  206. * General read-only accessor
  207. */
  208. function getProperty( $name ) {
  209. return $this->$name;
  210. }
  211. function getWikiID() {
  212. if( $this->mTablePrefix ) {
  213. return "{$this->mDBname}-{$this->mTablePrefix}";
  214. } else {
  215. return $this->mDBname;
  216. }
  217. }
  218. #------------------------------------------------------------------------------
  219. # Other functions
  220. #------------------------------------------------------------------------------
  221. /**
  222. * Constructor.
  223. * @param $server String: database server host
  224. * @param $user String: database user name
  225. * @param $password String: database user password
  226. * @param $dbName String: database name
  227. * @param $failFunction
  228. * @param $flags
  229. * @param $tablePrefix String: database table prefixes. By default use the prefix gave in LocalSettings.php
  230. */
  231. function __construct( $server = false, $user = false, $password = false, $dbName = false,
  232. $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) {
  233. global $wgOut, $wgDBprefix, $wgCommandLineMode;
  234. # Can't get a reference if it hasn't been set yet
  235. if ( !isset( $wgOut ) ) {
  236. $wgOut = NULL;
  237. }
  238. $this->mFailFunction = $failFunction;
  239. $this->mFlags = $flags;
  240. if ( $this->mFlags & DBO_DEFAULT ) {
  241. if ( $wgCommandLineMode ) {
  242. $this->mFlags &= ~DBO_TRX;
  243. } else {
  244. $this->mFlags |= DBO_TRX;
  245. }
  246. }
  247. /*
  248. // Faster read-only access
  249. if ( wfReadOnly() ) {
  250. $this->mFlags |= DBO_PERSISTENT;
  251. $this->mFlags &= ~DBO_TRX;
  252. }*/
  253. /** Get the default table prefix*/
  254. if ( $tablePrefix == 'get from global' ) {
  255. $this->mTablePrefix = $wgDBprefix;
  256. } else {
  257. $this->mTablePrefix = $tablePrefix;
  258. }
  259. if ( $server ) {
  260. $this->open( $server, $user, $password, $dbName );
  261. }
  262. }
  263. /**
  264. * Same as new Database( ... ), kept for backward compatibility
  265. * @param $server String: database server host
  266. * @param $user String: database user name
  267. * @param $password String: database user password
  268. * @param $dbName String: database name
  269. * @param failFunction
  270. * @param $flags
  271. */
  272. static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0 )
  273. {
  274. return new Database( $server, $user, $password, $dbName, $failFunction, $flags );
  275. }
  276. /**
  277. * Usually aborts on failure
  278. * If the failFunction is set to a non-zero integer, returns success
  279. * @param $server String: database server host
  280. * @param $user String: database user name
  281. * @param $password String: database user password
  282. * @param $dbName String: database name
  283. */
  284. function open( $server, $user, $password, $dbName ) {
  285. global $wgAllDBsAreLocalhost;
  286. wfProfileIn( __METHOD__ );
  287. # Test for missing mysql.so
  288. # First try to load it
  289. if (!@extension_loaded('mysql')) {
  290. @dl('mysql.so');
  291. }
  292. # Fail now
  293. # Otherwise we get a suppressed fatal error, which is very hard to track down
  294. if ( !function_exists( 'mysql_connect' ) ) {
  295. throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
  296. }
  297. # Debugging hack -- fake cluster
  298. if ( $wgAllDBsAreLocalhost ) {
  299. $realServer = 'localhost';
  300. } else {
  301. $realServer = $server;
  302. }
  303. $this->close();
  304. $this->mServer = $server;
  305. $this->mUser = $user;
  306. $this->mPassword = $password;
  307. $this->mDBname = $dbName;
  308. $success = false;
  309. wfProfileIn("dbconnect-$server");
  310. # The kernel's default SYN retransmission period is far too slow for us,
  311. # so we use a short timeout plus a manual retry. Retrying means that a small
  312. # but finite rate of SYN packet loss won't cause user-visible errors.
  313. $this->mConn = false;
  314. if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
  315. $numAttempts = 2;
  316. } else {
  317. $numAttempts = 1;
  318. }
  319. $this->installErrorHandler();
  320. for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) {
  321. if ( $i > 1 ) {
  322. usleep( 1000 );
  323. }
  324. if ( $this->mFlags & DBO_PERSISTENT ) {
  325. $this->mConn = mysql_pconnect( $realServer, $user, $password );
  326. } else {
  327. # Create a new connection...
  328. $this->mConn = mysql_connect( $realServer, $user, $password, true );
  329. }
  330. if ($this->mConn === false) {
  331. #$iplus = $i + 1;
  332. #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
  333. }
  334. }
  335. $phpError = $this->restoreErrorHandler();
  336. # Always log connection errors
  337. if ( !$this->mConn ) {
  338. $error = $this->lastError();
  339. if ( !$error ) {
  340. $error = $phpError;
  341. }
  342. wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
  343. wfDebug( "DB connection error\n" );
  344. wfDebug( "Server: $server, User: $user, Password: " .
  345. substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" );
  346. $success = false;
  347. }
  348. wfProfileOut("dbconnect-$server");
  349. if ( $dbName != '' && $this->mConn !== false ) {
  350. $success = @/**/mysql_select_db( $dbName, $this->mConn );
  351. if ( !$success ) {
  352. $error = "Error selecting database $dbName on server {$this->mServer} " .
  353. "from client host " . wfHostname() . "\n";
  354. wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
  355. wfDebug( $error );
  356. }
  357. } else {
  358. # Delay USE query
  359. $success = (bool)$this->mConn;
  360. }
  361. if ( $success ) {
  362. $version = $this->getServerVersion();
  363. if ( version_compare( $version, '4.1' ) >= 0 ) {
  364. // Tell the server we're communicating with it in UTF-8.
  365. // This may engage various charset conversions.
  366. global $wgDBmysql5;
  367. if( $wgDBmysql5 ) {
  368. $this->query( 'SET NAMES utf8', __METHOD__ );
  369. }
  370. // Turn off strict mode
  371. $this->query( "SET sql_mode = ''", __METHOD__ );
  372. }
  373. // Turn off strict mode if it is on
  374. } else {
  375. $this->reportConnectionError( $phpError );
  376. }
  377. $this->mOpened = $success;
  378. wfProfileOut( __METHOD__ );
  379. return $success;
  380. }
  381. protected function installErrorHandler() {
  382. $this->mPHPError = false;
  383. $this->htmlErrors = ini_set( 'html_errors', '0' );
  384. set_error_handler( array( $this, 'connectionErrorHandler' ) );
  385. }
  386. protected function restoreErrorHandler() {
  387. restore_error_handler();
  388. if ( $this->htmlErrors !== false ) {
  389. ini_set( 'html_errors', $this->htmlErrors );
  390. }
  391. if ( $this->mPHPError ) {
  392. $error = preg_replace( '!\[<a.*</a>\]!', '', $this->mPHPError );
  393. $error = preg_replace( '!^.*?:(.*)$!', '$1', $error );
  394. return $error;
  395. } else {
  396. return false;
  397. }
  398. }
  399. protected function connectionErrorHandler( $errno, $errstr ) {
  400. $this->mPHPError = $errstr;
  401. }
  402. /**
  403. * Closes a database connection.
  404. * if it is open : commits any open transactions
  405. *
  406. * @return Bool operation success. true if already closed.
  407. */
  408. function close()
  409. {
  410. $this->mOpened = false;
  411. if ( $this->mConn ) {
  412. if ( $this->trxLevel() ) {
  413. $this->immediateCommit();
  414. }
  415. return mysql_close( $this->mConn );
  416. } else {
  417. return true;
  418. }
  419. }
  420. /**
  421. * @param $error String: fallback error message, used if none is given by MySQL
  422. */
  423. function reportConnectionError( $error = 'Unknown error' ) {
  424. $myError = $this->lastError();
  425. if ( $myError ) {
  426. $error = $myError;
  427. }
  428. if ( $this->mFailFunction ) {
  429. # Legacy error handling method
  430. if ( !is_int( $this->mFailFunction ) ) {
  431. $ff = $this->mFailFunction;
  432. $ff( $this, $error );
  433. }
  434. } else {
  435. # New method
  436. throw new DBConnectionError( $this, $error );
  437. }
  438. }
  439. /**
  440. * Determine whether a query writes to the DB.
  441. * Should return true if unsure.
  442. */
  443. function isWriteQuery( $sql ) {
  444. return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW)\b/i', $sql );
  445. }
  446. /**
  447. * Usually aborts on failure. If errors are explicitly ignored, returns success.
  448. *
  449. * @param $sql String: SQL query
  450. * @param $fname String: Name of the calling function, for profiling/SHOW PROCESSLIST
  451. * comment (you can use __METHOD__ or add some extra info)
  452. * @param $tempIgnore Boolean: Whether to avoid throwing an exception on errors...
  453. * maybe best to catch the exception instead?
  454. * @return true for a successful write query, ResultWrapper object for a successful read query,
  455. * or false on failure if $tempIgnore set
  456. * @throws DBQueryError Thrown when the database returns an error of any kind
  457. */
  458. public function query( $sql, $fname = '', $tempIgnore = false ) {
  459. global $wgProfiler;
  460. $isMaster = !is_null( $this->getLBInfo( 'master' ) );
  461. if ( isset( $wgProfiler ) ) {
  462. # generalizeSQL will probably cut down the query to reasonable
  463. # logging size most of the time. The substr is really just a sanity check.
  464. # Who's been wasting my precious column space? -- TS
  465. #$profName = 'query: ' . $fname . ' ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
  466. if ( $isMaster ) {
  467. $queryProf = 'query-m: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
  468. $totalProf = 'Database::query-master';
  469. } else {
  470. $queryProf = 'query: ' . substr( Database::generalizeSQL( $sql ), 0, 255 );
  471. $totalProf = 'Database::query';
  472. }
  473. wfProfileIn( $totalProf );
  474. wfProfileIn( $queryProf );
  475. }
  476. $this->mLastQuery = $sql;
  477. if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) {
  478. // Set a flag indicating that writes have been done
  479. wfDebug( __METHOD__.": Writes done: $sql\n" );
  480. $this->mDoneWrites = true;
  481. }
  482. # Add a comment for easy SHOW PROCESSLIST interpretation
  483. #if ( $fname ) {
  484. global $wgUser;
  485. if ( is_object( $wgUser ) && !($wgUser instanceof StubObject) ) {
  486. $userName = $wgUser->getName();
  487. if ( mb_strlen( $userName ) > 15 ) {
  488. $userName = mb_substr( $userName, 0, 15 ) . '...';
  489. }
  490. $userName = str_replace( '/', '', $userName );
  491. } else {
  492. $userName = '';
  493. }
  494. $commentedSql = preg_replace('/\s/', " /* $fname $userName */ ", $sql, 1);
  495. #} else {
  496. # $commentedSql = $sql;
  497. #}
  498. # If DBO_TRX is set, start a transaction
  499. if ( ( $this->mFlags & DBO_TRX ) && !$this->trxLevel() &&
  500. $sql != 'BEGIN' && $sql != 'COMMIT' && $sql != 'ROLLBACK') {
  501. // avoid establishing transactions for SHOW and SET statements too -
  502. // that would delay transaction initializations to once connection
  503. // is really used by application
  504. $sqlstart = substr($sql,0,10); // very much worth it, benchmark certified(tm)
  505. if (strpos($sqlstart,"SHOW ")!==0 and strpos($sqlstart,"SET ")!==0)
  506. $this->begin();
  507. }
  508. if ( $this->debug() ) {
  509. $sqlx = substr( $commentedSql, 0, 500 );
  510. $sqlx = strtr( $sqlx, "\t\n", ' ' );
  511. if ( $isMaster ) {
  512. wfDebug( "SQL-master: $sqlx\n" );
  513. } else {
  514. wfDebug( "SQL: $sqlx\n" );
  515. }
  516. }
  517. if ( istainted( $sql ) & TC_MYSQL ) {
  518. throw new MWException( 'Tainted query found' );
  519. }
  520. # Do the query and handle errors
  521. $ret = $this->doQuery( $commentedSql );
  522. # Try reconnecting if the connection was lost
  523. if ( false === $ret && $this->wasErrorReissuable() ) {
  524. # Transaction is gone, like it or not
  525. $this->mTrxLevel = 0;
  526. wfDebug( "Connection lost, reconnecting...\n" );
  527. if ( $this->ping() ) {
  528. wfDebug( "Reconnected\n" );
  529. $sqlx = substr( $commentedSql, 0, 500 );
  530. $sqlx = strtr( $sqlx, "\t\n", ' ' );
  531. global $wgRequestTime;
  532. $elapsed = round( microtime(true) - $wgRequestTime, 3 );
  533. wfLogDBError( "Connection lost and reconnected after {$elapsed}s, query: $sqlx\n" );
  534. $ret = $this->doQuery( $commentedSql );
  535. } else {
  536. wfDebug( "Failed\n" );
  537. }
  538. }
  539. if ( false === $ret ) {
  540. $this->reportQueryError( $this->lastError(), $this->lastErrno(), $sql, $fname, $tempIgnore );
  541. }
  542. if ( isset( $wgProfiler ) ) {
  543. wfProfileOut( $queryProf );
  544. wfProfileOut( $totalProf );
  545. }
  546. return $this->resultObject( $ret );
  547. }
  548. /**
  549. * The DBMS-dependent part of query()
  550. * @param $sql String: SQL query.
  551. * @return Result object to feed to fetchObject, fetchRow, ...; or false on failure
  552. * @private
  553. */
  554. /*private*/ function doQuery( $sql ) {
  555. if( $this->bufferResults() ) {
  556. $ret = mysql_query( $sql, $this->mConn );
  557. } else {
  558. $ret = mysql_unbuffered_query( $sql, $this->mConn );
  559. }
  560. return $ret;
  561. }
  562. /**
  563. * @param $error String
  564. * @param $errno Integer
  565. * @param $sql String
  566. * @param $fname String
  567. * @param $tempIgnore Boolean
  568. */
  569. function reportQueryError( $error, $errno, $sql, $fname, $tempIgnore = false ) {
  570. global $wgCommandLineMode;
  571. # Ignore errors during error handling to avoid infinite recursion
  572. $ignore = $this->ignoreErrors( true );
  573. ++$this->mErrorCount;
  574. if( $ignore || $tempIgnore ) {
  575. wfDebug("SQL ERROR (ignored): $error\n");
  576. $this->ignoreErrors( $ignore );
  577. } else {
  578. $sql1line = str_replace( "\n", "\\n", $sql );
  579. wfLogDBError("$fname\t{$this->mServer}\t$errno\t$error\t$sql1line\n");
  580. wfDebug("SQL ERROR: " . $error . "\n");
  581. throw new DBQueryError( $this, $error, $errno, $sql, $fname );
  582. }
  583. }
  584. /**
  585. * Intended to be compatible with the PEAR::DB wrapper functions.
  586. * http://pear.php.net/manual/en/package.database.db.intro-execute.php
  587. *
  588. * ? = scalar value, quoted as necessary
  589. * ! = raw SQL bit (a function for instance)
  590. * & = filename; reads the file and inserts as a blob
  591. * (we don't use this though...)
  592. */
  593. function prepare( $sql, $func = 'Database::prepare' ) {
  594. /* MySQL doesn't support prepared statements (yet), so just
  595. pack up the query for reference. We'll manually replace
  596. the bits later. */
  597. return array( 'query' => $sql, 'func' => $func );
  598. }
  599. function freePrepared( $prepared ) {
  600. /* No-op for MySQL */
  601. }
  602. /**
  603. * Execute a prepared query with the various arguments
  604. * @param $prepared String: the prepared sql
  605. * @param $args Mixed: Either an array here, or put scalars as varargs
  606. */
  607. function execute( $prepared, $args = null ) {
  608. if( !is_array( $args ) ) {
  609. # Pull the var args
  610. $args = func_get_args();
  611. array_shift( $args );
  612. }
  613. $sql = $this->fillPrepared( $prepared['query'], $args );
  614. return $this->query( $sql, $prepared['func'] );
  615. }
  616. /**
  617. * Prepare & execute an SQL statement, quoting and inserting arguments
  618. * in the appropriate places.
  619. * @param $query String
  620. * @param $args ...
  621. */
  622. function safeQuery( $query, $args = null ) {
  623. $prepared = $this->prepare( $query, 'Database::safeQuery' );
  624. if( !is_array( $args ) ) {
  625. # Pull the var args
  626. $args = func_get_args();
  627. array_shift( $args );
  628. }
  629. $retval = $this->execute( $prepared, $args );
  630. $this->freePrepared( $prepared );
  631. return $retval;
  632. }
  633. /**
  634. * For faking prepared SQL statements on DBs that don't support
  635. * it directly.
  636. * @param $preparedQuery String: a 'preparable' SQL statement
  637. * @param $args Array of arguments to fill it with
  638. * @return string executable SQL
  639. */
  640. function fillPrepared( $preparedQuery, $args ) {
  641. reset( $args );
  642. $this->preparedArgs =& $args;
  643. return preg_replace_callback( '/(\\\\[?!&]|[?!&])/',
  644. array( &$this, 'fillPreparedArg' ), $preparedQuery );
  645. }
  646. /**
  647. * preg_callback func for fillPrepared()
  648. * The arguments should be in $this->preparedArgs and must not be touched
  649. * while we're doing this.
  650. *
  651. * @param $matches Array
  652. * @return String
  653. * @private
  654. */
  655. function fillPreparedArg( $matches ) {
  656. switch( $matches[1] ) {
  657. case '\\?': return '?';
  658. case '\\!': return '!';
  659. case '\\&': return '&';
  660. }
  661. list( /* $n */ , $arg ) = each( $this->preparedArgs );
  662. switch( $matches[1] ) {
  663. case '?': return $this->addQuotes( $arg );
  664. case '!': return $arg;
  665. case '&':
  666. # return $this->addQuotes( file_get_contents( $arg ) );
  667. throw new DBUnexpectedError( $this, '& mode is not implemented. If it\'s really needed, uncomment the line above.' );
  668. default:
  669. throw new DBUnexpectedError( $this, 'Received invalid match. This should never happen!' );
  670. }
  671. }
  672. /**
  673. * Free a result object
  674. * @param $res Mixed: A SQL result
  675. */
  676. function freeResult( $res ) {
  677. if ( $res instanceof ResultWrapper ) {
  678. $res = $res->result;
  679. }
  680. if ( !@/**/mysql_free_result( $res ) ) {
  681. throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
  682. }
  683. }
  684. /**
  685. * Fetch the next row from the given result object, in object form.
  686. * Fields can be retrieved with $row->fieldname, with fields acting like
  687. * member variables.
  688. *
  689. * @param $res SQL result object as returned from Database::query(), etc.
  690. * @return MySQL row object
  691. * @throws DBUnexpectedError Thrown if the database returns an error
  692. */
  693. function fetchObject( $res ) {
  694. if ( $res instanceof ResultWrapper ) {
  695. $res = $res->result;
  696. }
  697. @/**/$row = mysql_fetch_object( $res );
  698. if( $this->lastErrno() ) {
  699. throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
  700. }
  701. return $row;
  702. }
  703. /**
  704. * Fetch the next row from the given result object, in associative array
  705. * form. Fields are retrieved with $row['fieldname'].
  706. *
  707. * @param $res SQL result object as returned from Database::query(), etc.
  708. * @return MySQL row object
  709. * @throws DBUnexpectedError Thrown if the database returns an error
  710. */
  711. function fetchRow( $res ) {
  712. if ( $res instanceof ResultWrapper ) {
  713. $res = $res->result;
  714. }
  715. @/**/$row = mysql_fetch_array( $res );
  716. if ( $this->lastErrno() ) {
  717. throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
  718. }
  719. return $row;
  720. }
  721. /**
  722. * Get the number of rows in a result object
  723. * @param $res Mixed: A SQL result
  724. */
  725. function numRows( $res ) {
  726. if ( $res instanceof ResultWrapper ) {
  727. $res = $res->result;
  728. }
  729. @/**/$n = mysql_num_rows( $res );
  730. if( $this->lastErrno() ) {
  731. throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
  732. }
  733. return $n;
  734. }
  735. /**
  736. * Get the number of fields in a result object
  737. * See documentation for mysql_num_fields()
  738. * @param $res Mixed: A SQL result
  739. */
  740. function numFields( $res ) {
  741. if ( $res instanceof ResultWrapper ) {
  742. $res = $res->result;
  743. }
  744. return mysql_num_fields( $res );
  745. }
  746. /**
  747. * Get a field name in a result object
  748. * See documentation for mysql_field_name():
  749. * http://www.php.net/mysql_field_name
  750. * @param $res Mixed: A SQL result
  751. * @param $n Integer
  752. */
  753. function fieldName( $res, $n ) {
  754. if ( $res instanceof ResultWrapper ) {
  755. $res = $res->result;
  756. }
  757. return mysql_field_name( $res, $n );
  758. }
  759. /**
  760. * Get the inserted value of an auto-increment row
  761. *
  762. * The value inserted should be fetched from nextSequenceValue()
  763. *
  764. * Example:
  765. * $id = $dbw->nextSequenceValue('page_page_id_seq');
  766. * $dbw->insert('page',array('page_id' => $id));
  767. * $id = $dbw->insertId();
  768. */
  769. function insertId() { return mysql_insert_id( $this->mConn ); }
  770. /**
  771. * Change the position of the cursor in a result object
  772. * See mysql_data_seek()
  773. * @param $res Mixed: A SQL result
  774. * @param $row Mixed: Either MySQL row or ResultWrapper
  775. */
  776. function dataSeek( $res, $row ) {
  777. if ( $res instanceof ResultWrapper ) {
  778. $res = $res->result;
  779. }
  780. return mysql_data_seek( $res, $row );
  781. }
  782. /**
  783. * Get the last error number
  784. * See mysql_errno()
  785. */
  786. function lastErrno() {
  787. if ( $this->mConn ) {
  788. return mysql_errno( $this->mConn );
  789. } else {
  790. return mysql_errno();
  791. }
  792. }
  793. /**
  794. * Get a description of the last error
  795. * See mysql_error() for more details
  796. */
  797. function lastError() {
  798. if ( $this->mConn ) {
  799. # Even if it's non-zero, it can still be invalid
  800. wfSuppressWarnings();
  801. $error = mysql_error( $this->mConn );
  802. if ( !$error ) {
  803. $error = mysql_error();
  804. }
  805. wfRestoreWarnings();
  806. } else {
  807. $error = mysql_error();
  808. }
  809. if( $error ) {
  810. $error .= ' (' . $this->mServer . ')';
  811. }
  812. return $error;
  813. }
  814. /**
  815. * Get the number of rows affected by the last write query
  816. * See mysql_affected_rows() for more details
  817. */
  818. function affectedRows() { return mysql_affected_rows( $this->mConn ); }
  819. /**
  820. * Simple UPDATE wrapper
  821. * Usually aborts on failure
  822. * If errors are explicitly ignored, returns success
  823. *
  824. * This function exists for historical reasons, Database::update() has a more standard
  825. * calling convention and feature set
  826. */
  827. function set( $table, $var, $value, $cond, $fname = 'Database::set' ) {
  828. $table = $this->tableName( $table );
  829. $sql = "UPDATE $table SET $var = '" .
  830. $this->strencode( $value ) . "' WHERE ($cond)";
  831. return (bool)$this->query( $sql, $fname );
  832. }
  833. /**
  834. * Simple SELECT wrapper, returns a single field, input must be encoded
  835. * Usually aborts on failure
  836. * If errors are explicitly ignored, returns FALSE on failure
  837. */
  838. function selectField( $table, $var, $cond='', $fname = 'Database::selectField', $options = array() ) {
  839. if ( !is_array( $options ) ) {
  840. $options = array( $options );
  841. }
  842. $options['LIMIT'] = 1;
  843. $res = $this->select( $table, $var, $cond, $fname, $options );
  844. if ( $res === false || !$this->numRows( $res ) ) {
  845. return false;
  846. }
  847. $row = $this->fetchRow( $res );
  848. if ( $row !== false ) {
  849. $this->freeResult( $res );
  850. return reset( $row );
  851. } else {
  852. return false;
  853. }
  854. }
  855. /**
  856. * Returns an optional USE INDEX clause to go after the table, and a
  857. * string to go at the end of the query
  858. *
  859. * @private
  860. *
  861. * @param $options Array: associative array of options to be turned into
  862. * an SQL query, valid keys are listed in the function.
  863. * @return Array
  864. */
  865. function makeSelectOptions( $options ) {
  866. $preLimitTail = $postLimitTail = '';
  867. $startOpts = '';
  868. $noKeyOptions = array();
  869. foreach ( $options as $key => $option ) {
  870. if ( is_numeric( $key ) ) {
  871. $noKeyOptions[$option] = true;
  872. }
  873. }
  874. if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}";
  875. if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}";
  876. if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}";
  877. //if (isset($options['LIMIT'])) {
  878. // $tailOpts .= $this->limitResult('', $options['LIMIT'],
  879. // isset($options['OFFSET']) ? $options['OFFSET']
  880. // : false);
  881. //}
  882. if ( isset( $noKeyOptions['FOR UPDATE'] ) ) $postLimitTail .= ' FOR UPDATE';
  883. if ( isset( $noKeyOptions['LOCK IN SHARE MODE'] ) ) $postLimitTail .= ' LOCK IN SHARE MODE';
  884. if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT';
  885. # Various MySQL extensions
  886. if ( isset( $noKeyOptions['STRAIGHT_JOIN'] ) ) $startOpts .= ' /*! STRAIGHT_JOIN */';
  887. if ( isset( $noKeyOptions['HIGH_PRIORITY'] ) ) $startOpts .= ' HIGH_PRIORITY';
  888. if ( isset( $noKeyOptions['SQL_BIG_RESULT'] ) ) $startOpts .= ' SQL_BIG_RESULT';
  889. if ( isset( $noKeyOptions['SQL_BUFFER_RESULT'] ) ) $startOpts .= ' SQL_BUFFER_RESULT';
  890. if ( isset( $noKeyOptions['SQL_SMALL_RESULT'] ) ) $startOpts .= ' SQL_SMALL_RESULT';
  891. if ( isset( $noKeyOptions['SQL_CALC_FOUND_ROWS'] ) ) $startOpts .= ' SQL_CALC_FOUND_ROWS';
  892. if ( isset( $noKeyOptions['SQL_CACHE'] ) ) $startOpts .= ' SQL_CACHE';
  893. if ( isset( $noKeyOptions['SQL_NO_CACHE'] ) ) $startOpts .= ' SQL_NO_CACHE';
  894. if ( isset( $options['USE INDEX'] ) && ! is_array( $options['USE INDEX'] ) ) {
  895. $useIndex = $this->useIndexClause( $options['USE INDEX'] );
  896. } else {
  897. $useIndex = '';
  898. }
  899. return array( $startOpts, $useIndex, $preLimitTail, $postLimitTail );
  900. }
  901. /**
  902. * SELECT wrapper
  903. *
  904. * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
  905. * @param $vars Mixed: Array or string, field name(s) to be retrieved
  906. * @param $conds Mixed: Array or string, condition(s) for WHERE
  907. * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
  908. * @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
  909. * see Database::makeSelectOptions code for list of supported stuff
  910. * @param $join_conds Array: Associative array of table join conditions (optional)
  911. * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
  912. * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure
  913. */
  914. function select( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() )
  915. {
  916. $sql = $this->selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
  917. return $this->query( $sql, $fname );
  918. }
  919. /**
  920. * SELECT wrapper
  921. *
  922. * @param $table Mixed: Array or string, table name(s) (prefix auto-added)
  923. * @param $vars Mixed: Array or string, field name(s) to be retrieved
  924. * @param $conds Mixed: Array or string, condition(s) for WHERE
  925. * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
  926. * @param $options Array: Associative array of options (e.g. array('GROUP BY' => 'page_title')),
  927. * see Database::makeSelectOptions code for list of supported stuff
  928. * @param $join_conds Array: Associative array of table join conditions (optional)
  929. * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') )
  930. * @return string, the SQL text
  931. */
  932. function selectSQLText( $table, $vars, $conds='', $fname = 'Database::select', $options = array(), $join_conds = array() ) {
  933. if( is_array( $vars ) ) {
  934. $vars = implode( ',', $vars );
  935. }
  936. if( !is_array( $options ) ) {
  937. $options = array( $options );
  938. }
  939. if( is_array( $table ) ) {
  940. if ( !empty($join_conds) || ( isset( $options['USE INDEX'] ) && is_array( @$options['USE INDEX'] ) ) )
  941. $from = ' FROM ' . $this->tableNamesWithUseIndexOrJOIN( $table, @$options['USE INDEX'], $join_conds );
  942. else
  943. $from = ' FROM ' . implode( ',', array_map( array( &$this, 'tableName' ), $table ) );
  944. } elseif ($table!='') {
  945. if ($table{0}==' ') {
  946. $from = ' FROM ' . $table;
  947. } else {
  948. $from = ' FROM ' . $this->tableName( $table );
  949. }
  950. } else {
  951. $from = '';
  952. }
  953. list( $startOpts, $useIndex, $preLimitTail, $postLimitTail ) = $this->makeSelectOptions( $options );
  954. if( !empty( $conds ) ) {
  955. if ( is_array( $conds ) ) {
  956. $conds = $this->makeList( $conds, LIST_AND );
  957. }
  958. $sql = "SELECT $startOpts $vars $from $useIndex WHERE $conds $preLimitTail";
  959. } else {
  960. $sql = "SELECT $startOpts $vars $from $useIndex $preLimitTail";
  961. }
  962. if (isset($options['LIMIT']))
  963. $sql = $this->limitResult($sql, $options['LIMIT'],
  964. isset($options['OFFSET']) ? $options['OFFSET'] : false);
  965. $sql = "$sql $postLimitTail";
  966. if (isset($options['EXPLAIN'])) {
  967. $sql = 'EXPLAIN ' . $sql;
  968. }
  969. return $sql;
  970. }
  971. /**
  972. * Single row SELECT wrapper
  973. * Aborts or returns FALSE on error
  974. *
  975. * @param $table String: table name
  976. * @param $vars String: the selected variables
  977. * @param $conds Array: a condition map, terms are ANDed together.
  978. * Items with numeric keys are taken to be literal conditions
  979. * Takes an array of selected variables, and a condition map, which is ANDed
  980. * e.g: selectRow( "page", array( "page_id" ), array( "page_namespace" =>
  981. * NS_MAIN, "page_title" => "Astronomy" ) ) would return an object where
  982. * $obj- >page_id is the ID of the Astronomy article
  983. * @param $fname String: Calling functio name
  984. * @param $options Array
  985. * @param $join_conds Array
  986. *
  987. * @todo migrate documentation to phpdocumentor format
  988. */
  989. function selectRow( $table, $vars, $conds, $fname = 'Database::selectRow', $options = array(), $join_conds = array() ) {
  990. $options['LIMIT'] = 1;
  991. $res = $this->select( $table, $vars, $conds, $fname, $options, $join_conds );
  992. if ( $res === false )
  993. return false;
  994. if ( !$this->numRows($res) ) {
  995. $this->freeResult($res);
  996. return false;
  997. }
  998. $obj = $this->fetchObject( $res );
  999. $this->freeResult( $res );
  1000. return $obj;
  1001. }
  1002. /**
  1003. * Estimate rows in dataset
  1004. * Returns estimated count, based on EXPLAIN output
  1005. * Takes same arguments as Database::select()
  1006. */
  1007. function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) {
  1008. $options['EXPLAIN']=true;
  1009. $res = $this->select ($table, $vars, $conds, $fname, $options );
  1010. if ( $res === false )
  1011. return false;
  1012. if (!$this->numRows($res)) {
  1013. $this->freeResult($res);
  1014. return 0;
  1015. }
  1016. $rows=1;
  1017. while( $plan = $this->fetchObject( $res ) ) {
  1018. $rows *= ($plan->rows > 0)?$plan->rows:1; // avoid resetting to zero
  1019. }
  1020. $this->freeResult($res);
  1021. return $rows;
  1022. }
  1023. /**
  1024. * Removes most variables from an SQL query and replaces them with X or N for numbers.
  1025. * It's only slightly flawed. Don't use for anything important.
  1026. *
  1027. * @param $sql String: A SQL Query
  1028. */
  1029. static function generalizeSQL( $sql ) {
  1030. # This does the same as the regexp below would do, but in such a way
  1031. # as to avoid crashing php on some large strings.
  1032. # $sql = preg_replace ( "/'([^\\\\']|\\\\.)*'|\"([^\\\\\"]|\\\\.)*\"/", "'X'", $sql);
  1033. $sql = str_replace ( "\\\\", '', $sql);
  1034. $sql = str_replace ( "\\'", '', $sql);
  1035. $sql = str_replace ( "\\\"", '', $sql);
  1036. $sql = preg_replace ("/'.*'/s", "'X'", $sql);
  1037. $sql = preg_replace ('/".*"/s', "'X'", $sql);
  1038. # All newlines, tabs, etc replaced by single space
  1039. $sql = preg_replace ( '/\s+/', ' ', $sql);
  1040. # All numbers => N
  1041. $sql = preg_replace ('/-?[0-9]+/s', 'N', $sql);
  1042. return $sql;
  1043. }
  1044. /**
  1045. * Determines whether a field exists in a table
  1046. * Usually aborts on failure
  1047. * If errors are explicitly ignored, returns NULL on failure
  1048. */
  1049. function fieldExists( $table, $field, $fname = 'Database::fieldExists' ) {
  1050. $table = $this->tableName( $table );
  1051. $res = $this->query( 'DESCRIBE '.$table, $fname );
  1052. if ( !$res ) {
  1053. return NULL;
  1054. }
  1055. $found = false;
  1056. while ( $row = $this->fetchObject( $res ) ) {
  1057. if ( $row->Field == $field ) {
  1058. $found = true;
  1059. break;
  1060. }
  1061. }
  1062. return $found;
  1063. }
  1064. /**
  1065. * Determines whether an index exists
  1066. * Usually aborts on failure
  1067. * If errors are explicitly ignored, returns NULL on failure
  1068. */
  1069. function indexExists( $table, $index, $fname = 'Database::indexExists' ) {
  1070. $info = $this->indexInfo( $table, $index, $fname );
  1071. if ( is_null( $info ) ) {
  1072. return NULL;
  1073. } else {
  1074. return $info !== false;
  1075. }
  1076. }
  1077. /**
  1078. * Get information about an index into an object
  1079. * Returns false if the index does not exist
  1080. */
  1081. function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) {
  1082. # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
  1083. # SHOW INDEX should work for 3.x and up:
  1084. # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
  1085. $table = $this->tableName( $table );
  1086. $index = $this->indexName( $index );
  1087. $sql = 'SHOW INDEX FROM '.$table;
  1088. $res = $this->query( $sql, $fname );
  1089. if ( !$res ) {
  1090. return NULL;
  1091. }
  1092. $result = array();
  1093. while ( $row = $this->fetchObject( $res ) ) {
  1094. if ( $row->Key_name == $index ) {
  1095. $result[] = $row;
  1096. }
  1097. }
  1098. $this->freeResult($res);
  1099. return empty($result) ? false : $result;
  1100. }
  1101. /**
  1102. * Query whether a given table exists
  1103. */
  1104. function tableExists( $table ) {
  1105. $table = $this->tableName( $table );
  1106. $old = $this->ignoreErrors( true );
  1107. $res = $this->query( "SELECT 1 FROM $table LIMIT 1" );
  1108. $this->ignoreErrors( $old );
  1109. if( $res ) {
  1110. $this->freeResult( $res );
  1111. return true;
  1112. } else {
  1113. return false;
  1114. }
  1115. }
  1116. /**
  1117. * mysql_fetch_field() wrapper
  1118. * Returns false if the field doesn't exist
  1119. *
  1120. * @param $table
  1121. * @param $field
  1122. */
  1123. function fieldInfo( $table, $field ) {
  1124. $table = $this->tableName( $table );
  1125. $res = $this->query( "SELECT * FROM $table LIMIT 1" );
  1126. $n = mysql_num_fields( $res->result );
  1127. for( $i = 0; $i < $n; $i++ ) {
  1128. $meta = mysql_fetch_field( $res->result, $i );
  1129. if( $field == $meta->name ) {
  1130. return new MySQLField($meta);
  1131. }
  1132. }
  1133. return false;
  1134. }
  1135. /**
  1136. * mysql_field_type() wrapper
  1137. */
  1138. function fieldType( $res, $index ) {
  1139. if ( $res instanceof ResultWrapper ) {
  1140. $res = $res->result;
  1141. }
  1142. return mysql_field_type( $res, $index );
  1143. }
  1144. /**
  1145. * Determines if a given index is unique
  1146. */
  1147. function indexUnique( $table, $index ) {
  1148. $indexInfo = $this->indexInfo( $table, $index );
  1149. if ( !$indexInfo ) {
  1150. return NULL;
  1151. }
  1152. return !$indexInfo[0]->Non_unique;
  1153. }
  1154. /**
  1155. * INSERT wrapper, inserts an array into a table
  1156. *
  1157. * $a may be a single associative array, or an array of these with numeric keys, for
  1158. * multi-row insert.
  1159. *
  1160. * Usually aborts on failure
  1161. * If errors are explicitly ignored, returns success
  1162. */
  1163. function insert( $table, $a, $fname = 'Database::insert', $options = array() ) {
  1164. # No rows to insert, easy just return now
  1165. if ( !count( $a ) ) {
  1166. return true;
  1167. }
  1168. $table = $this->tableName( $table );
  1169. if ( !is_array( $options ) ) {
  1170. $options = array( $options );
  1171. }
  1172. if ( isset( $a[0] ) && is_array( $a[0] ) ) {
  1173. $multi = true;
  1174. $keys = array_keys( $a[0] );
  1175. } else {
  1176. $multi = false;
  1177. $keys = array_keys( $a );
  1178. }
  1179. $sql = 'INSERT ' . implode( ' ', $options ) .
  1180. " INTO $table (" . implode( ',', $keys ) . ') VALUES ';
  1181. if ( $multi ) {
  1182. $first = true;
  1183. foreach ( $a as $row ) {
  1184. if ( $first ) {
  1185. $first = false;
  1186. } else {
  1187. $sql .= ',';
  1188. }
  1189. $sql .= '(' . $this->makeList( $row ) . ')';
  1190. }
  1191. } else {
  1192. $sql .= '(' . $this->makeList( $a ) . ')';
  1193. }
  1194. return (bool)$this->query( $sql, $fname );
  1195. }
  1196. /**
  1197. * Make UPDATE options for the Database::update function
  1198. *
  1199. * @private
  1200. * @param $options Array: The options passed to Database::update
  1201. * @return string
  1202. */
  1203. function makeUpdateOptions( $options ) {
  1204. if( !is_array( $options ) ) {
  1205. $options = array( $options );
  1206. }
  1207. $opts = array();
  1208. if ( in_array( 'LOW_PRIORITY', $options ) )
  1209. $opts[] = $this->lowPriorityOption();
  1210. if ( in_array( 'IGNORE', $options ) )
  1211. $opts[] = 'IGNORE';
  1212. return implode(' ', $opts);
  1213. }
  1214. /**
  1215. * UPDATE wrapper, takes a condition array and a SET array
  1216. *
  1217. * @param $table String: The table to UPDATE
  1218. * @param $values Array: An array of values to SET
  1219. * @param $conds Array: An array of conditions (WHERE). Use '*' to update all rows.
  1220. * @param $fname String: The Class::Function calling this function
  1221. * (for the log)
  1222. * @param $options Array: An array of UPDATE options, can be one or
  1223. * more of IGNORE, LOW_PRIORITY
  1224. * @return Boolean
  1225. */
  1226. function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) {
  1227. $table = $this->tableName( $table );
  1228. $opts = $this->makeUpdateOptions( $options );
  1229. $sql = "UPDATE $opts $table SET " . $this->makeList( $values, LIST_SET );
  1230. if ( $conds != '*' ) {
  1231. $sql .= " WHERE " . $this->makeList( $conds, LIST_AND );
  1232. }
  1233. return $this->query( $sql, $fname );
  1234. }
  1235. /**
  1236. * Makes an encoded list of strings from an array
  1237. * $mode:
  1238. * LIST_COMMA - comma separated, no field names
  1239. * LIST_AND - ANDed WHERE clause (without the WHERE)
  1240. * LIST_OR - ORed WHERE clause (without the WHERE)
  1241. * LIST_SET - comma separated with field names, like a SET clause
  1242. * LIST_NAMES - comma separated field names
  1243. */
  1244. function makeList( $a, $mode = LIST_COMMA ) {
  1245. if ( !is_array( $a ) ) {
  1246. throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' );
  1247. }
  1248. $first = true;
  1249. $list = '';
  1250. foreach ( $a as $field => $value ) {
  1251. if ( !$first ) {
  1252. if ( $mode == LIST_AND ) {
  1253. $list .= ' AND ';
  1254. } elseif($mode == LIST_OR) {
  1255. $list .= ' OR ';
  1256. } else {
  1257. $list .= ',';
  1258. }
  1259. } else {
  1260. $first = false;
  1261. }
  1262. if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) {
  1263. $list .= "($value)";
  1264. } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) {
  1265. $list .= "$value";
  1266. } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) {
  1267. if( count( $value ) == 0 ) {
  1268. throw new MWException( __METHOD__.': empty input' );
  1269. } elseif( count( $value ) == 1 ) {
  1270. // Special-case single values, as IN isn't terribly efficient
  1271. // Don't necessarily assume the single key is 0; we don't
  1272. // enforce linear numeric ordering on other arrays here.
  1273. $value = array_values( $value );
  1274. $list .= $field." = ".$this->addQuotes( $value[0] );
  1275. } else {
  1276. $list .= $field." IN (".$this->makeList($value).") ";
  1277. }
  1278. } elseif( $value === null ) {
  1279. if ( $mode == LIST_AND || $mode == LIST_OR ) {
  1280. $list .= "$field IS ";
  1281. } elseif ( $mode == LIST_SET ) {
  1282. $list .= "$field = ";
  1283. }
  1284. $list .= 'NULL';
  1285. } else {
  1286. if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) {
  1287. $list .= "$field = ";
  1288. }
  1289. $list .= $mode == LIST_NAMES ? $value : $this->addQuotes( $value );
  1290. }
  1291. }
  1292. return $list;
  1293. }
  1294. /**
  1295. * Change the current database
  1296. */
  1297. function selectDB( $db ) {
  1298. $this->mDBname = $db;
  1299. return mysql_select_db( $db, $this->mConn );
  1300. }
  1301. /**
  1302. * Get the current DB name
  1303. */
  1304. function getDBname() {
  1305. return $this->mDBname;
  1306. }
  1307. /**
  1308. * Get the server hostname or IP address
  1309. */
  1310. function getServer() {
  1311. return $this->mServer;
  1312. }
  1313. /**
  1314. * Format a table name ready for use in constructing an SQL query
  1315. *
  1316. * This does two important things: it quotes the table names to clean them up,
  1317. * and it adds a table prefix if only given a table name with no quotes.
  1318. *
  1319. * All functions of this object which require a table name call this function
  1320. * themselves. Pass the canonical name to such functions. This is only needed
  1321. * when calling query() directly.
  1322. *
  1323. * @param $name String: database table name
  1324. * @return String: full database name
  1325. */
  1326. function tableName( $name ) {
  1327. global $wgSharedDB, $wgSharedPrefix, $wgSharedTables;
  1328. # Skip the entire process when we have a string quoted on both ends.
  1329. # Note that we check the end so that we will still quote any use of
  1330. # use of `database`.table. But won't break things if someone wants
  1331. # to query a database table with a dot in the name.
  1332. if ( $name[0] == '`' && substr( $name, -1, 1 ) == '`' ) return $name;
  1333. # Lets test for any bits of text that should never show up in a table
  1334. # name. Basically anything like JOIN or ON which are actually part of
  1335. # SQL queries, but may end up inside of the table value to combine
  1336. # sql. Such as how the API is doing.
  1337. # Note that we use a whitespace test rather than a \b test to avoid
  1338. # any remote case where a word like on may be inside of a table name
  1339. # surrounded by symbols which may be considered word breaks.
  1340. if( preg_match( '/(^|\s)(DISTINCT|JOIN|ON|AS)(\s|$)/i', $name ) !== 0 ) return $name;
  1341. # Split database and table into proper variables.
  1342. # We reverse the explode so that database.table and table both output
  1343. # the correct table.
  1344. $dbDetails = array_reverse( explode( '.', $name, 2 ) );
  1345. if( isset( $dbDetails[1] ) ) @list( $table, $database ) = $dbDetails;
  1346. else @list( $table ) = $dbDetails;
  1347. $prefix = $this->mTablePrefix; # Default prefix
  1348. # A database name has been specified in input. Quote the table name
  1349. # because we don't want any prefixes added.
  1350. if( isset($database) ) $table = ( $table[0] == '`' ? $table : "`{$table}`" );
  1351. # Note that we use the long format because php will complain in in_array if
  1352. # the input is not an array, and will complain in is_array if it is not set.
  1353. if( !isset( $database ) # Don't use shared database if pre selected.
  1354. && isset( $wgSharedDB ) # We have a shared database
  1355. && $table[0] != '`' # Paranoia check to prevent shared tables listing '`table`'
  1356. && isset( $wgSharedTables )
  1357. && is_array( $wgSharedTables )
  1358. && in_array( $table, $wgSharedTables ) ) { # A shared table is selected
  1359. $database = $wgSharedDB;
  1360. $prefix = isset( $wgSharedPrefix ) ? $wgSharedPrefix : $prefix;
  1361. }
  1362. # Quote the $database and $table and apply the prefix if not quoted.
  1363. if( isset($database) ) $database = ( $database[0] == '`' ? $database : "`{$database}`" );
  1364. $table = ( $table[0] == '`' ? $table : "`{$prefix}{$table}`" );
  1365. # Merge our database and table into our final table name.
  1366. $tableName = ( isset($database) ? "{$database}.{$table}" : "{$table}" );
  1367. # We're finished, return.
  1368. return $tableName;
  1369. }
  1370. /**
  1371. * Fetch a number of table names into an array
  1372. * This is handy when you need to construct SQL for joins
  1373. *
  1374. * Example:
  1375. * extract($dbr->tableNames('user','watchlist'));
  1376. * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
  1377. * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
  1378. */
  1379. public function tableNames() {
  1380. $inArray = func_get_args();
  1381. $retVal = array();
  1382. foreach ( $inArray as $name ) {
  1383. $retVal[$name] = $this->tableName( $name );
  1384. }
  1385. return $retVal;
  1386. }
  1387. /**
  1388. * Fetch a number of table names into an zero-indexed numerical array
  1389. * This is handy when you need to construct SQL for joins
  1390. *
  1391. * Example:
  1392. * list( $user, $watchlist ) = $dbr->tableNamesN('user','watchlist');
  1393. * $sql = "SELECT wl_namespace,wl_title FROM $watchlist,$user
  1394. * WHERE wl_user=user_id AND wl_user=$nameWithQuotes";
  1395. */
  1396. public function tableNamesN() {
  1397. $inArray = func_get_args();
  1398. $retVal = array();
  1399. foreach ( $inArray as $name ) {
  1400. $retVal[] = $this->tableName( $name );
  1401. }
  1402. return $retVal;
  1403. }
  1404. /**
  1405. * @private
  1406. */
  1407. function tableNamesWithUseIndexOrJOIN( $tables, $use_index = array(), $join_conds = array() ) {
  1408. $ret = array();
  1409. $retJOIN = array();
  1410. $use_index_safe = is_array($use_index) ? $use_index : array();
  1411. $join_conds_safe = is_array($join_conds) ? $join_conds : array();
  1412. foreach ( $tables as $table ) {
  1413. // Is there a JOIN and INDEX clause for this table?
  1414. if ( isset($join_conds_safe[$table]) && isset($use_index_safe[$table]) ) {
  1415. $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
  1416. $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
  1417. $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
  1418. $retJOIN[] = $tableClause;
  1419. // Is there an INDEX clause?
  1420. } else if ( isset($use_index_safe[$table]) ) {
  1421. $tableClause = $this->tableName( $table );
  1422. $tableClause .= ' ' . $this->useIndexClause( implode( ',', (array)$use_index_safe[$table] ) );
  1423. $ret[] = $tableClause;
  1424. // Is there a JOIN clause?
  1425. } else if ( isset($join_conds_safe[$table]) ) {
  1426. $tableClause = $join_conds_safe[$table][0] . ' ' . $this->tableName( $table );
  1427. $tableClause .= ' ON (' . $this->makeList((array)$join_conds_safe[$table][1], LIST_AND) . ')';
  1428. $retJOIN[] = $tableClause;
  1429. } else {
  1430. $tableClause = $this->tableName( $table );
  1431. $ret[] = $tableClause;
  1432. }
  1433. }
  1434. // We can't separate explicit JOIN clauses with ',', use ' ' for those
  1435. $straightJoins = !empty($ret) ? implode( ',', $ret ) : "";
  1436. $otherJoins = !empty($retJOIN) ? implode( ' ', $retJOIN ) : "";
  1437. // Compile our final table clause
  1438. return implode(' ',array($straightJoins,$otherJoins) );
  1439. }
  1440. /**
  1441. * Get the name of an index in a given table
  1442. */
  1443. function indexName( $index ) {
  1444. // Backwards-compatibility hack
  1445. $renamed = array(
  1446. 'ar_usertext_timestamp' => 'usertext_timestamp',
  1447. 'un_user_id' => 'user_id',
  1448. 'un_user_ip' => 'user_ip',
  1449. );
  1450. if( isset( $renamed[$index] ) ) {
  1451. return $renamed[$index];
  1452. } else {
  1453. return $index;
  1454. }
  1455. }
  1456. /**
  1457. * Wrapper for addslashes()
  1458. * @param $s String: to be slashed.
  1459. * @return String: slashed string.
  1460. */
  1461. function strencode( $s ) {
  1462. return mysql_real_escape_string( $s, $this->mConn );
  1463. }
  1464. /**
  1465. * If it's a string, adds quotes and backslashes
  1466. * Otherwise returns as-is
  1467. */
  1468. function addQuotes( $s ) {
  1469. if ( $s === null ) {
  1470. return 'NULL';
  1471. } else {
  1472. # This will also quote numeric values. This should be harmless,
  1473. # and protects against weird problems that occur when they really
  1474. # _are_ strings such as article titles and string->number->string
  1475. # conversion is not 1:1.
  1476. return "'" . $this->strencode( $s ) . "'";
  1477. }
  1478. }
  1479. /**
  1480. * Escape string for safe LIKE usage
  1481. */
  1482. function escapeLike( $s ) {
  1483. $s=str_replace('\\','\\\\',$s);
  1484. $s=$this->strencode( $s );
  1485. $s=str_replace(array('%','_'),array('\%','\_'),$s);
  1486. return $s;
  1487. }
  1488. /**
  1489. * Returns an appropriately quoted sequence value for inserting a new row.
  1490. * MySQL has autoincrement fields, so this is just NULL. But the PostgreSQL
  1491. * subclass will return an integer, and save the value for insertId()
  1492. */
  1493. function nextSequenceValue( $seqName ) {
  1494. return NULL;
  1495. }
  1496. /**
  1497. * USE INDEX clause
  1498. * PostgreSQL doesn't have them and returns ""
  1499. */
  1500. function useIndexClause( $index ) {
  1501. return "FORCE INDEX (" . $this->indexName( $index ) . ")";
  1502. }
  1503. /**
  1504. * REPLACE query wrapper
  1505. * PostgreSQL simulates this with a DELETE followed by INSERT
  1506. * $row is the row to insert, an associative array
  1507. * $uniqueIndexes is an array of indexes. Each element may be either a
  1508. * field name or an array of field names
  1509. *
  1510. * It may be more efficient to leave off unique indexes which are unlikely to collide.
  1511. * However if you do this, you run the risk of encountering errors which wouldn't have
  1512. * occurred in MySQL
  1513. *
  1514. * @todo migrate comment to phodocumentor format
  1515. */
  1516. function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) {
  1517. $table = $this->tableName( $table );
  1518. # Single row case
  1519. if ( !is_array( reset( $rows ) ) ) {
  1520. $rows = array( $rows );
  1521. }
  1522. $sql = "REPLACE INTO $table (" . implode( ',', array_keys( $rows[0] ) ) .') VALUES ';
  1523. $first = true;
  1524. foreach ( $rows as $row ) {
  1525. if ( $first ) {
  1526. $first = false;
  1527. } else {
  1528. $sql .= ',';
  1529. }
  1530. $sql .= '(' . $this->makeList( $row ) . ')';
  1531. }
  1532. return $this->query( $sql, $fname );
  1533. }
  1534. /**
  1535. * DELETE where the condition is a join
  1536. * MySQL does this with a multi-table DELETE syntax, PostgreSQL does it with sub-selects
  1537. *
  1538. * For safety, an empty $conds will not delete everything. If you want to delete all rows where the
  1539. * join condition matches, set $conds='*'
  1540. *
  1541. * DO NOT put the join condition in $conds
  1542. *
  1543. * @param $delTable String: The table to delete from.
  1544. * @param $joinTable String: The other table.
  1545. * @param $delVar String: The variable to join on, in the first table.
  1546. * @param $joinVar String: The variable to join on, in the second table.
  1547. * @param $conds Array: Condition array of field names mapped to variables, ANDed together in the WHERE clause
  1548. * @param $fname String: Calling function name (use __METHOD__) for logs/profiling
  1549. */
  1550. function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'Database::deleteJoin' ) {
  1551. if ( !$conds ) {
  1552. throw new DBUnexpectedError( $this, 'Database::deleteJoin() called with empty $conds' );
  1553. }
  1554. $delTable = $this->tableName( $delTable );
  1555. $joinTable = $this->tableName( $joinTable );
  1556. $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
  1557. if ( $conds != '*' ) {
  1558. $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
  1559. }
  1560. return $this->query( $sql, $fname );
  1561. }
  1562. /**
  1563. * Returns the size of a text field, or -1 for "unlimited"
  1564. */
  1565. function textFieldSize( $table, $field ) {
  1566. $table = $this->tableName( $table );
  1567. $sql = "SHOW COLUMNS FROM $table LIKE \"$field\";";
  1568. $res = $this->query( $sql, 'Database::textFieldSize' );
  1569. $row = $this->fetchObject( $res );
  1570. $this->freeResult( $res );
  1571. $m = array();
  1572. if ( preg_match( '/\((.*)\)/', $row->Type, $m ) ) {
  1573. $size = $m[1];
  1574. } else {
  1575. $size = -1;
  1576. }
  1577. return $size;
  1578. }
  1579. /**
  1580. * @return string Returns the text of the low priority option if it is supported, or a blank string otherwise
  1581. */
  1582. function lowPriorityOption() {
  1583. return 'LOW_PRIORITY';
  1584. }
  1585. /**
  1586. * DELETE query wrapper
  1587. *
  1588. * Use $conds == "*" to delete all rows
  1589. */
  1590. function delete( $table, $conds, $fname = 'Database::delete' ) {
  1591. if ( !$conds ) {
  1592. throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' );
  1593. }
  1594. $table = $this->tableName( $table );
  1595. $sql = "DELETE FROM $table";
  1596. if ( $conds != '*' ) {
  1597. $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
  1598. }
  1599. return $this->query( $sql, $fname );
  1600. }
  1601. /**
  1602. * INSERT SELECT wrapper
  1603. * $varMap must be an associative array of the form array( 'dest1' => 'source1', ...)
  1604. * Source items may be literals rather than field names, but strings should be quoted with Database::addQuotes()
  1605. * $conds may be "*" to copy the whole table
  1606. * srcTable may be an array of tables.
  1607. */
  1608. function insertSelect( $destTable, $srcTable, $varMap, $conds, $fname = 'Database::insertSelect',
  1609. $insertOptions = array(), $selectOptions = array() )
  1610. {
  1611. $destTable = $this->tableName( $destTable );
  1612. if ( is_array( $insertOptions ) ) {
  1613. $insertOptions = implode( ' ', $insertOptions );
  1614. }
  1615. if( !is_array( $selectOptions ) ) {
  1616. $selectOptions = array( $selectOptions );
  1617. }
  1618. list( $startOpts, $useIndex, $tailOpts ) = $this->makeSelectOptions( $selectOptions );
  1619. if( is_array( $srcTable ) ) {
  1620. $srcTable = implode( ',', array_map( array( &$this, 'tableName' ), $srcTable ) );
  1621. } else {
  1622. $srcTable = $this->tableName( $srcTable );
  1623. }
  1624. $sql = "INSERT $insertOptions INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ')' .
  1625. " SELECT $startOpts " . implode( ',', $varMap ) .
  1626. " FROM $srcTable $useIndex ";
  1627. if ( $conds != '*' ) {
  1628. $sql .= ' WHERE ' . $this->makeList( $conds, LIST_AND );
  1629. }
  1630. $sql .= " $tailOpts";
  1631. return $this->query( $sql, $fname );
  1632. }
  1633. /**
  1634. * Construct a LIMIT query with optional offset
  1635. * This is used for query pages
  1636. * @param $sql String: SQL query we will append the limit too
  1637. * @param $limit Integer: the SQL limit
  1638. * @param $offset Integer the SQL offset (default false)
  1639. */
  1640. function limitResult($sql, $limit, $offset=false) {
  1641. if( !is_numeric($limit) ) {
  1642. throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" );
  1643. }
  1644. return "$sql LIMIT "
  1645. . ( (is_numeric($offset) && $offset != 0) ? "{$offset}," : "" )
  1646. . "{$limit} ";
  1647. }
  1648. function limitResultForUpdate($sql, $num) {
  1649. return $this->limitResult($sql, $num, 0);
  1650. }
  1651. /**
  1652. * Returns an SQL expression for a simple conditional.
  1653. * Uses IF on MySQL.
  1654. *
  1655. * @param $cond String: SQL expression which will result in a boolean value
  1656. * @param $trueVal String: SQL expression to return if true
  1657. * @param $falseVal String: SQL expression to return if false
  1658. * @return String: SQL fragment
  1659. */
  1660. function conditional( $cond, $trueVal, $falseVal ) {
  1661. return " IF($cond, $trueVal, $falseVal) ";
  1662. }
  1663. /**
  1664. * Returns a comand for str_replace function in SQL query.
  1665. * Uses REPLACE() in MySQL
  1666. *
  1667. * @param $orig String: column to modify
  1668. * @param $old String: column to seek
  1669. * @param $new String: column to replace with
  1670. */
  1671. function strreplace( $orig, $old, $new ) {
  1672. return "REPLACE({$orig}, {$old}, {$new})";
  1673. }
  1674. /**
  1675. * Determines if the last failure was due to a deadlock
  1676. */
  1677. function wasDeadlock() {
  1678. return $this->lastErrno() == 1213;
  1679. }
  1680. /**
  1681. * Determines if the last query error was something that should be dealt
  1682. * with by pinging the connection and reissuing the query
  1683. */
  1684. function wasErrorReissuable() {
  1685. return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
  1686. }
  1687. /**
  1688. * Perform a deadlock-prone transaction.
  1689. *
  1690. * This function invokes a callback function to perform a set of write
  1691. * queries. If a deadlock occurs during the processing, the transaction
  1692. * will be rolled back and the callback function will be called again.
  1693. *
  1694. * Usage:
  1695. * $dbw->deadlockLoop( callback, ... );
  1696. *
  1697. * Extra arguments are passed through to the specified callback function.
  1698. *
  1699. * Returns whatever the callback function returned on its successful,
  1700. * iteration, or false on error, for example if the retry limit was
  1701. * reached.
  1702. */
  1703. function deadlockLoop() {
  1704. $myFname = 'Database::deadlockLoop';
  1705. $this->begin();
  1706. $args = func_get_args();
  1707. $function = array_shift( $args );
  1708. $oldIgnore = $this->ignoreErrors( true );
  1709. $tries = DEADLOCK_TRIES;
  1710. if ( is_array( $function ) ) {
  1711. $fname = $function[0];
  1712. } else {
  1713. $fname = $function;
  1714. }
  1715. do {
  1716. $retVal = call_user_func_array( $function, $args );
  1717. $error = $this->lastError();
  1718. $errno = $this->lastErrno();
  1719. $sql = $this->lastQuery();
  1720. if ( $errno ) {
  1721. if ( $this->wasDeadlock() ) {
  1722. # Retry
  1723. usleep( mt_rand( DEADLOCK_DELAY_MIN, DEADLOCK_DELAY_MAX ) );
  1724. } else {
  1725. $this->reportQueryError( $error, $errno, $sql, $fname );
  1726. }
  1727. }
  1728. } while( $this->wasDeadlock() && --$tries > 0 );
  1729. $this->ignoreErrors( $oldIgnore );
  1730. if ( $tries <= 0 ) {
  1731. $this->query( 'ROLLBACK', $myFname );
  1732. $this->reportQueryError( $error, $errno, $sql, $fname );
  1733. return false;
  1734. } else {
  1735. $this->query( 'COMMIT', $myFname );
  1736. return $retVal;
  1737. }
  1738. }
  1739. /**
  1740. * Do a SELECT MASTER_POS_WAIT()
  1741. *
  1742. * @param $pos MySQLMasterPos object
  1743. * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
  1744. */
  1745. function masterPosWait( MySQLMasterPos $pos, $timeout ) {
  1746. $fname = 'Database::masterPosWait';
  1747. wfProfileIn( $fname );
  1748. # Commit any open transactions
  1749. if ( $this->mTrxLevel ) {
  1750. $this->immediateCommit();
  1751. }
  1752. if ( !is_null( $this->mFakeSlaveLag ) ) {
  1753. $wait = intval( ( $pos->pos - microtime(true) + $this->mFakeSlaveLag ) * 1e6 );
  1754. if ( $wait > $timeout * 1e6 ) {
  1755. wfDebug( "Fake slave timed out waiting for $pos ($wait us)\n" );
  1756. wfProfileOut( $fname );
  1757. return -1;
  1758. } elseif ( $wait > 0 ) {
  1759. wfDebug( "Fake slave waiting $wait us\n" );
  1760. usleep( $wait );
  1761. wfProfileOut( $fname );
  1762. return 1;
  1763. } else {
  1764. wfDebug( "Fake slave up to date ($wait us)\n" );
  1765. wfProfileOut( $fname );
  1766. return 0;
  1767. }
  1768. }
  1769. # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
  1770. $encFile = $this->addQuotes( $pos->file );
  1771. $encPos = intval( $pos->pos );
  1772. $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
  1773. $res = $this->doQuery( $sql );
  1774. if ( $res && $row = $this->fetchRow( $res ) ) {
  1775. $this->freeResult( $res );
  1776. wfProfileOut( $fname );
  1777. return $row[0];
  1778. } else {
  1779. wfProfileOut( $fname );
  1780. return false;
  1781. }
  1782. }
  1783. /**
  1784. * Get the position of the master from SHOW SLAVE STATUS
  1785. */
  1786. function getSlavePos() {
  1787. if ( !is_null( $this->mFakeSlaveLag ) ) {
  1788. $pos = new MySQLMasterPos( 'fake', microtime(true) - $this->mFakeSlaveLag );
  1789. wfDebug( __METHOD__.": fake slave pos = $pos\n" );
  1790. return $pos;
  1791. }
  1792. $res = $this->query( 'SHOW SLAVE STATUS', 'Database::getSlavePos' );
  1793. $row = $this->fetchObject( $res );
  1794. if ( $row ) {
  1795. $pos = isset($row->Exec_master_log_pos) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
  1796. return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
  1797. } else {
  1798. return false;
  1799. }
  1800. }
  1801. /**
  1802. * Get the position of the master from SHOW MASTER STATUS
  1803. */
  1804. function getMasterPos() {
  1805. if ( $this->mFakeMaster ) {
  1806. return new MySQLMasterPos( 'fake', microtime( true ) );
  1807. }
  1808. $res = $this->query( 'SHOW MASTER STATUS', 'Database::getMasterPos' );
  1809. $row = $this->fetchObject( $res );
  1810. if ( $row ) {
  1811. return new MySQLMasterPos( $row->File, $row->Position );
  1812. } else {
  1813. return false;
  1814. }
  1815. }
  1816. /**
  1817. * Begin a transaction, committing any previously open transaction
  1818. */
  1819. function begin( $fname = 'Database::begin' ) {
  1820. $this->query( 'BEGIN', $fname );
  1821. $this->mTrxLevel = 1;
  1822. }
  1823. /**
  1824. * End a transaction
  1825. */
  1826. function commit( $fname = 'Database::commit' ) {
  1827. $this->query( 'COMMIT', $fname );
  1828. $this->mTrxLevel = 0;
  1829. }
  1830. /**
  1831. * Rollback a transaction.
  1832. * No-op on non-transactional databases.
  1833. */
  1834. function rollback( $fname = 'Database::rollback' ) {
  1835. $this->query( 'ROLLBACK', $fname, true );
  1836. $this->mTrxLevel = 0;
  1837. }
  1838. /**
  1839. * Begin a transaction, committing any previously open transaction
  1840. * @deprecated use begin()
  1841. */
  1842. function immediateBegin( $fname = 'Database::immediateBegin' ) {
  1843. $this->begin();
  1844. }
  1845. /**
  1846. * Commit transaction, if one is open
  1847. * @deprecated use commit()
  1848. */
  1849. function immediateCommit( $fname = 'Database::immediateCommit' ) {
  1850. $this->commit();
  1851. }
  1852. /**
  1853. * Return MW-style timestamp used for MySQL schema
  1854. */
  1855. function timestamp( $ts=0 ) {
  1856. return wfTimestamp(TS_MW,$ts);
  1857. }
  1858. /**
  1859. * Local database timestamp format or null
  1860. */
  1861. function timestampOrNull( $ts = null ) {
  1862. if( is_null( $ts ) ) {
  1863. return null;
  1864. } else {
  1865. return $this->timestamp( $ts );
  1866. }
  1867. }
  1868. /**
  1869. * @todo document
  1870. */
  1871. function resultObject( $result ) {
  1872. if( empty( $result ) ) {
  1873. return false;
  1874. } elseif ( $result instanceof ResultWrapper ) {
  1875. return $result;
  1876. } elseif ( $result === true ) {
  1877. // Successful write query
  1878. return $result;
  1879. } else {
  1880. return new ResultWrapper( $this, $result );
  1881. }
  1882. }
  1883. /**
  1884. * Return aggregated value alias
  1885. */
  1886. function aggregateValue ($valuedata,$valuename='value') {
  1887. return $valuename;
  1888. }
  1889. /**
  1890. * @return String: wikitext of a link to the server software's web site
  1891. */
  1892. function getSoftwareLink() {
  1893. return "[http://www.mysql.com/ MySQL]";
  1894. }
  1895. /**
  1896. * @return String: Version information from the database
  1897. */
  1898. function getServerVersion() {
  1899. return mysql_get_server_info( $this->mConn );
  1900. }
  1901. /**
  1902. * Ping the server and try to reconnect if it there is no connection
  1903. */
  1904. function ping() {
  1905. if( !function_exists( 'mysql_ping' ) ) {
  1906. wfDebug( "Tried to call mysql_ping but this is ancient PHP version. Faking it!\n" );
  1907. return true;
  1908. }
  1909. $ping = mysql_ping( $this->mConn );
  1910. if ( $ping ) {
  1911. return true;
  1912. }
  1913. // Need to reconnect manually in MySQL client 5.0.13+
  1914. if ( version_compare( mysql_get_client_info(), '5.0.13', '>=' ) ) {
  1915. mysql_close( $this->mConn );
  1916. $this->mOpened = false;
  1917. $this->mConn = false;
  1918. $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
  1919. return true;
  1920. }
  1921. return false;
  1922. }
  1923. /**
  1924. * Get slave lag.
  1925. * At the moment, this will only work if the DB user has the PROCESS privilege
  1926. */
  1927. function getLag() {
  1928. if ( !is_null( $this->mFakeSlaveLag ) ) {
  1929. wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
  1930. return $this->mFakeSlaveLag;
  1931. }
  1932. $res = $this->query( 'SHOW PROCESSLIST' );
  1933. # Find slave SQL thread
  1934. while ( $row = $this->fetchObject( $res ) ) {
  1935. /* This should work for most situations - when default db
  1936. * for thread is not specified, it had no events executed,
  1937. * and therefore it doesn't know yet how lagged it is.
  1938. *
  1939. * Relay log I/O thread does not select databases.
  1940. */
  1941. if ( $row->User == 'system user' &&
  1942. $row->State != 'Waiting for master to send event' &&
  1943. $row->State != 'Connecting to master' &&
  1944. $row->State != 'Queueing master event to the relay log' &&
  1945. $row->State != 'Waiting for master update' &&
  1946. $row->State != 'Requesting binlog dump'
  1947. ) {
  1948. # This is it, return the time (except -ve)
  1949. if ( $row->Time > 0x7fffffff ) {
  1950. return false;
  1951. } else {
  1952. return $row->Time;
  1953. }
  1954. }
  1955. }
  1956. return false;
  1957. }
  1958. /**
  1959. * Get status information from SHOW STATUS in an associative array
  1960. */
  1961. function getStatus($which="%") {
  1962. $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
  1963. $status = array();
  1964. while ( $row = $this->fetchObject( $res ) ) {
  1965. $status[$row->Variable_name] = $row->Value;
  1966. }
  1967. return $status;
  1968. }
  1969. /**
  1970. * Return the maximum number of items allowed in a list, or 0 for unlimited.
  1971. */
  1972. function maxListLen() {
  1973. return 0;
  1974. }
  1975. function encodeBlob($b) {
  1976. return $b;
  1977. }
  1978. function decodeBlob($b) {
  1979. return $b;
  1980. }
  1981. /**
  1982. * Override database's default connection timeout.
  1983. * May be useful for very long batch queries such as
  1984. * full-wiki dumps, where a single query reads out
  1985. * over hours or days.
  1986. * @param $timeout Integer in seconds
  1987. */
  1988. public function setTimeout( $timeout ) {
  1989. $this->query( "SET net_read_timeout=$timeout" );
  1990. $this->query( "SET net_write_timeout=$timeout" );
  1991. }
  1992. /**
  1993. * Read and execute SQL commands from a file.
  1994. * Returns true on success, error string or exception on failure (depending on object's error ignore settings)
  1995. * @param $filename String: File name to open
  1996. * @param $lineCallback Callback: Optional function called before reading each line
  1997. * @param $resultCallback Callback: Optional function called for each MySQL result
  1998. */
  1999. function sourceFile( $filename, $lineCallback = false, $resultCallback = false ) {
  2000. $fp = fopen( $filename, 'r' );
  2001. if ( false === $fp ) {
  2002. throw new MWException( "Could not open \"{$filename}\".\n" );
  2003. }
  2004. $error = $this->sourceStream( $fp, $lineCallback, $resultCallback );
  2005. fclose( $fp );
  2006. return $error;
  2007. }
  2008. /**
  2009. * Read and execute commands from an open file handle
  2010. * Returns true on success, error string or exception on failure (depending on object's error ignore settings)
  2011. * @param $fp String: File handle
  2012. * @param $lineCallback Callback: Optional function called before reading each line
  2013. * @param $resultCallback Callback: Optional function called for each MySQL result
  2014. */
  2015. function sourceStream( $fp, $lineCallback = false, $resultCallback = false ) {
  2016. $cmd = "";
  2017. $done = false;
  2018. $dollarquote = false;
  2019. while ( ! feof( $fp ) ) {
  2020. if ( $lineCallback ) {
  2021. call_user_func( $lineCallback );
  2022. }
  2023. $line = trim( fgets( $fp, 1024 ) );
  2024. $sl = strlen( $line ) - 1;
  2025. if ( $sl < 0 ) { continue; }
  2026. if ( '-' == $line{0} && '-' == $line{1} ) { continue; }
  2027. ## Allow dollar quoting for function declarations
  2028. if (substr($line,0,4) == '$mw$') {
  2029. if ($dollarquote) {
  2030. $dollarquote = false;
  2031. $done = true;
  2032. }
  2033. else {
  2034. $dollarquote = true;
  2035. }
  2036. }
  2037. else if (!$dollarquote) {
  2038. if ( ';' == $line{$sl} && ($sl < 2 || ';' != $line{$sl - 1})) {
  2039. $done = true;
  2040. $line = substr( $line, 0, $sl );
  2041. }
  2042. }
  2043. if ( '' != $cmd ) { $cmd .= ' '; }
  2044. $cmd .= "$line\n";
  2045. if ( $done ) {
  2046. $cmd = str_replace(';;', ";", $cmd);
  2047. $cmd = $this->replaceVars( $cmd );
  2048. $res = $this->query( $cmd, __METHOD__ );
  2049. if ( $resultCallback ) {
  2050. call_user_func( $resultCallback, $res, $this );
  2051. }
  2052. if ( false === $res ) {
  2053. $err = $this->lastError();
  2054. return "Query \"{$cmd}\" failed with error code \"$err\".\n";
  2055. }
  2056. $cmd = '';
  2057. $done = false;
  2058. }
  2059. }
  2060. return true;
  2061. }
  2062. /**
  2063. * Replace variables in sourced SQL
  2064. */
  2065. protected function replaceVars( $ins ) {
  2066. $varnames = array(
  2067. 'wgDBserver', 'wgDBname', 'wgDBintlname', 'wgDBuser',
  2068. 'wgDBpassword', 'wgDBsqluser', 'wgDBsqlpassword',
  2069. 'wgDBadminuser', 'wgDBadminpassword', 'wgDBTableOptions',
  2070. );
  2071. // Ordinary variables
  2072. foreach ( $varnames as $var ) {
  2073. if( isset( $GLOBALS[$var] ) ) {
  2074. $val = addslashes( $GLOBALS[$var] ); // FIXME: safety check?
  2075. $ins = str_replace( '{$' . $var . '}', $val, $ins );
  2076. $ins = str_replace( '/*$' . $var . '*/`', '`' . $val, $ins );
  2077. $ins = str_replace( '/*$' . $var . '*/', $val, $ins );
  2078. }
  2079. }
  2080. // Table prefixes
  2081. $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!',
  2082. array( $this, 'tableNameCallback' ), $ins );
  2083. // Index names
  2084. $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!',
  2085. array( $this, 'indexNameCallback' ), $ins );
  2086. return $ins;
  2087. }
  2088. /**
  2089. * Table name callback
  2090. * @private
  2091. */
  2092. protected function tableNameCallback( $matches ) {
  2093. return $this->tableName( $matches[1] );
  2094. }
  2095. /**
  2096. * Index name callback
  2097. */
  2098. protected function indexNameCallback( $matches ) {
  2099. return $this->indexName( $matches[1] );
  2100. }
  2101. /*
  2102. * Build a concatenation list to feed into a SQL query
  2103. */
  2104. function buildConcat( $stringList ) {
  2105. return 'CONCAT(' . implode( ',', $stringList ) . ')';
  2106. }
  2107. /**
  2108. * Acquire a lock
  2109. *
  2110. * Abstracted from Filestore::lock() so child classes can implement for
  2111. * their own needs.
  2112. *
  2113. * @param $lockName String: Name of lock to aquire
  2114. * @param $method String: Name of method calling us
  2115. * @return bool
  2116. */
  2117. public function lock( $lockName, $method ) {
  2118. $lockName = $this->addQuotes( $lockName );
  2119. $result = $this->query( "SELECT GET_LOCK($lockName, 5) AS lockstatus", $method );
  2120. $row = $this->fetchObject( $result );
  2121. $this->freeResult( $result );
  2122. if( $row->lockstatus == 1 ) {
  2123. return true;
  2124. } else {
  2125. wfDebug( __METHOD__." failed to acquire lock\n" );
  2126. return false;
  2127. }
  2128. }
  2129. /**
  2130. * Release a lock.
  2131. *
  2132. * @todo fixme - Figure out a way to return a bool
  2133. * based on successful lock release.
  2134. *
  2135. * @param $lockName String: Name of lock to release
  2136. * @param $method String: Name of method calling us
  2137. */
  2138. public function unlock( $lockName, $method ) {
  2139. $lockName = $this->addQuotes( $lockName );
  2140. $result = $this->query( "SELECT RELEASE_LOCK($lockName)", $method );
  2141. $this->freeResult( $result );
  2142. }
  2143. /**
  2144. * Get search engine class. All subclasses of this
  2145. * need to implement this if they wish to use searching.
  2146. *
  2147. * @return String
  2148. */
  2149. public function getSearchEngine() {
  2150. return "SearchMySQL";
  2151. }
  2152. }
  2153. /**
  2154. * Database abstraction object for mySQL
  2155. * Inherit all methods and properties of Database::Database()
  2156. *
  2157. * @ingroup Database
  2158. * @see Database
  2159. */
  2160. class DatabaseMysql extends Database {
  2161. # Inherit all
  2162. }
  2163. /******************************************************************************
  2164. * Utility classes
  2165. *****************************************************************************/
  2166. /**
  2167. * Utility class.
  2168. * @ingroup Database
  2169. */
  2170. class DBObject {
  2171. public $mData;
  2172. function DBObject($data) {
  2173. $this->mData = $data;
  2174. }
  2175. function isLOB() {
  2176. return false;
  2177. }
  2178. function data() {
  2179. return $this->mData;
  2180. }
  2181. }
  2182. /**
  2183. * Utility class
  2184. * @ingroup Database
  2185. *
  2186. * This allows us to distinguish a blob from a normal string and an array of strings
  2187. */
  2188. class Blob {
  2189. private $mData;
  2190. function __construct($data) {
  2191. $this->mData = $data;
  2192. }
  2193. function fetch() {
  2194. return $this->mData;
  2195. }
  2196. }
  2197. /**
  2198. * Utility class.
  2199. * @ingroup Database
  2200. */
  2201. class MySQLField {
  2202. private $name, $tablename, $default, $max_length, $nullable,
  2203. $is_pk, $is_unique, $is_multiple, $is_key, $type;
  2204. function __construct ($info) {
  2205. $this->name = $info->name;
  2206. $this->tablename = $info->table;
  2207. $this->default = $info->def;
  2208. $this->max_length = $info->max_length;
  2209. $this->nullable = !$info->not_null;
  2210. $this->is_pk = $info->primary_key;
  2211. $this->is_unique = $info->unique_key;
  2212. $this->is_multiple = $info->multiple_key;
  2213. $this->is_key = ($this->is_pk || $this->is_unique || $this->is_multiple);
  2214. $this->type = $info->type;
  2215. }
  2216. function name() {
  2217. return $this->name;
  2218. }
  2219. function tableName() {
  2220. return $this->tableName;
  2221. }
  2222. function defaultValue() {
  2223. return $this->default;
  2224. }
  2225. function maxLength() {
  2226. return $this->max_length;
  2227. }
  2228. function nullable() {
  2229. return $this->nullable;
  2230. }
  2231. function isKey() {
  2232. return $this->is_key;
  2233. }
  2234. function isMultipleKey() {
  2235. return $this->is_multiple;
  2236. }
  2237. function type() {
  2238. return $this->type;
  2239. }
  2240. }
  2241. /******************************************************************************
  2242. * Error classes
  2243. *****************************************************************************/
  2244. /**
  2245. * Database error base class
  2246. * @ingroup Database
  2247. */
  2248. class DBError extends MWException {
  2249. public $db;
  2250. /**
  2251. * Construct a database error
  2252. * @param $db Database object which threw the error
  2253. * @param $error A simple error message to be used for debugging
  2254. */
  2255. function __construct( Database &$db, $error ) {
  2256. $this->db =& $db;
  2257. parent::__construct( $error );
  2258. }
  2259. }
  2260. /**
  2261. * @ingroup Database
  2262. */
  2263. class DBConnectionError extends DBError {
  2264. public $error;
  2265. function __construct( Database &$db, $error = 'unknown error' ) {
  2266. $msg = 'DB connection error';
  2267. if ( trim( $error ) != '' ) {
  2268. $msg .= ": $error";
  2269. }
  2270. $this->error = $error;
  2271. parent::__construct( $db, $msg );
  2272. }
  2273. function useOutputPage() {
  2274. // Not likely to work
  2275. return false;
  2276. }
  2277. function useMessageCache() {
  2278. // Not likely to work
  2279. return false;
  2280. }
  2281. function getText() {
  2282. return $this->getMessage() . "\n";
  2283. }
  2284. function getLogMessage() {
  2285. # Don't send to the exception log
  2286. return false;
  2287. }
  2288. function getPageTitle() {
  2289. global $wgSitename, $wgLang;
  2290. $header = "$wgSitename has a problem";
  2291. if ( $wgLang instanceof Language ) {
  2292. $header = htmlspecialchars( $wgLang->getMessage( 'dberr-header' ) );
  2293. }
  2294. return $header;
  2295. }
  2296. function getHTML() {
  2297. global $wgLang, $wgMessageCache, $wgUseFileCache;
  2298. $sorry = 'Sorry! This site is experiencing technical difficulties.';
  2299. $again = 'Try waiting a few minutes and reloading.';
  2300. $info = '(Can\'t contact the database server: $1)';
  2301. if ( $wgLang instanceof Language ) {
  2302. $sorry = htmlspecialchars( $wgLang->getMessage( 'dberr-problems' ) );
  2303. $again = htmlspecialchars( $wgLang->getMessage( 'dberr-again' ) );
  2304. $info = htmlspecialchars( $wgLang->getMessage( 'dberr-info' ) );
  2305. }
  2306. # No database access
  2307. if ( is_object( $wgMessageCache ) ) {
  2308. $wgMessageCache->disable();
  2309. }
  2310. if ( trim( $this->error ) == '' ) {
  2311. $this->error = $this->db->getProperty('mServer');
  2312. }
  2313. $noconnect = "<p><strong>$sorry</strong><br />$again</p><p><small>$info</small></p>";
  2314. $text = str_replace( '$1', $this->error, $noconnect );
  2315. /*
  2316. if ( $GLOBALS['wgShowExceptionDetails'] ) {
  2317. $text .= '</p><p>Backtrace:</p><p>' .
  2318. nl2br( htmlspecialchars( $this->getTraceAsString() ) ) .
  2319. "</p>\n";
  2320. }*/
  2321. $extra = $this->searchForm();
  2322. if( $wgUseFileCache ) {
  2323. $cache = $this->fileCachedPage();
  2324. # Cached version on file system?
  2325. if( $cache !== null ) {
  2326. # Hack: extend the body for error messages
  2327. $cache = str_replace( array('</html>','</body>'), '', $cache );
  2328. # Add cache notice...
  2329. $cachederror = "This is a cached copy of the requested page, and may not be up to date. ";
  2330. # Localize it if possible...
  2331. if( $wgLang instanceof Language ) {
  2332. $cachederror = htmlspecialchars( $wgLang->getMessage( 'dberr-cachederror' ) );
  2333. }
  2334. $warning = "<div style='color:red;font-size:150%;font-weight:bold;'>$cachederror</div>";
  2335. # Output cached page with notices on bottom and re-close body
  2336. return "{$cache}{$warning}<hr />$text<hr />$extra</body></html>";
  2337. }
  2338. }
  2339. # Headers needed here - output is just the error message
  2340. return $this->htmlHeader()."$text<hr />$extra".$this->htmlFooter();
  2341. }
  2342. function searchForm() {
  2343. global $wgSitename, $wgServer, $wgLang, $wgInputEncoding;
  2344. $usegoogle = "You can try searching via Google in the meantime.";
  2345. $outofdate = "Note that their indexes of our content may be out of date.";
  2346. $googlesearch = "Search";
  2347. if ( $wgLang instanceof Language ) {
  2348. $usegoogle = htmlspecialchars( $wgLang->getMessage( 'dberr-usegoogle' ) );
  2349. $outofdate = htmlspecialchars( $wgLang->getMessage( 'dberr-outofdate' ) );
  2350. $googlesearch = htmlspecialchars( $wgLang->getMessage( 'searchbutton' ) );
  2351. }
  2352. $search = htmlspecialchars(@$_REQUEST['search']);
  2353. $trygoogle = <<<EOT
  2354. <div style="margin: 1.5em">$usegoogle<br />
  2355. <small>$outofdate</small></div>
  2356. <!-- SiteSearch Google -->
  2357. <form method="get" action="http://www.google.com/search" id="googlesearch">
  2358. <input type="hidden" name="domains" value="$wgServer" />
  2359. <input type="hidden" name="num" value="50" />
  2360. <input type="hidden" name="ie" value="$wgInputEncoding" />
  2361. <input type="hidden" name="oe" value="$wgInputEncoding" />
  2362. <img src="http://www.google.com/logos/Logo_40wht.gif" alt="" style="float:left; margin-left: 1.5em; margin-right: 1.5em;" />
  2363. <input type="text" name="q" size="31" maxlength="255" value="$search" />
  2364. <input type="submit" name="btnG" value="$googlesearch" />
  2365. <div>
  2366. <input type="radio" name="sitesearch" id="gwiki" value="$wgServer" checked="checked" /><label for="gwiki">$wgSitename</label>
  2367. <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label>
  2368. </div>
  2369. </form>
  2370. <!-- SiteSearch Google -->
  2371. EOT;
  2372. return $trygoogle;
  2373. }
  2374. function fileCachedPage() {
  2375. global $wgTitle, $title, $wgLang, $wgOut;
  2376. if( $wgOut->isDisabled() ) return; // Done already?
  2377. $mainpage = 'Main Page';
  2378. if ( $wgLang instanceof Language ) {
  2379. $mainpage = htmlspecialchars( $wgLang->getMessage( 'mainpage' ) );
  2380. }
  2381. if($wgTitle) {
  2382. $t =& $wgTitle;
  2383. } elseif($title) {
  2384. $t = Title::newFromURL( $title );
  2385. } else {
  2386. $t = Title::newFromText( $mainpage );
  2387. }
  2388. $cache = new HTMLFileCache( $t );
  2389. if( $cache->isFileCached() ) {
  2390. return $cache->fetchPageText();
  2391. } else {
  2392. return '';
  2393. }
  2394. }
  2395. function htmlBodyOnly() {
  2396. return true;
  2397. }
  2398. }
  2399. /**
  2400. * @ingroup Database
  2401. */
  2402. class DBQueryError extends DBError {
  2403. public $error, $errno, $sql, $fname;
  2404. function __construct( Database &$db, $error, $errno, $sql, $fname ) {
  2405. $message = "A database error has occurred\n" .
  2406. "Query: $sql\n" .
  2407. "Function: $fname\n" .
  2408. "Error: $errno $error\n";
  2409. parent::__construct( $db, $message );
  2410. $this->error = $error;
  2411. $this->errno = $errno;
  2412. $this->sql = $sql;
  2413. $this->fname = $fname;
  2414. }
  2415. function getText() {
  2416. if ( $this->useMessageCache() ) {
  2417. return wfMsg( 'dberrortextcl', htmlspecialchars( $this->getSQL() ),
  2418. htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) ) . "\n";
  2419. } else {
  2420. return $this->getMessage();
  2421. }
  2422. }
  2423. function getSQL() {
  2424. global $wgShowSQLErrors;
  2425. if( !$wgShowSQLErrors ) {
  2426. return $this->msg( 'sqlhidden', 'SQL hidden' );
  2427. } else {
  2428. return $this->sql;
  2429. }
  2430. }
  2431. function getLogMessage() {
  2432. # Don't send to the exception log
  2433. return false;
  2434. }
  2435. function getPageTitle() {
  2436. return $this->msg( 'databaseerror', 'Database error' );
  2437. }
  2438. function getHTML() {
  2439. if ( $this->useMessageCache() ) {
  2440. return wfMsgNoDB( 'dberrortext', htmlspecialchars( $this->getSQL() ),
  2441. htmlspecialchars( $this->fname ), $this->errno, htmlspecialchars( $this->error ) );
  2442. } else {
  2443. return nl2br( htmlspecialchars( $this->getMessage() ) );
  2444. }
  2445. }
  2446. }
  2447. /**
  2448. * @ingroup Database
  2449. */
  2450. class DBUnexpectedError extends DBError {}
  2451. /**
  2452. * Result wrapper for grabbing data queried by someone else
  2453. * @ingroup Database
  2454. */
  2455. class ResultWrapper implements Iterator {
  2456. var $db, $result, $pos = 0, $currentRow = null;
  2457. /**
  2458. * Create a new result object from a result resource and a Database object
  2459. */
  2460. function ResultWrapper( $database, $result ) {
  2461. $this->db = $database;
  2462. if ( $result instanceof ResultWrapper ) {
  2463. $this->result = $result->result;
  2464. } else {
  2465. $this->result = $result;
  2466. }
  2467. }
  2468. /**
  2469. * Get the number of rows in a result object
  2470. */
  2471. function numRows() {
  2472. return $this->db->numRows( $this );
  2473. }
  2474. /**
  2475. * Fetch the next row from the given result object, in object form.
  2476. * Fields can be retrieved with $row->fieldname, with fields acting like
  2477. * member variables.
  2478. *
  2479. * @param $res SQL result object as returned from Database::query(), etc.
  2480. * @return MySQL row object
  2481. * @throws DBUnexpectedError Thrown if the database returns an error
  2482. */
  2483. function fetchObject() {
  2484. return $this->db->fetchObject( $this );
  2485. }
  2486. /**
  2487. * Fetch the next row from the given result object, in associative array
  2488. * form. Fields are retrieved with $row['fieldname'].
  2489. *
  2490. * @param $res SQL result object as returned from Database::query(), etc.
  2491. * @return MySQL row object
  2492. * @throws DBUnexpectedError Thrown if the database returns an error
  2493. */
  2494. function fetchRow() {
  2495. return $this->db->fetchRow( $this );
  2496. }
  2497. /**
  2498. * Free a result object
  2499. */
  2500. function free() {
  2501. $this->db->freeResult( $this );
  2502. unset( $this->result );
  2503. unset( $this->db );
  2504. }
  2505. /**
  2506. * Change the position of the cursor in a result object
  2507. * See mysql_data_seek()
  2508. */
  2509. function seek( $row ) {
  2510. $this->db->dataSeek( $this, $row );
  2511. }
  2512. /*********************
  2513. * Iterator functions
  2514. * Note that using these in combination with the non-iterator functions
  2515. * above may cause rows to be skipped or repeated.
  2516. */
  2517. function rewind() {
  2518. if ($this->numRows()) {
  2519. $this->db->dataSeek($this, 0);
  2520. }
  2521. $this->pos = 0;
  2522. $this->currentRow = null;
  2523. }
  2524. function current() {
  2525. if ( is_null( $this->currentRow ) ) {
  2526. $this->next();
  2527. }
  2528. return $this->currentRow;
  2529. }
  2530. function key() {
  2531. return $this->pos;
  2532. }
  2533. function next() {
  2534. $this->pos++;
  2535. $this->currentRow = $this->fetchObject();
  2536. return $this->currentRow;
  2537. }
  2538. function valid() {
  2539. return $this->current() !== false;
  2540. }
  2541. }
  2542. class MySQLMasterPos {
  2543. var $file, $pos;
  2544. function __construct( $file, $pos ) {
  2545. $this->file = $file;
  2546. $this->pos = $pos;
  2547. }
  2548. function __toString() {
  2549. return "{$this->file}/{$this->pos}";
  2550. }
  2551. }