Reverted 'lockTimeout' option from r104069: table_lock_wait_timeout is GLOBAL and...
[lhc/web/wiklou.git] / includes / db / DatabaseMysql.php
1 <?php
2 /**
3 * This is the MySQL database abstraction layer.
4 *
5 * @file
6 * @ingroup Database
7 */
8
9 /**
10 * Database abstraction object for mySQL
11 * Inherit all methods and properties of Database::Database()
12 *
13 * @ingroup Database
14 * @see Database
15 */
16 class DatabaseMysql extends DatabaseBase {
17
18 /**
19 * @return string
20 */
21 function getType() {
22 return 'mysql';
23 }
24
25 protected function doQuery( $sql ) {
26 if( $this->bufferResults() ) {
27 $ret = mysql_query( $sql, $this->mConn );
28 } else {
29 $ret = mysql_unbuffered_query( $sql, $this->mConn );
30 }
31 return $ret;
32 }
33
34 function open( $server, $user, $password, $dbName ) {
35 global $wgAllDBsAreLocalhost;
36 wfProfileIn( __METHOD__ );
37
38 # Load mysql.so if we don't have it
39 wfDl( 'mysql' );
40
41 # Fail now
42 # Otherwise we get a suppressed fatal error, which is very hard to track down
43 if ( !function_exists( 'mysql_connect' ) ) {
44 throw new DBConnectionError( $this, "MySQL functions missing, have you compiled PHP with the --with-mysql option?\n" );
45 }
46
47 # Debugging hack -- fake cluster
48 if ( $wgAllDBsAreLocalhost ) {
49 $realServer = 'localhost';
50 } else {
51 $realServer = $server;
52 }
53 $this->close();
54 $this->mServer = $server;
55 $this->mUser = $user;
56 $this->mPassword = $password;
57 $this->mDBname = $dbName;
58
59 wfProfileIn("dbconnect-$server");
60
61 # The kernel's default SYN retransmission period is far too slow for us,
62 # so we use a short timeout plus a manual retry. Retrying means that a small
63 # but finite rate of SYN packet loss won't cause user-visible errors.
64 $this->mConn = false;
65 if ( ini_get( 'mysql.connect_timeout' ) <= 3 ) {
66 $numAttempts = 2;
67 } else {
68 $numAttempts = 1;
69 }
70 $this->installErrorHandler();
71 for ( $i = 0; $i < $numAttempts && !$this->mConn; $i++ ) {
72 if ( $i > 1 ) {
73 usleep( 1000 );
74 }
75 if ( $this->mFlags & DBO_PERSISTENT ) {
76 $this->mConn = mysql_pconnect( $realServer, $user, $password );
77 } else {
78 # Create a new connection...
79 $this->mConn = mysql_connect( $realServer, $user, $password, true );
80 }
81 #if ( $this->mConn === false ) {
82 #$iplus = $i + 1;
83 #wfLogDBError("Connect loop error $iplus of $max ($server): " . mysql_errno() . " - " . mysql_error()."\n");
84 #}
85 }
86 $phpError = $this->restoreErrorHandler();
87 # Always log connection errors
88 if ( !$this->mConn ) {
89 $error = $this->lastError();
90 if ( !$error ) {
91 $error = $phpError;
92 }
93 wfLogDBError( "Error connecting to {$this->mServer}: $error\n" );
94 wfDebug( "DB connection error\n" );
95 wfDebug( "Server: $server, User: $user, Password: " .
96 substr( $password, 0, 3 ) . "..., error: " . mysql_error() . "\n" );
97 }
98
99 wfProfileOut("dbconnect-$server");
100
101 if ( $dbName != '' && $this->mConn !== false ) {
102 wfSuppressWarnings();
103 $success = mysql_select_db( $dbName, $this->mConn );
104 wfRestoreWarnings();
105 if ( !$success ) {
106 $error = "Error selecting database $dbName on server {$this->mServer} " .
107 "from client host " . wfHostname() . "\n";
108 wfLogDBError(" Error selecting database $dbName on server {$this->mServer} \n");
109 wfDebug( $error );
110 }
111 } else {
112 # Delay USE query
113 $success = (bool)$this->mConn;
114 }
115
116 if ( $success ) {
117 // Tell the server we're communicating with it in UTF-8.
118 // This may engage various charset conversions.
119 global $wgDBmysql5;
120 if( $wgDBmysql5 ) {
121 $this->query( 'SET NAMES utf8', __METHOD__ );
122 } else {
123 $this->query( 'SET NAMES binary', __METHOD__ );
124 }
125 // Set SQL mode, default is turning them all off, can be overridden or skipped with null
126 global $wgSQLMode;
127 if ( is_string( $wgSQLMode ) ) {
128 $mode = $this->addQuotes( $wgSQLMode );
129 $this->query( "SET sql_mode = $mode", __METHOD__ );
130 }
131
132 // Turn off strict mode if it is on
133 } else {
134 $this->reportConnectionError( $phpError );
135 }
136
137 $this->mOpened = $success;
138 wfProfileOut( __METHOD__ );
139 return $success;
140 }
141
142 /**
143 * @return bool
144 */
145 function close() {
146 $this->mOpened = false;
147 if ( $this->mConn ) {
148 if ( $this->trxLevel() ) {
149 $this->commit();
150 }
151 return mysql_close( $this->mConn );
152 } else {
153 return true;
154 }
155 }
156
157 function freeResult( $res ) {
158 if ( $res instanceof ResultWrapper ) {
159 $res = $res->result;
160 }
161 wfSuppressWarnings();
162 $ok = mysql_free_result( $res );
163 wfRestoreWarnings();
164 if ( !$ok ) {
165 throw new DBUnexpectedError( $this, "Unable to free MySQL result" );
166 }
167 }
168
169 function fetchObject( $res ) {
170 if ( $res instanceof ResultWrapper ) {
171 $res = $res->result;
172 }
173 wfSuppressWarnings();
174 $row = mysql_fetch_object( $res );
175 wfRestoreWarnings();
176 if( $this->lastErrno() ) {
177 throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) );
178 }
179 return $row;
180 }
181
182 function fetchRow( $res ) {
183 if ( $res instanceof ResultWrapper ) {
184 $res = $res->result;
185 }
186 wfSuppressWarnings();
187 $row = mysql_fetch_array( $res );
188 wfRestoreWarnings();
189 if ( $this->lastErrno() ) {
190 throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) );
191 }
192 return $row;
193 }
194
195 /**
196 * @throws DBUnexpectedError
197 * @param $res
198 * @return int
199 */
200 function numRows( $res ) {
201 if ( $res instanceof ResultWrapper ) {
202 $res = $res->result;
203 }
204 wfSuppressWarnings();
205 $n = mysql_num_rows( $res );
206 wfRestoreWarnings();
207 if( $this->lastErrno() ) {
208 throw new DBUnexpectedError( $this, 'Error in numRows(): ' . htmlspecialchars( $this->lastError() ) );
209 }
210 return $n;
211 }
212
213 /**
214 * @param $res
215 * @return int
216 */
217 function numFields( $res ) {
218 if ( $res instanceof ResultWrapper ) {
219 $res = $res->result;
220 }
221 return mysql_num_fields( $res );
222 }
223
224 /**
225 * @param $res
226 * @param $n
227 * @return string
228 */
229 function fieldName( $res, $n ) {
230 if ( $res instanceof ResultWrapper ) {
231 $res = $res->result;
232 }
233 return mysql_field_name( $res, $n );
234 }
235
236 /**
237 * @return int
238 */
239 function insertId() {
240 return mysql_insert_id( $this->mConn );
241 }
242
243 function dataSeek( $res, $row ) {
244 if ( $res instanceof ResultWrapper ) {
245 $res = $res->result;
246 }
247 return mysql_data_seek( $res, $row );
248 }
249
250 function lastErrno() {
251 if ( $this->mConn ) {
252 return mysql_errno( $this->mConn );
253 } else {
254 return mysql_errno();
255 }
256 }
257
258 function lastError() {
259 if ( $this->mConn ) {
260 # Even if it's non-zero, it can still be invalid
261 wfSuppressWarnings();
262 $error = mysql_error( $this->mConn );
263 if ( !$error ) {
264 $error = mysql_error();
265 }
266 wfRestoreWarnings();
267 } else {
268 $error = mysql_error();
269 }
270 if( $error ) {
271 $error .= ' (' . $this->mServer . ')';
272 }
273 return $error;
274 }
275
276 /**
277 * @return int
278 */
279 function affectedRows() {
280 return mysql_affected_rows( $this->mConn );
281 }
282
283 function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMysql::replace' ) {
284 return $this->nativeReplace( $table, $rows, $fname );
285 }
286
287 /**
288 * Estimate rows in dataset
289 * Returns estimated count, based on EXPLAIN output
290 * Takes same arguments as Database::select()
291 *
292 * @return int
293 */
294 public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabaseMysql::estimateRowCount', $options = array() ) {
295 $options['EXPLAIN'] = true;
296 $res = $this->select( $table, $vars, $conds, $fname, $options );
297 if ( $res === false ) {
298 return false;
299 }
300 if ( !$this->numRows( $res ) ) {
301 return 0;
302 }
303
304 $rows = 1;
305 foreach ( $res as $plan ) {
306 $rows *= $plan->rows > 0 ? $plan->rows : 1; // avoid resetting to zero
307 }
308 return $rows;
309 }
310
311 /**
312 * @param $table string
313 * @param $field string
314 * @return bool|MySQLField
315 */
316 function fieldInfo( $table, $field ) {
317 $table = $this->tableName( $table );
318 $res = $this->query( "SELECT * FROM $table LIMIT 1", __METHOD__, true );
319 if ( !$res ) {
320 return false;
321 }
322 $n = mysql_num_fields( $res->result );
323 for( $i = 0; $i < $n; $i++ ) {
324 $meta = mysql_fetch_field( $res->result, $i );
325 if( $field == $meta->name ) {
326 return new MySQLField($meta);
327 }
328 }
329 return false;
330 }
331
332 /**
333 * Get information about an index into an object
334 * Returns false if the index does not exist
335 *
336 * @return false|array
337 */
338 function indexInfo( $table, $index, $fname = 'DatabaseMysql::indexInfo' ) {
339 # SHOW INDEX works in MySQL 3.23.58, but SHOW INDEXES does not.
340 # SHOW INDEX should work for 3.x and up:
341 # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html
342 $table = $this->tableName( $table );
343 $index = $this->indexName( $index );
344 $sql = 'SHOW INDEX FROM ' . $table;
345 $res = $this->query( $sql, $fname );
346
347 if ( !$res ) {
348 return null;
349 }
350
351 $result = array();
352
353 foreach ( $res as $row ) {
354 if ( $row->Key_name == $index ) {
355 $result[] = $row;
356 }
357 }
358
359 return empty( $result ) ? false : $result;
360 }
361
362 /**
363 * @param $db
364 * @return bool
365 */
366 function selectDB( $db ) {
367 $this->mDBname = $db;
368 return mysql_select_db( $db, $this->mConn );
369 }
370
371 /**
372 * @param $s string
373 *
374 * @return string
375 */
376 function strencode( $s ) {
377 $sQuoted = mysql_real_escape_string( $s, $this->mConn );
378
379 if($sQuoted === false) {
380 $this->ping();
381 $sQuoted = mysql_real_escape_string( $s, $this->mConn );
382 }
383 return $sQuoted;
384 }
385
386 /**
387 * MySQL uses `backticks` for identifier quoting instead of the sql standard "double quotes".
388 *
389 * @param $s string
390 *
391 * @return string
392 */
393 public function addIdentifierQuotes( $s ) {
394 return "`" . $this->strencode( $s ) . "`";
395 }
396
397 /**
398 * @param $name string
399 * @return bool
400 */
401 public function isQuotedIdentifier( $name ) {
402 return strlen( $name ) && $name[0] == '`' && substr( $name, -1, 1 ) == '`';
403 }
404
405 /**
406 * @return bool
407 */
408 function ping() {
409 $ping = mysql_ping( $this->mConn );
410 if ( $ping ) {
411 return true;
412 }
413
414 mysql_close( $this->mConn );
415 $this->mOpened = false;
416 $this->mConn = false;
417 $this->open( $this->mServer, $this->mUser, $this->mPassword, $this->mDBname );
418 return true;
419 }
420
421 /**
422 * Returns slave lag.
423 *
424 * On MySQL 4.1.9 and later, this will do a SHOW SLAVE STATUS
425 *
426 * @return int
427 */
428 function getLag() {
429 if ( !is_null( $this->mFakeSlaveLag ) ) {
430 wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" );
431 return $this->mFakeSlaveLag;
432 }
433
434 return $this->getLagFromSlaveStatus();
435 }
436
437 /**
438 * @return bool|int
439 */
440 function getLagFromSlaveStatus() {
441 $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ );
442 if ( !$res ) {
443 return false;
444 }
445 $row = $res->fetchObject();
446 if ( !$row ) {
447 return false;
448 }
449 if ( strval( $row->Seconds_Behind_Master ) === '' ) {
450 return false;
451 } else {
452 return intval( $row->Seconds_Behind_Master );
453 }
454 }
455
456 /**
457 * @deprecated in 1.19, use getLagFromSlaveStatus
458 *
459 * @return bool|int
460 */
461 function getLagFromProcesslist() {
462 $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ );
463 if( !$res ) {
464 return false;
465 }
466 # Find slave SQL thread
467 foreach( $res as $row ) {
468 /* This should work for most situations - when default db
469 * for thread is not specified, it had no events executed,
470 * and therefore it doesn't know yet how lagged it is.
471 *
472 * Relay log I/O thread does not select databases.
473 */
474 if ( $row->User == 'system user' &&
475 $row->State != 'Waiting for master to send event' &&
476 $row->State != 'Connecting to master' &&
477 $row->State != 'Queueing master event to the relay log' &&
478 $row->State != 'Waiting for master update' &&
479 $row->State != 'Requesting binlog dump' &&
480 $row->State != 'Waiting to reconnect after a failed master event read' &&
481 $row->State != 'Reconnecting after a failed master event read' &&
482 $row->State != 'Registering slave on master'
483 ) {
484 # This is it, return the time (except -ve)
485 if ( $row->Time > 0x7fffffff ) {
486 return false;
487 } else {
488 return $row->Time;
489 }
490 }
491 }
492 return false;
493 }
494
495 /**
496 * Wait for the slave to catch up to a given master position.
497 *
498 * @param $pos DBMasterPos object
499 * @param $timeout Integer: the maximum number of seconds to wait for synchronisation
500 */
501 function masterPosWait( DBMasterPos $pos, $timeout ) {
502 $fname = 'DatabaseBase::masterPosWait';
503 wfProfileIn( $fname );
504
505 # Commit any open transactions
506 if ( $this->mTrxLevel ) {
507 $this->commit();
508 }
509
510 if ( !is_null( $this->mFakeSlaveLag ) ) {
511 $status = parent::masterPosWait( $pos, $timeout );
512 wfProfileOut( $fname );
513 return $status;
514 }
515
516 # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set
517 $encFile = $this->addQuotes( $pos->file );
518 $encPos = intval( $pos->pos );
519 $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)";
520 $res = $this->doQuery( $sql );
521
522 if ( $res && $row = $this->fetchRow( $res ) ) {
523 wfProfileOut( $fname );
524 return $row[0];
525 } else {
526 wfProfileOut( $fname );
527 return false;
528 }
529 }
530
531 /**
532 * Get the position of the master from SHOW SLAVE STATUS
533 *
534 * @return MySQLMasterPos|false
535 */
536 function getSlavePos() {
537 if ( !is_null( $this->mFakeSlaveLag ) ) {
538 return parent::getSlavePos();
539 }
540
541 $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' );
542 $row = $this->fetchObject( $res );
543
544 if ( $row ) {
545 $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos;
546 return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos );
547 } else {
548 return false;
549 }
550 }
551
552 /**
553 * Get the position of the master from SHOW MASTER STATUS
554 *
555 * @return MySQLMasterPos|false
556 */
557 function getMasterPos() {
558 if ( $this->mFakeMaster ) {
559 return parent::getMasterPos();
560 }
561
562 $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' );
563 $row = $this->fetchObject( $res );
564
565 if ( $row ) {
566 return new MySQLMasterPos( $row->File, $row->Position );
567 } else {
568 return false;
569 }
570 }
571
572 /**
573 * @return string
574 */
575 function getServerVersion() {
576 return mysql_get_server_info( $this->mConn );
577 }
578
579 /**
580 * @param $index
581 * @return string
582 */
583 function useIndexClause( $index ) {
584 return "FORCE INDEX (" . $this->indexName( $index ) . ")";
585 }
586
587 /**
588 * @return string
589 */
590 function lowPriorityOption() {
591 return 'LOW_PRIORITY';
592 }
593
594 /**
595 * @return string
596 */
597 public static function getSoftwareLink() {
598 return '[http://www.mysql.com/ MySQL]';
599 }
600
601 function standardSelectDistinct() {
602 return false;
603 }
604
605 public function setSessionOptions( array $options ) {
606 if ( isset( $options['connTimeout'] ) ) {
607 $timeout = (int)$options['connTimeout'];
608 $this->query( "SET net_read_timeout=$timeout" );
609 $this->query( "SET net_write_timeout=$timeout" );
610 }
611 }
612
613 public function lock( $lockName, $method, $timeout = 5 ) {
614 $lockName = $this->addQuotes( $lockName );
615 $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
616 $row = $this->fetchObject( $result );
617
618 if( $row->lockstatus == 1 ) {
619 return true;
620 } else {
621 wfDebug( __METHOD__." failed to acquire lock\n" );
622 return false;
623 }
624 }
625
626 /**
627 * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
628 */
629 public function unlock( $lockName, $method ) {
630 $lockName = $this->addQuotes( $lockName );
631 $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
632 $row = $this->fetchObject( $result );
633 return $row->lockstatus;
634 }
635
636 public function lockTables( $read, $write, $method, $lowPriority = true ) {
637 $items = array();
638
639 foreach( $write as $table ) {
640 $tbl = $this->tableName( $table ) .
641 ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
642 ' WRITE';
643 $items[] = $tbl;
644 }
645 foreach( $read as $table ) {
646 $items[] = $this->tableName( $table ) . ' READ';
647 }
648 $sql = "LOCK TABLES " . implode( ',', $items );
649 $this->query( $sql, $method );
650 }
651
652 public function unlockTables( $method ) {
653 $this->query( "UNLOCK TABLES", $method );
654 }
655
656 /**
657 * Get search engine class. All subclasses of this
658 * need to implement this if they wish to use searching.
659 *
660 * @return String
661 */
662 public function getSearchEngine() {
663 return 'SearchMySQL';
664 }
665
666 public function setBigSelects( $value = true ) {
667 if ( $value === 'default' ) {
668 if ( $this->mDefaultBigSelects === null ) {
669 # Function hasn't been called before so it must already be set to the default
670 return;
671 } else {
672 $value = $this->mDefaultBigSelects;
673 }
674 } elseif ( $this->mDefaultBigSelects === null ) {
675 $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
676 }
677 $encValue = $value ? '1' : '0';
678 $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
679 }
680
681 /**
682 * DELETE where the condition is a join. MySql uses multi-table deletes.
683 */
684 function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
685 if ( !$conds ) {
686 throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
687 }
688
689 $delTable = $this->tableName( $delTable );
690 $joinTable = $this->tableName( $joinTable );
691 $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
692
693 if ( $conds != '*' ) {
694 $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
695 }
696
697 return $this->query( $sql, $fname );
698 }
699
700 /**
701 * Determines if the last failure was due to a deadlock
702 *
703 * @return bool
704 */
705 function wasDeadlock() {
706 return $this->lastErrno() == 1213;
707 }
708
709 /**
710 * Determines if the last query error was something that should be dealt
711 * with by pinging the connection and reissuing the query
712 *
713 * @return bool
714 */
715 function wasErrorReissuable() {
716 return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
717 }
718
719 /**
720 * Determines if the last failure was due to the database being read-only.
721 *
722 * @return bool
723 */
724 function wasReadOnlyError() {
725 return $this->lastErrno() == 1223 ||
726 ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
727 }
728
729 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseMysql::duplicateTableStructure' ) {
730 $tmp = $temporary ? 'TEMPORARY ' : '';
731 $newName = $this->addIdentifierQuotes( $newName );
732 $oldName = $this->addIdentifierQuotes( $oldName );
733 $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
734 $this->query( $query, $fname );
735 }
736
737 /**
738 * List all tables on the database
739 *
740 * @param $prefix Only show tables with this prefix, e.g. mw_
741 * @param $fname String: calling function name
742 */
743 function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) {
744 $result = $this->query( "SHOW TABLES", $fname);
745
746 $endArray = array();
747
748 foreach( $result as $table ) {
749 $vars = get_object_vars($table);
750 $table = array_pop( $vars );
751
752 if( !$prefix || strpos( $table, $prefix ) === 0 ) {
753 $endArray[] = $table;
754 }
755 }
756
757 return $endArray;
758 }
759
760 /**
761 * @param $tableName
762 * @param $fName string
763 * @return bool|ResultWrapper
764 */
765 public function dropTable( $tableName, $fName = 'DatabaseMysql::dropTable' ) {
766 if( !$this->tableExists( $tableName, $fName ) ) {
767 return false;
768 }
769 return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
770 }
771
772 /**
773 * @return array
774 */
775 protected function getDefaultSchemaVars() {
776 $vars = parent::getDefaultSchemaVars();
777 $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
778 $vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $GLOBALS['wgDBTableOptions'] );
779 return $vars;
780 }
781
782 /**
783 * Get status information from SHOW STATUS in an associative array
784 *
785 * @return array
786 */
787 function getMysqlStatus( $which = "%" ) {
788 $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
789 $status = array();
790
791 foreach ( $res as $row ) {
792 $status[$row->Variable_name] = $row->Value;
793 }
794
795 return $status;
796 }
797
798 }
799
800 /**
801 * Legacy support: Database == DatabaseMysql
802 *
803 * @deprecated in 1.16
804 */
805 class Database extends DatabaseMysql {}
806
807 /**
808 * Utility class.
809 * @ingroup Database
810 */
811 class MySQLField implements Field {
812 private $name, $tablename, $default, $max_length, $nullable,
813 $is_pk, $is_unique, $is_multiple, $is_key, $type;
814
815 function __construct ( $info ) {
816 $this->name = $info->name;
817 $this->tablename = $info->table;
818 $this->default = $info->def;
819 $this->max_length = $info->max_length;
820 $this->nullable = !$info->not_null;
821 $this->is_pk = $info->primary_key;
822 $this->is_unique = $info->unique_key;
823 $this->is_multiple = $info->multiple_key;
824 $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
825 $this->type = $info->type;
826 }
827
828 /**
829 * @return string
830 */
831 function name() {
832 return $this->name;
833 }
834
835 /**
836 * @return string
837 */
838 function tableName() {
839 return $this->tableName;
840 }
841
842 function type() {
843 return $this->type;
844 }
845
846 /**
847 * @return bool
848 */
849 function isNullable() {
850 return $this->nullable;
851 }
852
853 function defaultValue() {
854 return $this->default;
855 }
856
857 /**
858 * @return bool
859 */
860 function isKey() {
861 return $this->is_key;
862 }
863
864 /**
865 * @return bool
866 */
867 function isMultipleKey() {
868 return $this->is_multiple;
869 }
870 }
871
872 class MySQLMasterPos implements DBMasterPos {
873 var $file, $pos;
874
875 function __construct( $file, $pos ) {
876 $this->file = $file;
877 $this->pos = $pos;
878 }
879
880 function __toString() {
881 return "{$this->file}/{$this->pos}";
882 }
883 }