Kill mysql4 specific code from DatabaseMysql
[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 setTimeout( $timeout ) {
606 $this->query( "SET net_read_timeout=$timeout" );
607 $this->query( "SET net_write_timeout=$timeout" );
608 }
609
610 public function lock( $lockName, $method, $timeout = 5 ) {
611 $lockName = $this->addQuotes( $lockName );
612 $result = $this->query( "SELECT GET_LOCK($lockName, $timeout) AS lockstatus", $method );
613 $row = $this->fetchObject( $result );
614
615 if( $row->lockstatus == 1 ) {
616 return true;
617 } else {
618 wfDebug( __METHOD__." failed to acquire lock\n" );
619 return false;
620 }
621 }
622
623 /**
624 * FROM MYSQL DOCS: http://dev.mysql.com/doc/refman/5.0/en/miscellaneous-functions.html#function_release-lock
625 */
626 public function unlock( $lockName, $method ) {
627 $lockName = $this->addQuotes( $lockName );
628 $result = $this->query( "SELECT RELEASE_LOCK($lockName) as lockstatus", $method );
629 $row = $this->fetchObject( $result );
630 return $row->lockstatus;
631 }
632
633 public function lockTables( $read, $write, $method, $lowPriority = true ) {
634 $items = array();
635
636 foreach( $write as $table ) {
637 $tbl = $this->tableName( $table ) .
638 ( $lowPriority ? ' LOW_PRIORITY' : '' ) .
639 ' WRITE';
640 $items[] = $tbl;
641 }
642 foreach( $read as $table ) {
643 $items[] = $this->tableName( $table ) . ' READ';
644 }
645 $sql = "LOCK TABLES " . implode( ',', $items );
646 $this->query( $sql, $method );
647 }
648
649 public function unlockTables( $method ) {
650 $this->query( "UNLOCK TABLES", $method );
651 }
652
653 /**
654 * Get search engine class. All subclasses of this
655 * need to implement this if they wish to use searching.
656 *
657 * @return String
658 */
659 public function getSearchEngine() {
660 return 'SearchMySQL';
661 }
662
663 public function setBigSelects( $value = true ) {
664 if ( $value === 'default' ) {
665 if ( $this->mDefaultBigSelects === null ) {
666 # Function hasn't been called before so it must already be set to the default
667 return;
668 } else {
669 $value = $this->mDefaultBigSelects;
670 }
671 } elseif ( $this->mDefaultBigSelects === null ) {
672 $this->mDefaultBigSelects = (bool)$this->selectField( false, '@@sql_big_selects' );
673 }
674 $encValue = $value ? '1' : '0';
675 $this->query( "SET sql_big_selects=$encValue", __METHOD__ );
676 }
677
678 /**
679 * DELETE where the condition is a join. MySql uses multi-table deletes.
680 */
681 function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabaseBase::deleteJoin' ) {
682 if ( !$conds ) {
683 throw new DBUnexpectedError( $this, 'DatabaseBase::deleteJoin() called with empty $conds' );
684 }
685
686 $delTable = $this->tableName( $delTable );
687 $joinTable = $this->tableName( $joinTable );
688 $sql = "DELETE $delTable FROM $delTable, $joinTable WHERE $delVar=$joinVar ";
689
690 if ( $conds != '*' ) {
691 $sql .= ' AND ' . $this->makeList( $conds, LIST_AND );
692 }
693
694 return $this->query( $sql, $fname );
695 }
696
697 /**
698 * Determines if the last failure was due to a deadlock
699 *
700 * @return bool
701 */
702 function wasDeadlock() {
703 return $this->lastErrno() == 1213;
704 }
705
706 /**
707 * Determines if the last query error was something that should be dealt
708 * with by pinging the connection and reissuing the query
709 *
710 * @return bool
711 */
712 function wasErrorReissuable() {
713 return $this->lastErrno() == 2013 || $this->lastErrno() == 2006;
714 }
715
716 /**
717 * Determines if the last failure was due to the database being read-only.
718 *
719 * @return bool
720 */
721 function wasReadOnlyError() {
722 return $this->lastErrno() == 1223 ||
723 ( $this->lastErrno() == 1290 && strpos( $this->lastError(), '--read-only' ) !== false );
724 }
725
726 function duplicateTableStructure( $oldName, $newName, $temporary = false, $fname = 'DatabaseMysql::duplicateTableStructure' ) {
727 $tmp = $temporary ? 'TEMPORARY ' : '';
728 $newName = $this->addIdentifierQuotes( $newName );
729 $oldName = $this->addIdentifierQuotes( $oldName );
730 $query = "CREATE $tmp TABLE $newName (LIKE $oldName)";
731 $this->query( $query, $fname );
732 }
733
734 /**
735 * List all tables on the database
736 *
737 * @param $prefix Only show tables with this prefix, e.g. mw_
738 * @param $fname String: calling function name
739 */
740 function listTables( $prefix = null, $fname = 'DatabaseMysql::listTables' ) {
741 $result = $this->query( "SHOW TABLES", $fname);
742
743 $endArray = array();
744
745 foreach( $result as $table ) {
746 $vars = get_object_vars($table);
747 $table = array_pop( $vars );
748
749 if( !$prefix || strpos( $table, $prefix ) === 0 ) {
750 $endArray[] = $table;
751 }
752 }
753
754 return $endArray;
755 }
756
757 /**
758 * @param $tableName
759 * @param $fName string
760 * @return bool|ResultWrapper
761 */
762 public function dropTable( $tableName, $fName = 'DatabaseMysql::dropTable' ) {
763 if( !$this->tableExists( $tableName, $fName ) ) {
764 return false;
765 }
766 return $this->query( "DROP TABLE IF EXISTS " . $this->tableName( $tableName ), $fName );
767 }
768
769 /**
770 * @return array
771 */
772 protected function getDefaultSchemaVars() {
773 $vars = parent::getDefaultSchemaVars();
774 $vars['wgDBTableOptions'] = str_replace( 'TYPE', 'ENGINE', $GLOBALS['wgDBTableOptions'] );
775 $vars['wgDBTableOptions'] = str_replace( 'CHARSET=mysql4', 'CHARSET=binary', $GLOBALS['wgDBTableOptions'] );
776 return $vars;
777 }
778
779 /**
780 * Get status information from SHOW STATUS in an associative array
781 *
782 * @return array
783 */
784 function getMysqlStatus( $which = "%" ) {
785 $res = $this->query( "SHOW STATUS LIKE '{$which}'" );
786 $status = array();
787
788 foreach ( $res as $row ) {
789 $status[$row->Variable_name] = $row->Value;
790 }
791
792 return $status;
793 }
794
795 }
796
797 /**
798 * Legacy support: Database == DatabaseMysql
799 *
800 * @deprecated in 1.16
801 */
802 class Database extends DatabaseMysql {}
803
804 /**
805 * Utility class.
806 * @ingroup Database
807 */
808 class MySQLField implements Field {
809 private $name, $tablename, $default, $max_length, $nullable,
810 $is_pk, $is_unique, $is_multiple, $is_key, $type;
811
812 function __construct ( $info ) {
813 $this->name = $info->name;
814 $this->tablename = $info->table;
815 $this->default = $info->def;
816 $this->max_length = $info->max_length;
817 $this->nullable = !$info->not_null;
818 $this->is_pk = $info->primary_key;
819 $this->is_unique = $info->unique_key;
820 $this->is_multiple = $info->multiple_key;
821 $this->is_key = ( $this->is_pk || $this->is_unique || $this->is_multiple );
822 $this->type = $info->type;
823 }
824
825 /**
826 * @return string
827 */
828 function name() {
829 return $this->name;
830 }
831
832 /**
833 * @return string
834 */
835 function tableName() {
836 return $this->tableName;
837 }
838
839 function type() {
840 return $this->type;
841 }
842
843 /**
844 * @return bool
845 */
846 function isNullable() {
847 return $this->nullable;
848 }
849
850 function defaultValue() {
851 return $this->default;
852 }
853
854 /**
855 * @return bool
856 */
857 function isKey() {
858 return $this->is_key;
859 }
860
861 /**
862 * @return bool
863 */
864 function isMultipleKey() {
865 return $this->is_multiple;
866 }
867 }
868
869 class MySQLMasterPos implements DBMasterPos {
870 var $file, $pos;
871
872 function __construct( $file, $pos ) {
873 $this->file = $file;
874 $this->pos = $pos;
875 }
876
877 function __toString() {
878 return "{$this->file}/{$this->pos}";
879 }
880 }