be9c066ab705e92b4214fad3fd8bec389d86ee42
[lhc/web/wiklou.git] / includes / externalstore / 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 *
26 * In this system, each store "location" maps to a database "cluster".
27 * The clusters must be defined in the normal LBFactory configuration.
28 *
29 * @ingroup ExternalStorage
30 */
31 class ExternalStoreDB extends ExternalStoreMedium {
32 /**
33 * The URL returned is of the form of the form DB://cluster/id
34 * or DB://cluster/id/itemid for concatened storage.
35 *
36 * @see ExternalStoreMedium::fetchFromURL()
37 */
38 public function fetchFromURL( $url ) {
39 $path = explode( '/', $url );
40 $cluster = $path[2];
41 $id = $path[3];
42 if ( isset( $path[4] ) ) {
43 $itemID = $path[4];
44 } else {
45 $itemID = false;
46 }
47
48 $ret =& $this->fetchBlob( $cluster, $id, $itemID );
49
50 if ( $itemID !== false && $ret !== false ) {
51 return $ret->getItem( $itemID );
52 }
53 return $ret;
54 }
55
56 /**
57 * @see ExternalStoreMedium::store()
58 */
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 ),
64 __METHOD__ );
65 $id = $dbw->insertId();
66 if ( !$id ) {
67 throw new MWException( __METHOD__ . ': no insert ID' );
68 }
69 if ( $dbw->getFlag( DBO_TRX ) ) {
70 $dbw->commit( __METHOD__ );
71 }
72 return "DB://$cluster/$id";
73 }
74
75 /**
76 * Get a LoadBalancer for the specified cluster
77 *
78 * @param string $cluster cluster name
79 * @return LoadBalancer object
80 */
81 function &getLoadBalancer( $cluster ) {
82 $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
83
84 return wfGetLBFactory()->getExternalLB( $cluster, $wiki );
85 }
86
87 /**
88 * Get a slave database connection for the specified cluster
89 *
90 * @param string $cluster cluster name
91 * @return DatabaseBase object
92 */
93 function &getSlave( $cluster ) {
94 global $wgDefaultExternalStore;
95
96 $wiki = isset( $this->params['wiki'] ) ? $this->params['wiki'] : false;
97 $lb =& $this->getLoadBalancer( $cluster );
98
99 if ( !in_array( "DB://" . $cluster, (array)$wgDefaultExternalStore ) ) {
100 wfDebug( "read only external store" );
101 $lb->allowLagged( true );
102 } else {
103 wfDebug( "writable external store" );
104 }
105
106 return $lb->getConnection( DB_SLAVE, array(), $wiki );
107 }
108
109 /**
110 * Get a master database connection for the specified cluster
111 *
112 * @param string $cluster cluster name
113 * @return DatabaseBase object
114 */
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 );
119 }
120
121 /**
122 * Get the 'blobs' table name for this database
123 *
124 * @param $db DatabaseBase
125 * @return String: table name ('blobs' by default)
126 */
127 function getTable( &$db ) {
128 $table = $db->getLBInfo( 'blobs table' );
129 if ( is_null( $table ) ) {
130 $table = 'blobs';
131 }
132 return $table;
133 }
134
135 /**
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.
139 * @param $cluster
140 * @param $id
141 * @param $itemID
142 * @return mixed
143 * @private
144 */
145 function &fetchBlob( $cluster, $id, $itemID ) {
146 /**
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.
151 */
152 static $externalBlobCache = array();
153
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];
158 }
159
160 wfDebugLog( 'ExternalStoreDB-cache', "ExternalStoreDB::fetchBlob cache miss on $cacheID\n" );
161
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" );
166 // Try the master
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" );
171 }
172 }
173 if ( $itemID !== false && $ret !== false ) {
174 // Unserialise object; caller extracts item
175 $ret = unserialize( $ret );
176 }
177
178 $externalBlobCache = array( $cacheID => &$ret );
179 return $ret;
180 }
181 }