Index: trunk/phase3/RELEASE-NOTES-1.19 |
— | — | @@ -54,6 +54,8 @@ |
55 | 55 | * (bug 29441) Expose CapitalLinks config in JS to allow modules to properly |
56 | 56 | handle titles on case-sensitive wikis. |
57 | 57 | * (bug 29397) Implement mw.Title module in core. |
| 58 | +* In MySQL 4.1.9+ with replication enabled, get the slave lag from SHOW SLAVE |
| 59 | + STATUS instead of SHOW PROCESSLIST. |
58 | 60 | |
59 | 61 | === Bug fixes in 1.19 === |
60 | 62 | * (bug 28868) Show total pages in the subtitle of an image on the |
Index: trunk/phase3/includes/db/DatabaseSqlite.php |
— | — | @@ -532,12 +532,12 @@ |
533 | 533 | if ( isset( $rows[0] ) && is_array( $rows[0] ) ) { |
534 | 534 | $ret = true; |
535 | 535 | foreach ( $rows as $v ) { |
536 | | - if ( !parent::replace( $table, $uniqueIndexes, $v, "$fname/multi-row" ) ) { |
| 536 | + if ( !$this->nativeReplace( $table, $v, "$fname/multi-row" ) ) { |
537 | 537 | $ret = false; |
538 | 538 | } |
539 | 539 | } |
540 | 540 | } else { |
541 | | - $ret = parent::replace( $table, $uniqueIndexes, $rows, "$fname/single-row" ); |
| 541 | + $ret = $this->nativeReplace( $table, $rows, "$fname/single-row" ); |
542 | 542 | } |
543 | 543 | |
544 | 544 | return $ret; |
Index: trunk/phase3/includes/db/LoadBalancer.php |
— | — | @@ -50,9 +50,18 @@ |
51 | 51 | $this->mLaggedSlaveMode = false; |
52 | 52 | $this->mErrorConnection = false; |
53 | 53 | $this->mAllowLagged = false; |
54 | | - $this->mLoadMonitorClass = isset( $params['loadMonitor'] ) |
55 | | - ? $params['loadMonitor'] : 'LoadMonitor_MySQL'; |
56 | 54 | |
| 55 | + if ( isset( $params['loadMonitor'] ) ) { |
| 56 | + $this->mLoadMonitorClass = $params['loadMonitor']; |
| 57 | + } else { |
| 58 | + $master = reset( $params['servers'] ); |
| 59 | + if ( isset( $master['type'] ) && $master['type'] === 'mysql' ) { |
| 60 | + $this->mLoadMonitorClass = 'LoadMonitor_MySQL'; |
| 61 | + } else { |
| 62 | + $this->mLoadMonitorClass = 'LoadMonitor_Null'; |
| 63 | + } |
| 64 | + } |
| 65 | + |
57 | 66 | foreach( $params['servers'] as $i => $server ) { |
58 | 67 | $this->mLoads[$i] = $server['load']; |
59 | 68 | if ( isset( $server['groupLoads'] ) ) { |
Index: trunk/phase3/includes/db/LoadMonitor.php |
— | — | @@ -56,7 +56,22 @@ |
57 | 57 | function getLagTimes( $serverIndexes, $wiki ); |
58 | 58 | } |
59 | 59 | |
| 60 | +class LoadMonitor_Null implements LoadMonitor { |
| 61 | + function __construct( $parent ) { |
| 62 | + } |
60 | 63 | |
| 64 | + function scaleLoads( &$loads, $group = false, $wiki = false ) { |
| 65 | + } |
| 66 | + |
| 67 | + function postConnectionBackoff( $conn, $threshold ) { |
| 68 | + } |
| 69 | + |
| 70 | + function getLagTimes( $serverIndexes, $wiki ) { |
| 71 | + return array_fill_keys( $serverIndexes, 0 ); |
| 72 | + } |
| 73 | +} |
| 74 | + |
| 75 | + |
61 | 76 | /** |
62 | 77 | * Basic MySQL load monitor with no external dependencies |
63 | 78 | * Uses memcached to cache the replication lag for a short time |
— | — | @@ -150,7 +165,7 @@ |
151 | 166 | if ( !$threshold ) { |
152 | 167 | return 0; |
153 | 168 | } |
154 | | - $status = $conn->getStatus("Thread%"); |
| 169 | + $status = $conn->getMysqlStatus("Thread%"); |
155 | 170 | if ( $status['Threads_running'] > $threshold ) { |
156 | 171 | return $status['Threads_connected']; |
157 | 172 | } else { |
Index: trunk/phase3/includes/db/DatabaseUtility.php |
— | — | @@ -259,3 +259,10 @@ |
260 | 260 | return $this->str; |
261 | 261 | } |
262 | 262 | } |
| 263 | + |
| 264 | +/** |
| 265 | + * An object representing a master or slave position in a replicated setup. |
| 266 | + */ |
| 267 | +interface DBMasterPos { |
| 268 | +} |
| 269 | + |
Index: trunk/phase3/includes/db/DatabaseMysql.php |
— | — | @@ -241,6 +241,10 @@ |
242 | 242 | |
243 | 243 | function affectedRows() { return mysql_affected_rows( $this->mConn ); } |
244 | 244 | |
| 245 | + function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMysql::replace' ) { |
| 246 | + return $this->nativeReplace( $table, $rows, $fname ); |
| 247 | + } |
| 248 | + |
245 | 249 | /** |
246 | 250 | * Estimate rows in dataset |
247 | 251 | * Returns estimated count, based on EXPLAIN output |
— | — | @@ -348,7 +352,11 @@ |
349 | 353 | |
350 | 354 | /** |
351 | 355 | * Returns slave lag. |
352 | | - * At the moment, this will only work if the DB user has the PROCESS privilege |
| 356 | + * |
| 357 | + * On MySQL 4.1.9 and later, this will do a SHOW SLAVE STATUS. On earlier |
| 358 | + * versions of MySQL, it uses SHOW PROCESSLIST, which requires the PROCESS |
| 359 | + * privilege. |
| 360 | + * |
353 | 361 | * @result int |
354 | 362 | */ |
355 | 363 | function getLag() { |
— | — | @@ -356,6 +364,31 @@ |
357 | 365 | wfDebug( "getLag: fake slave lagged {$this->mFakeSlaveLag} seconds\n" ); |
358 | 366 | return $this->mFakeSlaveLag; |
359 | 367 | } |
| 368 | + |
| 369 | + if ( version_compare( $this->getServerVersion(), '4.1.9', '>=' ) ) { |
| 370 | + return $this->getLagFromSlaveStatus(); |
| 371 | + } else { |
| 372 | + return $this->getLagFromProcesslist(); |
| 373 | + } |
| 374 | + } |
| 375 | + |
| 376 | + function getLagFromSlaveStatus() { |
| 377 | + $res = $this->query( 'SHOW SLAVE STATUS', __METHOD__ ); |
| 378 | + if ( !$res ) { |
| 379 | + return false; |
| 380 | + } |
| 381 | + $row = $res->fetchObject(); |
| 382 | + if ( !$row ) { |
| 383 | + return false; |
| 384 | + } |
| 385 | + if ( strval( $row->Seconds_Behind_Master ) === '' ) { |
| 386 | + return false; |
| 387 | + } else { |
| 388 | + return intval( $row->Seconds_Behind_Master ); |
| 389 | + } |
| 390 | + } |
| 391 | + |
| 392 | + function getLagFromProcesslist() { |
360 | 393 | $res = $this->query( 'SHOW PROCESSLIST', __METHOD__ ); |
361 | 394 | if( !$res ) { |
362 | 395 | return false; |
— | — | @@ -388,7 +421,84 @@ |
389 | 422 | } |
390 | 423 | return false; |
391 | 424 | } |
| 425 | + |
| 426 | + /** |
| 427 | + * Wait for the slave to catch up to a given master position. |
| 428 | + * |
| 429 | + * @param $pos DBMasterPos object |
| 430 | + * @param $timeout Integer: the maximum number of seconds to wait for synchronisation |
| 431 | + */ |
| 432 | + function masterPosWait( DBMasterPos $pos, $timeout ) { |
| 433 | + $fname = 'DatabaseBase::masterPosWait'; |
| 434 | + wfProfileIn( $fname ); |
392 | 435 | |
| 436 | + # Commit any open transactions |
| 437 | + if ( $this->mTrxLevel ) { |
| 438 | + $this->commit(); |
| 439 | + } |
| 440 | + |
| 441 | + if ( !is_null( $this->mFakeSlaveLag ) ) { |
| 442 | + $status = parent::masterPosWait( $pos, $timeout ); |
| 443 | + wfProfileOut( $fname ); |
| 444 | + return $status; |
| 445 | + } |
| 446 | + |
| 447 | + # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set |
| 448 | + $encFile = $this->addQuotes( $pos->file ); |
| 449 | + $encPos = intval( $pos->pos ); |
| 450 | + $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)"; |
| 451 | + $res = $this->doQuery( $sql ); |
| 452 | + |
| 453 | + if ( $res && $row = $this->fetchRow( $res ) ) { |
| 454 | + wfProfileOut( $fname ); |
| 455 | + return $row[0]; |
| 456 | + } else { |
| 457 | + wfProfileOut( $fname ); |
| 458 | + return false; |
| 459 | + } |
| 460 | + } |
| 461 | + |
| 462 | + /** |
| 463 | + * Get the position of the master from SHOW SLAVE STATUS |
| 464 | + * |
| 465 | + * @return MySQLMasterPos|false |
| 466 | + */ |
| 467 | + function getSlavePos() { |
| 468 | + if ( !is_null( $this->mFakeSlaveLag ) ) { |
| 469 | + return parent::getSlavePos(); |
| 470 | + } |
| 471 | + |
| 472 | + $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' ); |
| 473 | + $row = $this->fetchObject( $res ); |
| 474 | + |
| 475 | + if ( $row ) { |
| 476 | + $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos; |
| 477 | + return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos ); |
| 478 | + } else { |
| 479 | + return false; |
| 480 | + } |
| 481 | + } |
| 482 | + |
| 483 | + /** |
| 484 | + * Get the position of the master from SHOW MASTER STATUS |
| 485 | + * |
| 486 | + * @return MySQLMasterPos|false |
| 487 | + */ |
| 488 | + function getMasterPos() { |
| 489 | + if ( $this->mFakeMaster ) { |
| 490 | + return parent::getMasterPos(); |
| 491 | + } |
| 492 | + |
| 493 | + $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' ); |
| 494 | + $row = $this->fetchObject( $res ); |
| 495 | + |
| 496 | + if ( $row ) { |
| 497 | + return new MySQLMasterPos( $row->File, $row->Position ); |
| 498 | + } else { |
| 499 | + return false; |
| 500 | + } |
| 501 | + } |
| 502 | + |
393 | 503 | function getServerVersion() { |
394 | 504 | return mysql_get_server_info( $this->mConn ); |
395 | 505 | } |
— | — | @@ -587,6 +697,23 @@ |
588 | 698 | $vars['wgDBTableOptions'] = $GLOBALS['wgDBTableOptions']; |
589 | 699 | return $vars; |
590 | 700 | } |
| 701 | + |
| 702 | + /** |
| 703 | + * Get status information from SHOW STATUS in an associative array |
| 704 | + * |
| 705 | + * @return array |
| 706 | + */ |
| 707 | + function getMysqlStatus( $which = "%" ) { |
| 708 | + $res = $this->query( "SHOW STATUS LIKE '{$which}'" ); |
| 709 | + $status = array(); |
| 710 | + |
| 711 | + foreach ( $res as $row ) { |
| 712 | + $status[$row->Variable_name] = $row->Value; |
| 713 | + } |
| 714 | + |
| 715 | + return $status; |
| 716 | + } |
| 717 | + |
591 | 718 | } |
592 | 719 | |
593 | 720 | /** |
— | — | @@ -644,7 +771,7 @@ |
645 | 772 | } |
646 | 773 | } |
647 | 774 | |
648 | | -class MySQLMasterPos { |
| 775 | +class MySQLMasterPos implements DBMasterPos { |
649 | 776 | var $file, $pos; |
650 | 777 | |
651 | 778 | function __construct( $file, $pos ) { |
Index: trunk/phase3/includes/db/DatabaseOracle.php |
— | — | @@ -707,66 +707,6 @@ |
708 | 708 | return ( isset( $this->sequenceData[$table] ) ) ? $this->sequenceData[$table] : false; |
709 | 709 | } |
710 | 710 | |
711 | | - /** |
712 | | - * REPLACE query wrapper |
713 | | - * Oracle simulates this with a DELETE followed by INSERT |
714 | | - * $row is the row to insert, an associative array |
715 | | - * $uniqueIndexes is an array of indexes. Each element may be either a |
716 | | - * field name or an array of field names |
717 | | - * |
718 | | - * It may be more efficient to leave off unique indexes which are unlikely to collide. |
719 | | - * However if you do this, you run the risk of encountering errors which wouldn't have |
720 | | - * occurred in MySQL. |
721 | | - * |
722 | | - * @param $table String: table name |
723 | | - * @param $uniqueIndexes Array: array of indexes. Each element may be |
724 | | - * either a field name or an array of field names |
725 | | - * @param $rows Array: rows to insert to $table |
726 | | - * @param $fname String: function name, you can use __METHOD__ here |
727 | | - */ |
728 | | - function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseOracle::replace' ) { |
729 | | - $table = $this->tableName( $table ); |
730 | | - |
731 | | - if ( count( $rows ) == 0 ) { |
732 | | - return; |
733 | | - } |
734 | | - |
735 | | - # Single row case |
736 | | - if ( !is_array( reset( $rows ) ) ) { |
737 | | - $rows = array( $rows ); |
738 | | - } |
739 | | - |
740 | | - $sequenceData = $this->getSequenceData( $table ); |
741 | | - |
742 | | - foreach ( $rows as $row ) { |
743 | | - # Delete rows which collide |
744 | | - if ( $uniqueIndexes ) { |
745 | | - $deleteConds = array(); |
746 | | - foreach ( $uniqueIndexes as $key=>$index ) { |
747 | | - if ( is_array( $index ) ) { |
748 | | - $deleteConds2 = array(); |
749 | | - foreach ( $index as $col ) { |
750 | | - $deleteConds2[$col] = $row[$col]; |
751 | | - } |
752 | | - $deleteConds[$key] = $this->makeList( $deleteConds2, LIST_AND ); |
753 | | - } else { |
754 | | - $deleteConds[$index] = $row[$index]; |
755 | | - } |
756 | | - } |
757 | | - $deleteConds = array( $this->makeList( $deleteConds, LIST_OR ) ); |
758 | | - $this->delete( $table, $deleteConds, $fname ); |
759 | | - } |
760 | | - |
761 | | - |
762 | | - if ( $sequenceData !== false && !isset( $row[$sequenceData['column']] ) ) { |
763 | | - $row[$sequenceData['column']] = $this->nextSequenceValue( $sequenceData['sequence'] ); |
764 | | - } |
765 | | - |
766 | | - # Now insert the row |
767 | | - $this->insert( $table, $row, $fname ); |
768 | | - } |
769 | | - } |
770 | | - |
771 | 711 | # Returns the size of a text field, or -1 for "unlimited" |
772 | 712 | function textFieldSize( $table, $field ) { |
773 | 713 | $fieldInfoData = $this->fieldInfo( $table, $field ); |
Index: trunk/phase3/includes/db/DatabasePostgres.php |
— | — | @@ -647,66 +647,6 @@ |
648 | 648 | return $currval; |
649 | 649 | } |
650 | 650 | |
651 | | - /** |
652 | | - * REPLACE query wrapper |
653 | | - * Postgres simulates this with a DELETE followed by INSERT |
654 | | - * $row is the row to insert, an associative array |
655 | | - * $uniqueIndexes is an array of indexes. Each element may be either a |
656 | | - * field name or an array of field names |
657 | | - * |
658 | | - * It may be more efficient to leave off unique indexes which are unlikely to collide. |
659 | | - * However if you do this, you run the risk of encountering errors which wouldn't have |
660 | | - * occurred in MySQL |
661 | | - */ |
662 | | - function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabasePostgres::replace' ) { |
663 | | - $table = $this->tableName( $table ); |
664 | | - |
665 | | - if ( count( $rows ) == 0 ) { |
666 | | - return; |
667 | | - } |
668 | | - |
669 | | - # Single row case |
670 | | - if ( !is_array( reset( $rows ) ) ) { |
671 | | - $rows = array( $rows ); |
672 | | - } |
673 | | - |
674 | | - foreach( $rows as $row ) { |
675 | | - # Delete rows which collide |
676 | | - if ( $uniqueIndexes ) { |
677 | | - $sql = "DELETE FROM $table WHERE "; |
678 | | - $first = true; |
679 | | - foreach ( $uniqueIndexes as $index ) { |
680 | | - if ( $first ) { |
681 | | - $first = false; |
682 | | - $sql .= '('; |
683 | | - } else { |
684 | | - $sql .= ') OR ('; |
685 | | - } |
686 | | - if ( is_array( $index ) ) { |
687 | | - $first2 = true; |
688 | | - foreach ( $index as $col ) { |
689 | | - if ( $first2 ) { |
690 | | - $first2 = false; |
691 | | - } else { |
692 | | - $sql .= ' AND '; |
693 | | - } |
694 | | - $sql .= $col.'=' . $this->addQuotes( $row[$col] ); |
695 | | - } |
696 | | - } else { |
697 | | - $sql .= $index.'=' . $this->addQuotes( $row[$index] ); |
698 | | - } |
699 | | - } |
700 | | - $sql .= ')'; |
701 | | - $this->query( $sql, $fname ); |
702 | | - } |
703 | | - |
704 | | - # Now insert the row |
705 | | - $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' . |
706 | | - $this->makeList( $row, LIST_COMMA ) . ')'; |
707 | | - $this->query( $sql, $fname ); |
708 | | - } |
709 | | - } |
710 | | - |
711 | 651 | # Returns the size of a text field, or -1 for "unlimited" |
712 | 652 | function textFieldSize( $table, $field ) { |
713 | 653 | $table = $this->tableName( $table ); |
Index: trunk/phase3/includes/db/DatabaseIbm_db2.php |
— | — | @@ -555,45 +555,6 @@ |
556 | 556 | } |
557 | 557 | |
558 | 558 | /** |
559 | | - * Create tables, stored procedures, and so on |
560 | | - */ |
561 | | - public function setup_database() { |
562 | | - try { |
563 | | - // TODO: switch to root login if available |
564 | | - |
565 | | - // Switch into the correct namespace |
566 | | - $this->applySchema(); |
567 | | - $this->begin(); |
568 | | - |
569 | | - $res = $this->sourceFile( "../maintenance/ibm_db2/tables.sql" ); |
570 | | - if ( $res !== true ) { |
571 | | - print ' <b>FAILED</b>: ' . htmlspecialchars( $res ) . '</li>'; |
572 | | - } else { |
573 | | - print ' done</li>'; |
574 | | - } |
575 | | - $res = $this->sourceFile( "../maintenance/ibm_db2/foreignkeys.sql" ); |
576 | | - if ( $res !== true ) { |
577 | | - print ' <b>FAILED</b>: ' . htmlspecialchars( $res ) . '</li>'; |
578 | | - } else { |
579 | | - print '<li>Foreign keys done</li>'; |
580 | | - } |
581 | | - |
582 | | - // TODO: populate interwiki links |
583 | | - |
584 | | - if ( $this->lastError() ) { |
585 | | - $this->installPrint( |
586 | | - 'Errors encountered during table creation -- rolled back' ); |
587 | | - $this->installPrint( 'Please install again' ); |
588 | | - $this->rollback(); |
589 | | - } else { |
590 | | - $this->commit(); |
591 | | - } |
592 | | - } catch ( MWException $mwe ) { |
593 | | - print "<br><pre>$mwe</pre><br>"; |
594 | | - } |
595 | | - } |
596 | | - |
597 | | - /** |
598 | 559 | * Escapes strings |
599 | 560 | * Doesn't escape numbers |
600 | 561 | * |
— | — | @@ -1068,63 +1029,6 @@ |
1069 | 1030 | } |
1070 | 1031 | |
1071 | 1032 | /** |
1072 | | - * Simulates REPLACE with a DELETE followed by INSERT |
1073 | | - * @param $table Object |
1074 | | - * @param $uniqueIndexes Array consisting of indexes and arrays of indexes |
1075 | | - * @param $rows Array: rows to insert |
1076 | | - * @param $fname String: name of the function for profiling |
1077 | | - * @return nothing |
1078 | | - */ |
1079 | | - function replace( $table, $uniqueIndexes, $rows, |
1080 | | - $fname = 'DatabaseIbm_db2::replace' ) |
1081 | | - { |
1082 | | - $table = $this->tableName( $table ); |
1083 | | - |
1084 | | - if ( count( $rows )==0 ) { |
1085 | | - return; |
1086 | | - } |
1087 | | - |
1088 | | - # Single row case |
1089 | | - if ( !is_array( reset( $rows ) ) ) { |
1090 | | - $rows = array( $rows ); |
1091 | | - } |
1092 | | - |
1093 | | - foreach( $rows as $row ) { |
1094 | | - # Delete rows which collide |
1095 | | - if ( $uniqueIndexes ) { |
1096 | | - $sql = "DELETE FROM $table WHERE "; |
1097 | | - $first = true; |
1098 | | - foreach ( $uniqueIndexes as $index ) { |
1099 | | - if ( $first ) { |
1100 | | - $first = false; |
1101 | | - $sql .= '( '; |
1102 | | - } else { |
1103 | | - $sql .= ' ) OR ( '; |
1104 | | - } |
1105 | | - if ( is_array( $index ) ) { |
1106 | | - $first2 = true; |
1107 | | - foreach ( $index as $col ) { |
1108 | | - if ( $first2 ) { |
1109 | | - $first2 = false; |
1110 | | - } else { |
1111 | | - $sql .= ' AND '; |
1112 | | - } |
1113 | | - $sql .= $col . '=' . $this->addQuotes( $row[$col] ); |
1114 | | - } |
1115 | | - } else { |
1116 | | - $sql .= $index . '=' . $this->addQuotes( $row[$index] ); |
1117 | | - } |
1118 | | - } |
1119 | | - $sql .= ' )'; |
1120 | | - $this->query( $sql, $fname ); |
1121 | | - } |
1122 | | - |
1123 | | - # Now insert the row |
1124 | | - $this->insert($table, $row); |
1125 | | - } |
1126 | | - } |
1127 | | - |
1128 | | - /** |
1129 | 1033 | * Returns the number of rows in the result set |
1130 | 1034 | * Has to be called right after the corresponding select query |
1131 | 1035 | * @param $res Object result set |
— | — | @@ -1347,14 +1251,6 @@ |
1348 | 1252 | ###################################### |
1349 | 1253 | /** |
1350 | 1254 | * Not implemented |
1351 | | - * @return string '' |
1352 | | - */ |
1353 | | - public function getStatus( $which = '%' ) { |
1354 | | - $this->installPrint( 'Not implemented for DB2: getStatus()' ); |
1355 | | - return ''; |
1356 | | - } |
1357 | | - /** |
1358 | | - * Not implemented |
1359 | 1255 | * @return string $sql |
1360 | 1256 | */ |
1361 | 1257 | public function limitResultForUpdate( $sql, $num ) { |
Index: trunk/phase3/includes/db/Database.php |
— | — | @@ -2215,22 +2215,86 @@ |
2216 | 2216 | } |
2217 | 2217 | |
2218 | 2218 | /** |
2219 | | - * REPLACE query wrapper |
2220 | | - * PostgreSQL simulates this with a DELETE followed by INSERT |
2221 | | - * $row is the row to insert, an associative array |
2222 | | - * $uniqueIndexes is an array of indexes. Each element may be either a |
2223 | | - * field name or an array of field names |
| 2219 | + * REPLACE query wrapper. |
2224 | 2220 | * |
2225 | | - * It may be more efficient to leave off unique indexes which are unlikely to collide. |
2226 | | - * However if you do this, you run the risk of encountering errors which wouldn't have |
2227 | | - * occurred in MySQL |
| 2221 | + * REPLACE is a very handy MySQL extension, which functions like an INSERT |
| 2222 | + * except that when there is a duplicate key error, the old row is deleted |
| 2223 | + * and the new row is inserted in its place. |
2228 | 2224 | * |
| 2225 | + * We simulate this with standard SQL with a DELETE followed by INSERT. To |
| 2226 | + * perform the delete, we need to know what the unique indexes are so that |
| 2227 | + * we know how to find the conflicting rows. |
| 2228 | + * |
| 2229 | + * It may be more efficient to leave off unique indexes which are unlikely |
| 2230 | + * to collide. However if you do this, you run the risk of encountering |
| 2231 | + * errors which wouldn't have occurred in MySQL. |
| 2232 | + * |
| 2233 | + * @param $rows Can be either a single row to insert, or multiple rows, |
| 2234 | + * in the same format as for DatabaseBase::insert() |
| 2235 | + * @param $uniqueIndexes is an array of indexes. Each element may be either |
| 2236 | + * a field name or an array of field names |
| 2237 | + * |
2229 | 2238 | * @param $table String: The table to replace the row(s) in. |
2230 | 2239 | * @param $uniqueIndexes Array: An associative array of indexes |
2231 | 2240 | * @param $rows Array: Array of rows to replace |
2232 | 2241 | * @param $fname String: Calling function name (use __METHOD__) for logs/profiling |
2233 | 2242 | */ |
2234 | 2243 | function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseBase::replace' ) { |
| 2244 | + $quotedTable = $this->tableName( $table ); |
| 2245 | + |
| 2246 | + if ( count( $rows ) == 0 ) { |
| 2247 | + return; |
| 2248 | + } |
| 2249 | + |
| 2250 | + # Single row case |
| 2251 | + if ( !is_array( reset( $rows ) ) ) { |
| 2252 | + $rows = array( $rows ); |
| 2253 | + } |
| 2254 | + |
| 2255 | + foreach( $rows as $row ) { |
| 2256 | + # Delete rows which collide |
| 2257 | + if ( $uniqueIndexes ) { |
| 2258 | + $sql = "DELETE FROM $quotedTable WHERE "; |
| 2259 | + $first = true; |
| 2260 | + foreach ( $uniqueIndexes as $index ) { |
| 2261 | + if ( $first ) { |
| 2262 | + $first = false; |
| 2263 | + $sql .= '( '; |
| 2264 | + } else { |
| 2265 | + $sql .= ' ) OR ( '; |
| 2266 | + } |
| 2267 | + if ( is_array( $index ) ) { |
| 2268 | + $first2 = true; |
| 2269 | + foreach ( $index as $col ) { |
| 2270 | + if ( $first2 ) { |
| 2271 | + $first2 = false; |
| 2272 | + } else { |
| 2273 | + $sql .= ' AND '; |
| 2274 | + } |
| 2275 | + $sql .= $col . '=' . $this->addQuotes( $row[$col] ); |
| 2276 | + } |
| 2277 | + } else { |
| 2278 | + $sql .= $index . '=' . $this->addQuotes( $row[$index] ); |
| 2279 | + } |
| 2280 | + } |
| 2281 | + $sql .= ' )'; |
| 2282 | + $this->query( $sql, $fname ); |
| 2283 | + } |
| 2284 | + |
| 2285 | + # Now insert the row |
| 2286 | + $this->insert( $table, $row ); |
| 2287 | + } |
| 2288 | + } |
| 2289 | + |
| 2290 | + /** |
| 2291 | + * REPLACE query wrapper for MySQL and SQLite, which have a native REPLACE |
| 2292 | + * statement. |
| 2293 | + * |
| 2294 | + * @param $table Table name |
| 2295 | + * @param $rows Rows to insert |
| 2296 | + * @param $fname Caller function name |
| 2297 | + */ |
| 2298 | + protected function nativeReplace( $table, $rows, $fname ) { |
2235 | 2299 | $table = $this->tableName( $table ); |
2236 | 2300 | |
2237 | 2301 | # Single row case |
— | — | @@ -2584,20 +2648,20 @@ |
2585 | 2649 | } |
2586 | 2650 | |
2587 | 2651 | /** |
2588 | | - * Do a SELECT MASTER_POS_WAIT() |
| 2652 | + * Wait for the slave to catch up to a given master position. |
2589 | 2653 | * |
2590 | | - * @param $pos MySQLMasterPos object |
2591 | | - * @param $timeout Integer: the maximum number of seconds to wait for synchronisation |
| 2654 | + * @param $pos DBMasterPos object |
| 2655 | + * @param $timeout Integer: the maximum number of seconds to wait for |
| 2656 | + * synchronisation |
| 2657 | + * |
| 2658 | + * @return An integer: zero if the slave was past that position already, |
| 2659 | + * greater than zero if we waited for some period of time, less than |
| 2660 | + * zero if we timed out. |
2592 | 2661 | */ |
2593 | | - function masterPosWait( MySQLMasterPos $pos, $timeout ) { |
| 2662 | + function masterPosWait( DBMasterPos $pos, $timeout ) { |
2594 | 2663 | $fname = 'DatabaseBase::masterPosWait'; |
2595 | 2664 | wfProfileIn( $fname ); |
2596 | 2665 | |
2597 | | - # Commit any open transactions |
2598 | | - if ( $this->mTrxLevel ) { |
2599 | | - $this->commit(); |
2600 | | - } |
2601 | | - |
2602 | 2666 | if ( !is_null( $this->mFakeSlaveLag ) ) { |
2603 | 2667 | $wait = intval( ( $pos->pos - microtime( true ) + $this->mFakeSlaveLag ) * 1e6 ); |
2604 | 2668 | |
— | — | @@ -2617,59 +2681,36 @@ |
2618 | 2682 | } |
2619 | 2683 | } |
2620 | 2684 | |
2621 | | - # Call doQuery() directly, to avoid opening a transaction if DBO_TRX is set |
2622 | | - $encFile = $this->addQuotes( $pos->file ); |
2623 | | - $encPos = intval( $pos->pos ); |
2624 | | - $sql = "SELECT MASTER_POS_WAIT($encFile, $encPos, $timeout)"; |
2625 | | - $res = $this->doQuery( $sql ); |
| 2685 | + wfProfileOut( $fname ); |
2626 | 2686 | |
2627 | | - if ( $res && $row = $this->fetchRow( $res ) ) { |
2628 | | - wfProfileOut( $fname ); |
2629 | | - return $row[0]; |
2630 | | - } else { |
2631 | | - wfProfileOut( $fname ); |
2632 | | - return false; |
2633 | | - } |
| 2687 | + # Real waits are implemented in the subclass. |
| 2688 | + return 0; |
2634 | 2689 | } |
2635 | 2690 | |
2636 | 2691 | /** |
2637 | | - * Get the position of the master from SHOW SLAVE STATUS |
| 2692 | + * Get the replication position of this slave |
2638 | 2693 | * |
2639 | | - * @return MySQLMasterPos|false |
| 2694 | + * @return DBMasterPos, or false if this is not a slave. |
2640 | 2695 | */ |
2641 | 2696 | function getSlavePos() { |
2642 | 2697 | if ( !is_null( $this->mFakeSlaveLag ) ) { |
2643 | 2698 | $pos = new MySQLMasterPos( 'fake', microtime( true ) - $this->mFakeSlaveLag ); |
2644 | 2699 | wfDebug( __METHOD__ . ": fake slave pos = $pos\n" ); |
2645 | 2700 | return $pos; |
2646 | | - } |
2647 | | - |
2648 | | - $res = $this->query( 'SHOW SLAVE STATUS', 'DatabaseBase::getSlavePos' ); |
2649 | | - $row = $this->fetchObject( $res ); |
2650 | | - |
2651 | | - if ( $row ) { |
2652 | | - $pos = isset( $row->Exec_master_log_pos ) ? $row->Exec_master_log_pos : $row->Exec_Master_Log_Pos; |
2653 | | - return new MySQLMasterPos( $row->Relay_Master_Log_File, $pos ); |
2654 | 2701 | } else { |
| 2702 | + # Stub |
2655 | 2703 | return false; |
2656 | 2704 | } |
2657 | 2705 | } |
2658 | 2706 | |
2659 | 2707 | /** |
2660 | | - * Get the position of the master from SHOW MASTER STATUS |
| 2708 | + * Get the position of this master |
2661 | 2709 | * |
2662 | | - * @return MySQLMasterPos|false |
| 2710 | + * @return DBMasterPos, or false if this is not a master |
2663 | 2711 | */ |
2664 | 2712 | function getMasterPos() { |
2665 | 2713 | if ( $this->mFakeMaster ) { |
2666 | 2714 | return new MySQLMasterPos( 'fake', microtime( true ) ); |
2667 | | - } |
2668 | | - |
2669 | | - $res = $this->query( 'SHOW MASTER STATUS', 'DatabaseBase::getMasterPos' ); |
2670 | | - $row = $this->fetchObject( $res ); |
2671 | | - |
2672 | | - if ( $row ) { |
2673 | | - return new MySQLMasterPos( $row->File, $row->Position ); |
2674 | 2715 | } else { |
2675 | 2716 | return false; |
2676 | 2717 | } |
— | — | @@ -2818,22 +2859,6 @@ |
2819 | 2860 | } |
2820 | 2861 | |
2821 | 2862 | /** |
2822 | | - * Get status information from SHOW STATUS in an associative array |
2823 | | - * |
2824 | | - * @return array |
2825 | | - */ |
2826 | | - function getStatus( $which = "%" ) { |
2827 | | - $res = $this->query( "SHOW STATUS LIKE '{$which}'" ); |
2828 | | - $status = array(); |
2829 | | - |
2830 | | - foreach ( $res as $row ) { |
2831 | | - $status[$row->Variable_name] = $row->Value; |
2832 | | - } |
2833 | | - |
2834 | | - return $status; |
2835 | | - } |
2836 | | - |
2837 | | - /** |
2838 | 2863 | * Return the maximum number of items allowed in a list, or 0 for unlimited. |
2839 | 2864 | * |
2840 | 2865 | * return int |
Index: trunk/phase3/includes/db/DatabaseMssql.php |
— | — | @@ -539,65 +539,6 @@ |
540 | 540 | } |
541 | 541 | } |
542 | 542 | |
543 | | - |
544 | | - # REPLACE query wrapper |
545 | | - # MSSQL simulates this with a DELETE followed by INSERT |
546 | | - # $row is the row to insert, an associative array |
547 | | - # $uniqueIndexes is an array of indexes. Each element may be either a |
548 | | - # field name or an array of field names |
549 | | - # |
550 | | - # It may be more efficient to leave off unique indexes which are unlikely to collide. |
551 | | - # However if you do this, you run the risk of encountering errors which wouldn't have |
552 | | - # occurred in MySQL |
553 | | - function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseMssql::replace' ) { |
554 | | - $table = $this->tableName( $table ); |
555 | | - |
556 | | - if ( count( $rows ) == 0 ) { |
557 | | - return; |
558 | | - } |
559 | | - |
560 | | - # Single row case |
561 | | - if ( !is_array( reset( $rows ) ) ) { |
562 | | - $rows = array( $rows ); |
563 | | - } |
564 | | - |
565 | | - foreach ( $rows as $row ) { |
566 | | - # Delete rows which collide |
567 | | - if ( $uniqueIndexes ) { |
568 | | - $sql = "DELETE FROM $table WHERE "; |
569 | | - $first = true; |
570 | | - foreach ( $uniqueIndexes as $index ) { |
571 | | - if ( $first ) { |
572 | | - $first = false; |
573 | | - $sql .= "("; |
574 | | - } else { |
575 | | - $sql .= ') OR ('; |
576 | | - } |
577 | | - if ( is_array( $index ) ) { |
578 | | - $first2 = true; |
579 | | - foreach ( $index as $col ) { |
580 | | - if ( $first2 ) { |
581 | | - $first2 = false; |
582 | | - } else { |
583 | | - $sql .= ' AND '; |
584 | | - } |
585 | | - $sql .= $col . '=' . $this->addQuotes( $row[$col] ); |
586 | | - } |
587 | | - } else { |
588 | | - $sql .= $index . '=' . $this->addQuotes( $row[$index] ); |
589 | | - } |
590 | | - } |
591 | | - $sql .= ')'; |
592 | | - $this->query( $sql, $fname ); |
593 | | - } |
594 | | - |
595 | | - # Now insert the row |
596 | | - $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) . ') VALUES (' . |
597 | | - $this->makeList( $row, LIST_COMMA ) . ')'; |
598 | | - $this->query( $sql, $fname ); |
599 | | - } |
600 | | - } |
601 | | - |
602 | 543 | # Returns the size of a text field, or -1 for "unlimited" |
603 | 544 | function textFieldSize( $table, $field ) { |
604 | 545 | $table = $this->tableName( $table ); |
— | — | @@ -762,48 +703,6 @@ |
763 | 704 | $this->mTrxLevel = 0; |
764 | 705 | } |
765 | 706 | |
766 | | - function setup_database() { |
767 | | - global $wgDBuser; |
768 | | - |
769 | | - // Make sure that we can write to the correct schema |
770 | | - $ctest = "mediawiki_test_table"; |
771 | | - if ( $this->tableExists( $ctest ) ) { |
772 | | - $this->doQuery( "DROP TABLE $ctest" ); |
773 | | - } |
774 | | - $SQL = "CREATE TABLE $ctest (a int)"; |
775 | | - $res = $this->doQuery( $SQL ); |
776 | | - if ( !$res ) { |
777 | | - print "<b>FAILED</b>. Make sure that the user " . htmlspecialchars( $wgDBuser ) . " can write to the database</li>\n"; |
778 | | - die(); |
779 | | - } |
780 | | - $this->doQuery( "DROP TABLE $ctest" ); |
781 | | - |
782 | | - $res = $this->sourceFile( "../maintenance/mssql/tables.sql" ); |
783 | | - if ( $res !== true ) { |
784 | | - echo " <b>FAILED</b></li>"; |
785 | | - die( htmlspecialchars( $res ) ); |
786 | | - } |
787 | | - |
788 | | - # Avoid the non-standard "REPLACE INTO" syntax |
789 | | - $f = fopen( "../maintenance/interwiki.sql", 'r' ); |
790 | | - if ( $f == false ) { |
791 | | - die( "<li>Could not find the interwiki.sql file" ); |
792 | | - } |
793 | | - # We simply assume it is already empty as we have just created it |
794 | | - $SQL = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES "; |
795 | | - while ( ! feof( $f ) ) { |
796 | | - $line = fgets( $f, 1024 ); |
797 | | - $matches = array(); |
798 | | - if ( !preg_match( '/^\s*(\(.+?),(\d)\)/', $line, $matches ) ) { |
799 | | - continue; |
800 | | - } |
801 | | - $this->query( "$SQL $matches[1],$matches[2])" ); |
802 | | - } |
803 | | - print " (table interwiki successfully populated)...\n"; |
804 | | - |
805 | | - $this->commit(); |
806 | | - } |
807 | | - |
808 | 707 | /** |
809 | 708 | * Escapes a identifier for use inm SQL. |
810 | 709 | * Throws an exception if it is invalid. |
Index: trunk/phase3/includes/AutoLoader.php |
— | — | @@ -386,6 +386,7 @@ |
387 | 387 | 'DBConnectionError' => 'includes/db/DatabaseError.php', |
388 | 388 | 'DBError' => 'includes/db/DatabaseError.php', |
389 | 389 | 'DBObject' => 'includes/db/DatabaseUtility.php', |
| 390 | + 'DBMasterPos' => 'includes/db/DatabaseUtility.php', |
390 | 391 | 'DBQueryError' => 'includes/db/DatabaseError.php', |
391 | 392 | 'DBUnexpectedError' => 'includes/db/DatabaseError.php', |
392 | 393 | 'FakeResultWrapper' => 'includes/db/DatabaseUtility.php', |
— | — | @@ -401,6 +402,7 @@ |
402 | 403 | 'LoadBalancer_Single' => 'includes/db/LBFactory_Single.php', |
403 | 404 | 'LoadMonitor' => 'includes/db/LoadMonitor.php', |
404 | 405 | 'LoadMonitor_MySQL' => 'includes/db/LoadMonitor.php', |
| 406 | + 'LoadMonitor_Null' => 'includes/db/LoadMonitor.php', |
405 | 407 | 'MySQLField' => 'includes/db/DatabaseMysql.php', |
406 | 408 | 'MySQLMasterPos' => 'includes/db/DatabaseMysql.php', |
407 | 409 | 'ORAField' => 'includes/db/DatabaseOracle.php', |