Renamed $pairs => $triples.
[lhc/web/wiklou.git] / includes / ExternalStoreDB.php
1 <?php
2 /**
3 * External storage in SQL database.
4 *
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.
9 *
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.
14 *
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
19 *
20 * @file
21 */
22
23 /**
24 * DB accessable external objects
25 * @ingroup ExternalStorage
26 */
27 class ExternalStoreDB {
28
29 function __construct( $params = array() ) {
30 $this->mParams = $params;
31 }
32
33 /**
34 * Get a LoadBalancer for the specified cluster
35 *
36 * @param $cluster String: cluster name
37 * @return LoadBalancer object
38 */
39 function &getLoadBalancer( $cluster ) {
40 $wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
41
42 return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
43 }
44
45 /**
46 * Get a slave database connection for the specified cluster
47 *
48 * @param $cluster String: cluster name
49 * @return DatabaseBase object
50 */
51 function &getSlave( $cluster ) {
52 global $wgDefaultExternalStore;
53
54 $wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
55 $lb =& $this->getLoadBalancer( $cluster );
56
57 if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
58 wfDebug( "read only external store" );
59 $lb->allowLagged(true);
60 } else {
61 wfDebug( "writable external store" );
62 }
63
64 return $lb->getConnection( DB_SLAVE, array(), $wiki );
65 }
66
67 /**
68 * Get a master database connection for the specified cluster
69 *
70 * @param $cluster String: cluster name
71 * @return DatabaseBase object
72 */
73 function &getMaster( $cluster ) {
74 $wiki = isset($this->mParams['wiki']) ? $this->mParams['wiki'] : false;
75 $lb =& $this->getLoadBalancer( $cluster );
76 return $lb->getConnection( DB_MASTER, array(), $wiki );
77 }
78
79 /**
80 * Get the 'blobs' table name for this database
81 *
82 * @param $db DatabaseBase
83 * @return String: table name ('blobs' by default)
84 */
85 function getTable( &$db ) {
86 $table = $db->getLBInfo( 'blobs table' );
87 if ( is_null( $table ) ) {
88 $table = 'blobs';
89 }
90 return $table;
91 }
92
93 /**
94 * Fetch data from given URL
95 * @param $url String: an url of the form DB://cluster/id or DB://cluster/id/itemid for concatened storage.
96 * @return mixed
97 */
98 function fetchFromURL( $url ) {
99 $path = explode( '/', $url );
100 $cluster = $path[2];
101 $id = $path[3];
102 if ( isset( $path[4] ) ) {
103 $itemID = $path[4];
104 } else {
105 $itemID = false;
106 }
107
108 $ret =& $this->fetchBlob( $cluster, $id, $itemID );
109
110 if ( $itemID !== false && $ret !== false ) {
111 return $ret->getItem( $itemID );
112 }
113 return $ret;
114 }
115
116 /**
117 * Fetch a blob item out of the database; a cache of the last-loaded
118 * blob will be kept so that multiple loads out of a multi-item blob
119 * can avoid redundant database access and decompression.
120 * @param $cluster
121 * @param $id
122 * @param $itemID
123 * @return mixed
124 * @private
125 */
126 function &fetchBlob( $cluster, $id, $itemID ) {
127 /**
128 * One-step cache variable to hold base blobs; operations that
129 * pull multiple revisions may often pull multiple times from
130 * the same blob. By keeping the last-used one open, we avoid
131 * redundant unserialization and decompression overhead.
132 */
133 static $externalBlobCache = array();
134
135 $cacheID = ( $itemID === false ) ? "$cluster/$id" : "$cluster/$id/";
136 if( isset( $externalBlobCache[$cacheID] ) ) {
137 wfDebug( "ExternalStoreDB::fetchBlob cache hit on $cacheID\n" );
138 return $externalBlobCache[$cacheID];
139 }
140
141 wfDebug( "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
142
143 $dbr =& $this->getSlave( $cluster );
144 $ret = $dbr->selectField( $this->getTable( $dbr ), 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
145 if ( $ret === false ) {
146 wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master fallback on $cacheID\n" );
147 // Try the master
148 $dbw =& $this->getMaster( $cluster );
149 $ret = $dbw->selectField( $this->getTable( $dbw ), 'blob_text', array( 'blob_id' => $id ), __METHOD__ );
150 if( $ret === false) {
151 wfDebugLog( 'ExternalStoreDB', "ExternalStoreDB::fetchBlob master failed to find $cacheID\n" );
152 }
153 }
154 if( $itemID !== false && $ret !== false ) {
155 // Unserialise object; caller extracts item
156 $ret = unserialize( $ret );
157 }
158
159 $externalBlobCache = array( $cacheID => &$ret );
160 return $ret;
161 }
162
163 /**
164 * Insert a data item into a given cluster
165 *
166 * @param $cluster String: the cluster name
167 * @param $data String: the data item
168 * @return string URL
169 */
170 function store( $cluster, $data ) {
171 $dbw = $this->getMaster( $cluster );
172 $id = $dbw->nextSequenceValue( 'blob_blob_id_seq' );
173 $dbw->insert( $this->getTable( $dbw ),
174 array( 'blob_id' => $id, 'blob_text' => $data ),
175 __METHOD__ );
176 $id = $dbw->insertId();
177 if ( !$id ) {
178 throw new MWException( __METHOD__.': no insert ID' );
179 }
180 if ( $dbw->getFlag( DBO_TRX ) ) {
181 $dbw->commit( __METHOD__ );
182 }
183 return "DB://$cluster/$id";
184 }
185 }