Merge "rdbms: Remove reliance on internal STATUS_TRX and STATUS_SESS_ sequence"
[mediawiki.git] / includes / externalstore / ExternalStoreMwstore.php
blob562c0d07973db198f60ee88fb9acfd7d5a82a3db
1 <?php
2 /**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
18 * @file
21 use MediaWiki\FileBackend\FileBackendGroup;
22 use MediaWiki\MediaWikiServices;
23 use MediaWiki\WikiMap\WikiMap;
24 use Wikimedia\FileBackend\FileBackend;
25 use Wikimedia\FileBackend\FSFileBackend;
27 /**
28 * External storage in a FileBackend.
30 * In this system, each store "location" maps to the name of a file backend.
31 * The file backends must be defined in $wgFileBackends and must be global
32 * and fully qualified with a global "wikiId" prefix in the configuration.
34 * @see ExternalStoreAccess
35 * @ingroup ExternalStorage
36 * @since 1.21
38 class ExternalStoreMwstore extends ExternalStoreMedium {
39 /** @var FileBackendGroup */
40 private $fbGroup;
42 /**
43 * @see ExternalStoreMedium::__construct()
44 * @param array $params Additional parameters include:
45 * - fbGroup: a FileBackendGroup instance
47 public function __construct( array $params ) {
48 parent::__construct( $params );
49 if ( !isset( $params['fbGroup'] ) || !( $params['fbGroup'] instanceof FileBackendGroup ) ) {
50 throw new InvalidArgumentException( "FileBackendGroup required in 'fbGroup' field." );
52 $this->fbGroup = $params['fbGroup'];
55 /**
56 * Fetch data from a given external store URL
58 * @see ExternalStoreMedium::fetchFromURL()
59 * @param string $url An external store URL in the form of mwstore://backend/container/wiki/id
60 * @return string|bool
62 public function fetchFromURL( $url ) {
63 $be = $this->fbGroup->backendFromPath( $url );
64 if ( $be instanceof FileBackend ) {
65 // We don't need "latest" since objects are immutable and
66 // backends should at least have "read-after-create" consistency.
67 return $be->getFileContents( [ 'src' => $url ] );
70 return false;
73 /**
74 * Fetch data from given external store URLs.
75 * The URLs are in the form of mwstore://backend/container/wiki/id
77 * @param array $urls An array of external store URLs
78 * @return array A map from url to stored content. Failed results are not represented.
80 public function batchFetchFromURLs( array $urls ) {
81 $pathsByBackend = [];
82 foreach ( $urls as $url ) {
83 $be = $this->fbGroup->backendFromPath( $url );
84 if ( $be instanceof FileBackend ) {
85 $pathsByBackend[$be->getName()][] = $url;
88 $blobs = [];
89 foreach ( $pathsByBackend as $backendName => $paths ) {
90 $be = $this->fbGroup->get( $backendName );
91 $blobs += $be->getFileContentsMulti( [ 'srcs' => $paths ] );
94 return $blobs;
97 public function store( $backend, $data ) {
98 $be = $this->fbGroup->get( $backend );
99 // Get three random base 36 characters to act as shard directories
100 $rand = Wikimedia\base_convert( (string)mt_rand( 0, 46655 ), 10, 36, 3 );
101 // Make sure ID is roughly lexicographically increasing for performance
102 $gen = MediaWikiServices::getInstance()->getGlobalIdGenerator();
103 $id = str_pad( $gen->newTimestampedUID128( 32 ), 26, '0', STR_PAD_LEFT );
104 // Segregate items by DB domain ID for the sake of bookkeeping
105 $domain = $this->isDbDomainExplicit
106 ? $this->dbDomain
107 // @FIXME: this does not include the schema for b/c but it ideally should
108 : WikiMap::getWikiIdFromDbDomain( $this->dbDomain );
109 $url = $be->getContainerStoragePath( 'data' ) . '/' . rawurlencode( $domain );
110 // Use directory/container sharding
111 $url .= ( $be instanceof FSFileBackend )
112 ? "/{$rand[0]}/{$rand[1]}/{$rand[2]}/{$id}" // keep directories small
113 : "/{$rand[0]}/{$rand[1]}/{$id}"; // container sharding is only 2-levels
115 $be->prepare( [ 'dir' => dirname( $url ), 'noAccess' => 1, 'noListing' => 1 ] );
116 $status = $be->create( [ 'dst' => $url, 'content' => $data ] );
118 if ( $status->isOK() ) {
119 return $url;
122 throw new ExternalStoreException( __METHOD__ . ": operation failed: $status" );
125 public function isReadOnly( $backend ) {
126 if ( parent::isReadOnly( $backend ) ) {
127 return true;
130 $be = $this->fbGroup->get( $backend );
132 return $be->isReadOnly();