4 * Base class for general text storage via the "object" flag in old_flags, or
5 * two-part external storage URLs. Used for represent efficient concatenated
6 * storage, and migration-related pointer objects.
11 * Adds an item of text, returns a stub object which points to the item.
12 * You must call setLocation() on the stub object before storing it to the
14 * Returns the key for getItem()
16 public function addItem( $text );
19 * Get item by key, or false if the key is not present
21 public function getItem( $key );
24 * Set the "default text"
25 * This concept is an odd property of the current DB schema, whereby each text item has a revision
26 * associated with it. The default text is the text of the associated revision. There may, however,
27 * be other revisions in the same object.
29 * Default text is not required for two-part external storage URLs.
31 public function setText( $text );
34 * Get default text. This is called from Revision::getRevisionText()
40 * Concatenated gzip (CGZ) storage
41 * Improves compression ratio by concatenating like objects before gzipping
43 class ConcatenatedGzipHistoryBlob
implements HistoryBlob
45 public $mVersion = 0, $mCompressed = false, $mItems = array(), $mDefaultHash = '';
47 public $mMaxSize = 10000000;
48 public $mMaxCount = 100;
51 public function ConcatenatedGzipHistoryBlob() {
52 if ( !function_exists( 'gzdeflate' ) ) {
53 throw new MWException( "Need zlib support to read or write this kind of history object (ConcatenatedGzipHistoryBlob)\n" );
57 public function addItem( $text ) {
60 $this->mItems
[$hash] = $text;
61 $this->mSize +
= strlen( $text );
66 public function getItem( $hash ) {
68 if ( array_key_exists( $hash, $this->mItems
) ) {
69 return $this->mItems
[$hash];
75 public function setText( $text ) {
77 $this->mDefaultHash
= $this->addItem( $text );
80 public function getText() {
82 return $this->getItem( $this->mDefaultHash
);
88 public function removeItem( $hash ) {
89 $this->mSize
-= strlen( $this->mItems
[$hash] );
90 unset( $this->mItems
[$hash] );
94 * Compress the bulk data in the object
96 public function compress() {
97 if ( !$this->mCompressed
) {
98 $this->mItems
= gzdeflate( serialize( $this->mItems
) );
99 $this->mCompressed
= true;
104 * Uncompress bulk data
106 public function uncompress() {
107 if ( $this->mCompressed
) {
108 $this->mItems
= unserialize( gzinflate( $this->mItems
) );
109 $this->mCompressed
= false;
116 return array( 'mVersion', 'mCompressed', 'mItems', 'mDefaultHash' );
119 function __wakeup() {
124 * Helper function for compression jobs
125 * Returns true until the object is "full" and ready to be committed
127 public function isHappy() {
128 return $this->mSize
< $this->mMaxSize
129 && count( $this->mItems
) < $this->mMaxCount
;
135 * One-step cache variable to hold base blobs; operations that
136 * pull multiple revisions may often pull multiple times from
137 * the same blob. By keeping the last-used one open, we avoid
138 * redundant unserialization and decompression overhead.
141 $wgBlobCache = array();
145 * Pointer object for an item within a CGZ blob stored in the text table.
147 class HistoryBlobStub
{
148 var $mOldId, $mHash, $mRef;
151 * @param string $hash The content hash of the text
152 * @param integer $oldid The old_id for the CGZ object
154 function HistoryBlobStub( $hash = '', $oldid = 0 ) {
155 $this->mHash
= $hash;
159 * Sets the location (old_id) of the main object to which this object
162 function setLocation( $id ) {
167 * Sets the location (old_id) of the referring object
169 function setReferrer( $id ) {
174 * Gets the location of the referring object
176 function getReferrer() {
181 $fname = 'HistoryBlobStub::getText';
183 if( isset( $wgBlobCache[$this->mOldId
] ) ) {
184 $obj = $wgBlobCache[$this->mOldId
];
186 $dbr = wfGetDB( DB_SLAVE
);
187 $row = $dbr->selectRow( 'text', array( 'old_flags', 'old_text' ), array( 'old_id' => $this->mOldId
) );
191 $flags = explode( ',', $row->old_flags
);
192 if( in_array( 'external', $flags ) ) {
194 @list
( /* $proto */ ,$path)=explode('://',$url,2);
196 wfProfileOut( $fname );
199 $row->old_text
=ExternalStore
::fetchFromUrl($url);
202 if( !in_array( 'object', $flags ) ) {
206 if( in_array( 'gzip', $flags ) ) {
207 // This shouldn't happen, but a bug in the compress script
208 // may at times gzip-compress a HistoryBlob object row.
209 $obj = unserialize( gzinflate( $row->old_text
) );
211 $obj = unserialize( $row->old_text
);
214 if( !is_object( $obj ) ) {
215 // Correct for old double-serialization bug.
216 $obj = unserialize( $obj );
219 // Save this item for reference; if pulling many
220 // items in a row we'll likely use it again.
222 $wgBlobCache = array( $this->mOldId
=> $obj );
224 return $obj->getItem( $this->mHash
);
228 * Get the content hash
237 * To speed up conversion from 1.4 to 1.5 schema, text rows can refer to the
238 * leftover cur table as the backend. This avoids expensively copying hundreds
239 * of megabytes of data during the conversion downtime.
241 * Serialized HistoryBlobCurStub objects will be inserted into the text table
242 * on conversion if $wgFastSchemaUpgrades is set to true.
244 class HistoryBlobCurStub
{
248 * @param integer $curid The cur_id pointed to
250 function HistoryBlobCurStub( $curid = 0 ) {
251 $this->mCurId
= $curid;
255 * Sets the location (cur_id) of the main object to which this object
258 function setLocation( $id ) {
263 $dbr = wfGetDB( DB_SLAVE
);
264 $row = $dbr->selectRow( 'cur', array( 'cur_text' ), array( 'cur_id' => $this->mCurId
) );
268 return $row->cur_text
;
273 * Diff-based history compression
274 * Requires xdiff 1.5+ and zlib
276 class DiffHistoryBlob
implements HistoryBlob
{
277 /** Uncompressed item cache */
278 var $mItems = array();
281 * Array of diffs, where $this->mDiffs[0] is the diff between
282 * $this->mDiffs[0] and $this->mDiffs[1]
284 var $mDiffs = array();
287 * The key for getText()
297 * True if the object is locked against further writes
299 var $mFrozen = false;
303 * The maximum uncompressed size before the object becomes sad
304 * Should be less than max_allowed_packet
306 var $mMaxSize = 10000000;
309 * The maximum number of text items before the object becomes sad
311 var $mMaxCount = 100;
313 function __construct() {
314 if ( !function_exists( 'xdiff_string_bdiff' ) ){
315 throw new MWException( "Need xdiff 1.5+ support to read or write DiffHistoryBlob\n" );
317 if ( !function_exists( 'gzdeflate' ) ) {
318 throw new MWException( "Need zlib support to read or write DiffHistoryBlob\n" );
322 function addItem( $text ) {
323 if ( $this->mFrozen
) {
324 throw new MWException( __METHOD__
.": Cannot add more items after sleep/wakeup" );
327 $this->mItems
[] = $text;
328 $this->mSize +
= strlen( $text );
329 $i = count( $this->mItems
) - 1;
331 # Need to do a null concatenation with warnings off, due to bugs in the current version of xdiff
332 # "String is not zero-terminated"
333 wfSuppressWarnings();
334 $this->mDiffs
[] = xdiff_string_bdiff( $this->mItems
[$i-1], $text ) . '';
340 function getItem( $key ) {
341 if ( $key > count( $this->mDiffs
) +
1 ) {
344 $key = intval( $key );
346 return $this->mItems
[0];
349 $last = count( $this->mItems
) - 1;
350 for ( $i = $last +
1; $i <= $key; $i++
) {
351 # Need to do a null concatenation with warnings off, due to bugs in the current version of xdiff
352 # "String is not zero-terminated"
353 wfSuppressWarnings();
354 $this->mItems
[$i] = xdiff_string_bpatch( $this->mItems
[$i - 1], $this->mDiffs
[$i - 1] ) . '';
357 return $this->mItems
[$key];
360 function setText( $text ) {
361 $this->mDefaultKey
= $this->addItem( $text );
365 return $this->getItem( $this->mDefaultKey
);
369 if ( !isset( $this->mItems
[0] ) ) {
374 'base' => $this->mItems
[0],
375 'diffs' => $this->mDiffs
378 if ( isset( $this->mDefaultKey
) ) {
379 $info['default'] = $this->mDefaultKey
;
381 $this->mCompressed
= gzdeflate( serialize( $info ) );
382 return array( 'mCompressed' );
385 function __wakeup() {
386 // addItem() doesn't work if mItems is partially filled from mDiffs
387 $this->mFrozen
= true;
388 $info = unserialize( gzinflate( $this->mCompressed
) );
389 unset( $this->mCompressed
);
396 if ( isset( $info['default'] ) ) {
397 $this->mDefaultKey
= $info['default'];
399 $this->mItems
[0] = $info['base'];
400 $this->mDiffs
= $info['diffs'];
404 * Helper function for compression jobs
405 * Returns true until the object is "full" and ready to be committed
408 return $this->mSize
< $this->mMaxSize
409 && count( $this->mItems
) < $this->mMaxCount
;