Completed
Push — master ( 68d370...07e827 )
by mw
33:19 queued 18:50
created

markPossibleDuplicateProperties()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 33
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3.0987

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 33
ccs 14
cts 18
cp 0.7778
rs 8.8571
cc 3
eloc 18
nc 3
nop 1
crap 3.0987
1
<?php
2
3
namespace SMW\SQLStore;
4
5
use Hooks;
6
use SMW\DIProperty;
7
use SMW\DIWikiPage;
8
use SMW\MediaWiki\Jobs\JobBase;
9
use SMW\MediaWiki\Jobs\UpdateJob;
10
use SMW\SemanticData;
11
use SMW\Store;
12
use Title;
13
14
/**
15
 * @license GNU GPL v2+
16
 * @since 2.3
17
 *
18
 * @author Markus Krötzsch
19
 * @author Jeroen De Dauw
20
 * @author Nischay Nahata
21
 * @author mwjames
22
 */
23
class ByIdDataRebuildDispatcher {
24
25
	/**
26
	 * @var SQLStore
27
	 */
28
	private $store = null;
29
30
	/**
31
	 * @var PropertyTableIdReferenceDisposer
32
	 */
33
	private $propertyTableIdReferenceDisposer = null;
34
35
	/**
36
	 * @var integer
37
	 */
38
	private $updateJobParseMode;
39
40
	/**
41
	 * @var boolean
42
	 */
43
	private $useJobQueueScheduler = true;
44
45
	/**
46
	 * @var array|false
47
	 */
48
	private $namespaces = false;
49
50
	/**
51
	 * @var integer
52
	 */
53
	private $iterationLimit = 1;
54
55
	/**
56
	 * @var integer
57
	 */
58
	private $progress = 1;
59
60
	/**
61
	 * @var array
62
	 */
63
	private $dispatchedEntities = array();
64
65
	/**
66
	 * @since 2.3
67
	 *
68
	 * @param SQLStore $store
69
	 */
70 10
	public function __construct( SQLStore $store ) {
71 10
		$this->store = $store;
72 10
		$this->propertyTableIdReferenceDisposer = new PropertyTableIdReferenceDisposer( $store );
73 10
	}
74
75
	/**
76
	 * @since 2.3
77
	 *
78
	 * @param integer $updateJobParseMode
79
	 */
80 4
	public function setUpdateJobParseMode( $updateJobParseMode ) {
81 4
		$this->updateJobParseMode = $updateJobParseMode;
82 4
	}
83
84
	/**
85
	 * @since 2.3
86
	 *
87
	 * @param boolean $useJobQueueScheduler
88
	 */
89 9
	public function setUpdateJobToUseJobQueueScheduler( $useJobQueueScheduler ) {
90 9
		$this->useJobQueueScheduler = (bool)$useJobQueueScheduler;
91 9
	}
92
93
	/**
94
	 * @since 2.3
95
	 *
96
	 * @param array|false $namespaces
97
	 */
98 7
	public function setNamespacesTo( $namespaces ) {
99 7
		$this->namespaces = $namespaces;
100 7
	}
101
102
	/**
103
	 * @since 2.3
104
	 *
105
	 * @param integer $iterationLimit
106
	 */
107 9
	public function setIterationLimit( $iterationLimit ) {
108 9
		$this->iterationLimit = (int)$iterationLimit;
109 9
	}
110
111
	/**
112
	 * @since 2.3
113
	 *
114
	 * @return integer
115
	 */
116 8
	public function getMaxId() {
117
118 8
		$db = $this->store->getConnection( 'mw.db' );
119
120 8
		$maxByPageId = (int)$db->selectField(
121 8
			'page',
122 8
			'MAX(page_id)',
123 8
			'',
124 8
			__METHOD__
125
		);
126
127 8
		$maxBySmwId = (int)$db->selectField(
128 8
			\SMWSql3SmwIds::TABLE_NAME,
129 8
			'MAX(smw_id)',
130 8
			'',
131 8
			__METHOD__
132
		);
133
134 8
		return max( $maxByPageId, $maxBySmwId );
135
	}
136
137
	/**
138
	 * Decimal between 0 and 1 to indicate the overall progress of the rebuild
139
	 * process
140
	 *
141
	 * @since 2.3
142
	 *
143
	 * @return integer
144
	 */
145 8
	public function getEstimatedProgress() {
146 8
		return $this->progress;
147
	}
148
149
	/**
150
	 * @since 2.4
151
	 *
152
	 * @return array
153
	 */
154 2
	public function getDispatchedEntities() {
155 2
		return $this->dispatchedEntities;
156
	}
157
158
	/**
159
	 * Dispatching of a single or a chunk of ids in either online or batch mode
160
	 * using the JobQueueScheduler
161
	 *
162
	 * @since 2.3
163
	 *
164
	 * @param integer &$id
165
	 */
166 9
	public function dispatchRebuildFor( &$id ) {
167
168 9
		$updatejobs = array();
169 9
		$this->dispatchedEntities = array();
170
171
		// was nothing done in this run?
172 9
		$emptyrange = true;
173
174 9
		$this->createUpdateJobsForTitleIdRange( $id, $updatejobs );
175
176 9
		if ( $updatejobs !== array() ) {
177 7
			$emptyrange = false;
178
		}
179
180 9
		$this->createUpdateJobsForSmwIdRange( $id, $updatejobs, $emptyrange );
181
182
		// Deprecated since 2.3, use 'SMW::SQLStore::BeforeDataRebuildJobInsert'
0 ignored issues
show
Unused Code Comprehensibility introduced by
37% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
183 9
		\Hooks::run('smwRefreshDataJobs', array( &$updatejobs ) );
184
185 9
		Hooks::run( 'SMW::SQLStore::BeforeDataRebuildJobInsert', array( $this->store, &$updatejobs ) );
186
187 9
		if ( $this->useJobQueueScheduler ) {
188 3
			JobBase::batchInsert( $updatejobs );
189
		} else {
190 6
			foreach ( $updatejobs as $job ) {
191 4
				$job->run();
192
			}
193
		}
194
195
		// -1 means that no next position is available
196 9
		$this->findNextIdPosition( $id, $emptyrange );
197
198 9
		return $this->progress = $id > 0 ? $id / $this->getMaxId() : 1;
0 ignored issues
show
Documentation Bug introduced by
It seems like $id > 0 ? $id / $this->getMaxId() : 1 can also be of type double. However, the property $progress is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
199
	}
200
201
	/**
202
	 * @param integer $id
203
	 * @param UpdateJob[] &$updatejobs
204
	 */
205 9
	private function createUpdateJobsForTitleIdRange( $id, &$updatejobs ) {
206
207
		// Update by MediaWiki page id --> make sure we get all pages.
208 9
		$tids = array();
209
210
		// Array of ids
211 9
		for ( $i = $id; $i < $id + $this->iterationLimit; $i++ ) {
212 9
			$tids[] = $i;
213
		}
214
215 9
		$titles = Title::newFromIDs( $tids );
216
217 9
		foreach ( $titles as $title ) {
218 7
			if ( ( $this->namespaces == false ) || ( in_array( $title->getNamespace(), $this->namespaces ) ) ) {
219 7
				$updatejobs[] = $this->newUpdateJob( $title );
220
			}
221
222 7
			$this->dispatchedEntities[] = array( 't' => $title->getPrefixedDBKey() );
223
		}
224 9
	}
225
226
	/**
227
	 * @param integer $id
228
	 * @param UpdateJob[] &$updatejobs
229
	 */
230 9
	private function createUpdateJobsForSmwIdRange( $id, &$updatejobs, &$emptyrange ) {
231
232
		// update by internal SMW id --> make sure we get all objects in SMW
233 9
		$db = $this->store->getConnection( 'mw.db' );
234
235 9
		$res = $db->select(
236 9
			\SMWSql3SmwIds::TABLE_NAME,
237 9
			array( 'smw_id', 'smw_title', 'smw_namespace', 'smw_iw', 'smw_subobject', 'smw_sortkey', 'smw_proptable_hash' ),
238
			array(
239 9
				"smw_id >= $id ",
240 9
				" smw_id < " . $db->addQuotes( $id + $this->iterationLimit )
241
			),
242 9
			__METHOD__
243
		);
244
245 9
		foreach ( $res as $row ) {
246
247 7
			$emptyrange = false; // note this even if no jobs were created
248
249 7
			if ( $this->namespaces && !in_array( $row->smw_namespace, $this->namespaces ) ) {
250
				continue;
251
			}
252
253
			// Find page to refresh, even for special properties:
254 7
			if ( $row->smw_title != '' && $row->smw_title{0} != '_' ) {
255 2
				$titleKey = $row->smw_title;
256 7
			} elseif ( $row->smw_namespace == SMW_NS_PROPERTY && $row->smw_iw == '' && $row->smw_subobject == '' ) {
257 7
				$titleKey = str_replace( ' ', '_', DIProperty::findPropertyLabel( $row->smw_title ) );
0 ignored issues
show
Deprecated Code introduced by
The method SMW\DIProperty::findPropertyLabel() has been deprecated with message: since 2.1, use PropertyRegistry::findPropertyLabelById

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
258
			} else {
259 2
				$titleKey = '';
260
			}
261
262 7
			if ( $row->smw_subobject !== '' && $row->smw_iw !== SMW_SQL3_SMWDELETEIW ) {
263
				// leave subobjects alone; they ought to be changed with their pages
264 1
				$this->dispatchedEntities[] = array( 's' => $row->smw_title . '#' . $row->smw_namespace . '#' .$row->smw_subobject );
265 7
			} elseif ( $this->isPlainObjectValue( $row ) ) {
266 2
				$this->propertyTableIdReferenceDisposer->tryToRemoveOutdatedIDFromEntityTables( $row->smw_id );
267 7
			} elseif ( $row->smw_iw === '' && $titleKey != '' ) {
268
				// objects representing pages
269 7
				$title = Title::makeTitleSafe( $row->smw_namespace, $titleKey );
270
271 7
				if ( $title !== null ) {
272 7
					$this->dispatchedEntities[] = array( 's' => $title->getPrefixedDBKey() );
273 7
					$updatejobs[] = $this->newUpdateJob( $title );
274
				}
275
276 2
			} elseif ( $row->smw_iw == SMW_SQL3_SMWREDIIW && $titleKey != '' ) {
277
				// TODO: special treatment of redirects needed, since the store will
278
				// not act on redirects that did not change according to its records
279
				$title = Title::makeTitleSafe( $row->smw_namespace, $titleKey );
280
281
				if ( $title !== null && !$title->exists() ) {
282
					$this->dispatchedEntities[] = array( 's' => $title->getPrefixedDBKey() );
283
					$updatejobs[] = $this->newUpdateJob( $title );
284
				}
285 2
			} elseif ( $row->smw_iw == SMW_SQL3_SMWIW_OUTDATED || $row->smw_iw == SMW_SQL3_SMWDELETEIW ) { // remove outdated internal object references
286 1
				$this->propertyTableIdReferenceDisposer->cleanUpTableEntriesFor( $row->smw_id );
287 2
			} elseif ( $titleKey != '' ) { // "normal" interwiki pages or outdated internal objects -- delete
288
				$diWikiPage = new DIWikiPage( $titleKey, $row->smw_namespace, $row->smw_iw );
289
				$emptySemanticData = new SemanticData( $diWikiPage );
290
				$this->store->updateData( $emptySemanticData );
291
				$this->dispatchedEntities[] = array( 's' => $diWikiPage );
292
			}
293
294 7
			if ( $row->smw_namespace == SMW_NS_PROPERTY && $row->smw_iw == '' && $row->smw_subobject == '' ) {
295 7
				$this->markPossibleDuplicateProperties( $row );
296
			}
297
		}
298
299 9
		$db->freeResult( $res );
300 9
	}
301
302 7
	private function isPlainObjectValue( $row ) {
303
304
		// A rogue title should never happen
305 7
		if ( $row->smw_title === '' && $row->smw_proptable_hash === null ) {
306 2
			return true;
307
		}
308
309 7
		return $row->smw_iw != SMW_SQL3_SMWDELETEIW &&
310 7
			$row->smw_iw != SMW_SQL3_SMWREDIIW &&
311 7
			$row->smw_iw != SMW_SQL3_SMWIW_OUTDATED &&
312
			// Leave any pre-defined property (_...) untouched
313 7
			$row->smw_title != '' &&
314 7
			$row->smw_title{0} != '_' &&
315
			// smw_proptable_hash === null means it is not a subject but an object value
316 7
			$row->smw_proptable_hash === null;
317
	}
318
319 7
	private function markPossibleDuplicateProperties( $row ) {
320
321 7
		$db = $this->store->getConnection( 'mw.db' );
322
323
		// Use the sortkey (comparing the label and not the "_..." key) in order
324
		// to match possible duplicate properties by label (not by key)
325 7
		$duplicates = $db->select(
326 7
			\SMWSql3SmwIds::TABLE_NAME,
327 7
			'smw_id',
328
			array(
329 7
				"smw_id !=" . $db->addQuotes( $row->smw_id ),
330 7
				"smw_sortkey =" . $db->addQuotes( $row->smw_sortkey ),
331 7
				"smw_namespace =" . $row->smw_namespace,
332 7
				"smw_subobject =" . $db->addQuotes( $row->smw_subobject )
333
			),
334 7
			__METHOD__,
335 7
			array( 'ORDER BY' => "smw_id ASC" )
336
		);
337
338 7
		if ( $duplicates === false ) {
339
			return;
340
		}
341
342
		// Instead of copying ID's across DB tables have the re-parse to ensure
343
		// that all property value ID's are reassigned together while the duplicate
344
		// is marked for removal until the next run
345 7
		foreach ( $duplicates as $duplicate ) {
346
			$this->store->getObjectIds()->updateInterwikiField(
347
				$duplicate->smw_id,
348
				new DIWikiPage( $row->smw_title, $row->smw_namespace, SMW_SQL3_SMWDELETEIW )
349
			);
350
		}
351 7
	}
352
353 9
	private function findNextIdPosition( &$id, $emptyrange ) {
354
355 9
		$nextpos = $id + $this->iterationLimit;
356 9
		$db = $this->store->getConnection( 'mw.db' );
357
358
		// nothing found, check if there will be more pages later on
359 9
		if ( $emptyrange && $nextpos > \SMWSql3SmwIds::FXD_PROP_BORDER_ID ) {
360
361 3
			$nextByPageId = (int)$db->selectField(
362 3
				'page',
363 3
				'page_id',
364 3
				"page_id >= $nextpos",
365 3
				__METHOD__,
366 3
				array( 'ORDER BY' => "page_id ASC" )
367
			);
368
369 3
			$nextBySmwId = (int)$db->selectField(
370 3
				\SMWSql3SmwIds::TABLE_NAME,
371 3
				'smw_id',
372 3
				"smw_id >= $nextpos",
373 3
				__METHOD__,
374 3
				array( 'ORDER BY' => "smw_id ASC" )
375
			);
376
377
			// Next position is determined by the pool with the maxId
378 3
			$nextpos = $nextBySmwId != 0 && $nextBySmwId > $nextByPageId ? $nextBySmwId : $nextByPageId;
379
		}
380
381 9
		$id = $nextpos ? $nextpos : -1;
382 9
	}
383
384 7
	private function newUpdateJob( $title ) {
385 7
		return new UpdateJob( $title, array( 'pm' => $this->updateJobParseMode ) );
386
	}
387
388
}
389