3 * External storage in SQL database.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
24 * DB accessable external objects.
26 * In this system, each store "location" maps to a database "cluster".
27 * The clusters must be defined in the normal LBFactory configuration.
29 * @ingroup ExternalStorage
31 class ExternalStoreDB
extends ExternalStoreMedium
{
33 * The URL returned is of the form of the form DB://cluster/id
34 * or DB://cluster/id/itemid for concatened storage.
36 * @see ExternalStoreMedium::fetchFromURL()
38 public function fetchFromURL( $url ) {
39 $path = explode( '/', $url );
42 if ( isset( $path[4] ) ) {
48 $ret =& $this->fetchBlob( $cluster, $id, $itemID );
50 if ( $itemID !== false && $ret !== false ) {
51 return $ret->getItem( $itemID );
57 * @see ExternalStoreMedium::store()
59 public function store( $cluster, $data ) {
60 $dbw = $this->getMaster( $cluster );
61 $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' );
62 $dbw->insert( $this->getTable( $dbw ),
63 array( 'blob_id' => $id, 'blob_text' => $data ),
65 $id = $dbw->insertId();
67 throw new MWException( __METHOD__
. ': no insert ID' );
69 if ( $dbw->getFlag( DBO_TRX
) ) {
70 $dbw->commit( __METHOD__
);
72 return "DB://$cluster/$id";
76 * Get a LoadBalancer for the specified cluster
78 * @param string $cluster cluster name
79 * @return LoadBalancer object
81 function &getLoadBalancer( $cluster ) {
82 $wiki = isset( $this->params
['wiki'] ) ?
$this->params
['wiki'] : false;
84 return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
88 * Get a slave database connection for the specified cluster
90 * @param string $cluster cluster name
91 * @return DatabaseBase object
93 function &getSlave( $cluster ) {
94 global $wgDefaultExternalStore;
96 $wiki = isset( $this->params
['wiki'] ) ?
$this->params
['wiki'] : false;
97 $lb =& $this->getLoadBalancer( $cluster );
99 if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
100 wfDebug( "read only external store" );
101 $lb->allowLagged( true );
103 wfDebug( "writable external store" );
106 return $lb->getConnection( DB_SLAVE
, array(), $wiki );
110 * Get a master database connection for the specified cluster
112 * @param string $cluster cluster name
113 * @return DatabaseBase object
115 function &getMaster( $cluster ) {
116 $wiki = isset( $this->params
['wiki'] ) ?
$this->params
['wiki'] : false;
117 $lb =& $this->getLoadBalancer( $cluster );
118 return $lb->getConnection( DB_MASTER
, array(), $wiki );
122 * Get the 'blobs' table name for this database
124 * @param $db DatabaseBase
125 * @return String: table name ('blobs' by default)
127 function getTable( &$db ) {
128 $table = $db->getLBInfo( 'blobs table' );
129 if ( is_null( $table ) ) {
136 * Fetch a blob item out of the database; a cache of the last-loaded
137 * blob will be kept so that multiple loads out of a multi-item blob
138 * can avoid redundant database access and decompression.
145 function &fetchBlob( $cluster, $id, $itemID ) {
147 * One-step cache variable to hold base blobs; operations that
148 * pull multiple revisions may often pull multiple times from
149 * the same blob. By keeping the last-used one open, we avoid
150 * redundant unserialization and decompression overhead.
152 static $externalBlobCache = array();
154 $cacheID = ( $itemID === false ) ?
"$cluster/$id" : "$cluster/$id/";
155 if ( isset( $externalBlobCache[$cacheID] ) ) {
156 wfDebugLog( 'ExternalStoreDB-cache', "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
157 return $externalBlobCache[$cacheID];
160 wfDebugLog( 'ExternalStoreDB-cache', "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
162 $dbr =& $this->getSlave( $cluster );
163 $ret = $dbr->selectField( $this->getTable( $dbr ), 'blob_text', array( 'blob_id' => $id ), __METHOD__
);
164 if ( $ret === false ) {
165 wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master fallback on $cacheID\n" );
167 $dbw =& $this->getMaster( $cluster );
168 $ret = $dbw->selectField( $this->getTable( $dbw ), 'blob_text', array( 'blob_id' => $id ), __METHOD__
);
169 if ( $ret === false) {
170 wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master failed to find $cacheID\n" );
173 if ( $itemID !== false && $ret !== false ) {
174 // Unserialise object; caller extracts item
175 $ret = unserialize( $ret );
178 $externalBlobCache = array( $cacheID => &$ret );