Completed
Push — master ( 2a8480...b4e0c6 )
by mw
415:39 queued 380:48
created

pushUpdatesFromPropertyTableDiff()   C

Complexity

Conditions 7
Paths 15

Size

Total Lines 49
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7.0046

Importance

Changes 0
Metric Value
cc 7
eloc 24
nc 15
nop 1
dl 0
loc 49
ccs 21
cts 22
cp 0.9545
crap 7.0046
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace SMW\SQLStore\QueryEngine\Fulltext;
4
5
use SMW\SQLStore\CompositePropertyTableDiffIterator;
6
use SMW\SQLStore\ChangeOp\TableChangeOp;
7
use SMW\MediaWiki\Database;
8
use SMW\DeferredRequestDispatchManager;
9
use SMW\DIWikiPage;
10
use SMWDIBlob as DIBlob;
11
use SMWDIUri as DIUri;
12
use SMW\SQLStore\ChangeOp\TempChangeOpStore;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\LoggerAwareInterface;
15
use SMW\Utils\Timer;
16
17
/**
18
 * @license GNU GPL v2+
19
 * @since 2.5
20
 *
21
 * @author mwjames
22
 */
23
class TextByChangeUpdater implements LoggerAwareInterface {
24
25
	/**
26
	 * @var Database
27
	 */
28
	private $connection;
29
30
	/**
31
	 * @var SearchTableUpdater
32
	 */
33
	private $searchTableUpdater;
34
35
	/**
36
	 * @var TextSanitizer
37
	 */
38
	private $textSanitizer;
39
40
	/**
41
	 * @var TempChangeOpStore
42
	 */
43
	private $tempChangeOpStore;
44
45
	/**
46
	 * @var LoggerInterface
47
	 */
48
	private $logger;
49
50
	/**
51
	 * @var boolean
52
	 */
53
	private $asDeferredUpdate = true;
54
55
	/**
56
	 * @var boolean
57
	 */
58 205
	private $isCommandLineMode = false;
59 205
60 205
	/**
61 205
	 * @since 2.5
62 205
	 *
63 205
	 * @param Database $connection
64
	 * @param SearchTableUpdater $searchTableUpdater
65
	 * @param TextSanitizer $textSanitizer
66
	 * @param TempChangeOpStore $tempChangeOpStore
67
	 */
68
	public function __construct( Database $connection, SearchTableUpdater $searchTableUpdater, TextSanitizer $textSanitizer, TempChangeOpStore $tempChangeOpStore ) {
69
		$this->connection = $connection;
70
		$this->searchTableUpdater = $searchTableUpdater;
71
		$this->textSanitizer = $textSanitizer;
72 204
		$this->tempChangeOpStore = $tempChangeOpStore;
73 204
	}
74 204
75
	/**
76
	 * @see LoggerAwareInterface::setLogger
77
	 *
78
	 * @since 2.5
79
	 *
80
	 * @param LoggerInterface $logger
81
	 */
82
	public function setLogger( LoggerInterface $logger ) {
83
		$this->logger = $logger;
84
	}
85 202
86 202
	/**
87 202
	 * @note See comments in the DefaultSettings.php on the smwgFulltextDeferredUpdate setting
88
	 *
89
	 * @since 2.5
90
	 *
91
	 * @param boolean $asDeferredUpdate
92
	 */
93
	public function asDeferredUpdate( $asDeferredUpdate ) {
94
		$this->asDeferredUpdate = (bool)$asDeferredUpdate;
95
	}
96
97 204
	/**
98
	 * When running from commandLine, push updates directly to avoid overhead when
99 204
	 * it is known that within that mode transactions are FIFO (i.e. the likelihood
100 198
	 * for race conditions of unfinished updates are diminishable).
101
	 *
102
	 * @since 2.5
103
	 *
104 6
	 * @param boolean $isCommandLineMode
105 5
	 */
106
	public function isCommandLineMode( $isCommandLineMode ) {
107
		$this->isCommandLineMode = (bool)$isCommandLineMode;
108 1
	}
109
110
	/**
111
	 * @see SMW::SQLStore::AfterDataUpdateComplete hook
112 1
	 *
113
	 * @since 2.5
114
	 *
115
	 * @param CompositePropertyTableDiffIterator $compositePropertyTableDiffIterator
116 1
	 * @param DeferredRequestDispatchManager $deferredRequestDispatchManager
117 1
	 */
118
	public function pushUpdates( CompositePropertyTableDiffIterator $compositePropertyTableDiffIterator, DeferredRequestDispatchManager $deferredRequestDispatchManager ) {
119 1
120
		if ( !$this->searchTableUpdater->isEnabled() ) {
121
			return;
122 1
		}
123
124
		Timer::start( __METHOD__ );
125
126
		// Update within the same transaction as started by SMW::SQLStore::AfterDataUpdateComplete
127
		if ( !$this->asDeferredUpdate || $this->isCommandLineMode ) {
128
			return $this->pushUpdatesFromPropertyTableDiff( $compositePropertyTableDiffIterator );
129
		}
130
131
		if ( !$this->canPostUpdate( $compositePropertyTableDiffIterator ) ) {
132
			return;
133
		}
134
135
		$slot = $this->tempChangeOpStore->createSlotFrom(
136
			$compositePropertyTableDiffIterator
137
		);
138
139
		$deferredRequestDispatchManager->dispatchFulltextSearchTableUpdateJobWith(
140
			$compositePropertyTableDiffIterator->getSubject()->getTitle(),
141
			array(
142
				'slot:id' => $slot
143
			)
144
		);
145
146
		$this->log( __METHOD__ . ' (procTime in sec: '. Timer::getElapsedTime( __METHOD__, 5 ) . ')' );
147
	}
148
149
	/**
150
	 * @see SearchTableUpdateJob::run
151
	 *
152
	 * @since 2.5
153
	 *
154
	 * @param array|boolan $parameters
155
	 */
156 5
	public function pushUpdatesFromJobParameters( $parameters ) {
157
158 5
		if ( !$this->searchTableUpdater->isEnabled() || !isset( $parameters['slot:id'] ) || $parameters['slot:id'] === false ) {
159
			return;
160
		}
161
162 5
		Timer::start( __METHOD__ );
163
164 5
		$compositePropertyTableDiffIterator = $this->tempChangeOpStore->newCompositePropertyTableDiffIterator(
165 3
			$parameters['slot:id']
166
		);
167
168 5
		if ( $compositePropertyTableDiffIterator === null ) {
169 5
			return $this->log( __METHOD__ . ' Failed compositePropertyTableDiff from slot: ' . $parameters['slot:id'] );
170
		}
171 3
172
		$this->pushUpdatesFromPropertyTableDiff( $compositePropertyTableDiffIterator );
173 3
174 3
		$this->tempChangeOpStore->delete(
175
			$parameters['slot:id']
176 3
		);
177
178
		$this->log( __METHOD__ . ' (procTime in sec: '. Timer::getElapsedTime( __METHOD__, 5 ) . ')' );
179 3
	}
180 3
181
	/**
182
	 * @since 2.5
183 3
	 *
184
	 * @param CompositePropertyTableDiffIterator $compositePropertyTableDiffIterator
185
	 */
186 3
	public function pushUpdatesFromPropertyTableDiff( CompositePropertyTableDiffIterator $compositePropertyTableDiffIterator ) {
187
188
		if ( !$this->searchTableUpdater->isEnabled() ) {
189 3
			return;
190 3
		}
191
192
		Timer::start( __METHOD__ );
193 3
194
		$dataChangeOps = $compositePropertyTableDiffIterator->getDataChangeOps();
195
		$diffChangeOps = $compositePropertyTableDiffIterator->getTableChangeOps();
196 3
197 3
		$insertIds = $compositePropertyTableDiffIterator->getListOfChangedEntityIdsByType(
198
			$compositePropertyTableDiffIterator::TYPE_INSERT
199 3
		);
200
201 3
		$updates = array();
202
203
		// Ensure that any delete operation is being accounted for to avoid that
204 3
		// removed value annotation remain
205 3
		if ( $diffChangeOps !== array() ) {
206
			$this->doDeleteFromTableChangeOps( $diffChangeOps );
207
		}
208 3
209
		if ( $insertIds === array() ) {
210
			return;
211
		}
212
213 3
		// Build a composite of replacements where a change occured, this my
214 1
		// contain some false positives
215
		foreach ( $dataChangeOps as $dataChangeOp ) {
216
			$this->doCreateCompositeUpdate( $dataChangeOp, $updates, $insertIds );
217
		}
218 3
219 2
		foreach ( $updates as $key => $value ) {
220 2
			list( $sid, $pid ) = explode( ':', $key, 2 );
221
222
			if ( $this->searchTableUpdater->exists( $sid, $pid ) === false ) {
223
				$this->searchTableUpdater->insert( $sid, $pid );
224 3
			}
225
226
			$this->searchTableUpdater->update(
227
				$sid,
228 3
				$pid,
229
				$value
230 3
			);
231 1
		}
232
233
		$this->log( __METHOD__ . ' (procTime in sec: '. Timer::getElapsedTime( __METHOD__, 5 ) . ')' );
234 3
	}
235 3
236
	private function doCreateCompositeUpdate( TableChangeOp $dataChangeOp, &$updates, $ids ) {
237
238
		$searchTable = $this->searchTableUpdater->getSearchTable();
239
240 3
		foreach ( $dataChangeOp->getFieldChangeOps() as $fieldChangeOp ) {
241 3
242 2
			// Exempted property -> out
243 2
			if ( !$fieldChangeOp->has( 'p_id' ) || $searchTable->isExemptedPropertyById( $fieldChangeOp->get( 'p_id' ) ) ) {
244
				continue;
245 3
			}
246
247 3
			$sid = $fieldChangeOp->get( 's_id' );
248
			$pid = $fieldChangeOp->get( 'p_id' );
249 3
250 3
			// Check whether changes occured for a matchable pair of subject/property
251 3
			// IDs
252
			if ( !isset( $ids[$sid] ) && !isset( $ids[$pid] ) ) {
253 3
				continue;
254
			}
255 3
256 2
			if ( !$fieldChangeOp->has( 'o_blob' ) && !$fieldChangeOp->has( 'o_hash' ) && !$fieldChangeOp->has( 'o_serialized' ) && !$fieldChangeOp->has( 'o_id' ) ) {
257
				continue;
258 2
			}
259
260
			// Re-map (url type)
261
			if ( $fieldChangeOp->has( 'o_serialized' ) ) {
262
				$fieldChangeOp->set( 'o_blob', $fieldChangeOp->get( 'o_serialized' ) );
263 2
			}
264
265
			// Re-map (wpg type)
266
			if ( $fieldChangeOp->has( 'o_id' ) ) {
267 2
				$dataItem = $searchTable->getDataItemById( $fieldChangeOp->get( 'o_id' ) );
268 2
				$fieldChangeOp->set( 'o_blob', $dataItem !== null ? $dataItem->getSortKey() : 'NO_TEXT' );
269
			}
270
271
			// If the blob value is empty then the DIHandler has put any text < 72
272
			// into the hash field
273 2
			$text = $fieldChangeOp->get( 'o_blob' );
274
			$key = $sid . ':' . $pid;
275 3
276
			if ( $text === null || $text === '' ) {
277 3
				$text = $fieldChangeOp->get( 'o_hash' );
278
			}
279 3
280 3
			$updates[$key] = !isset( $updates[$key] ) ? $text : $updates[$key] . ' ' . $text;
281
		}
282 3
	}
283
284
	private function doDeleteFromTableChangeOps( array $tableChangeOps ) {
285
		foreach ( $tableChangeOps as $tableChangeOp ) {
286 3
			$this->doDeleteFromTableChangeOp( $tableChangeOp );
287
		}
288
	}
289
290
	private function doDeleteFromTableChangeOp( TableChangeOp $tableChangeOp ) {
291 3
292 3
		foreach ( $tableChangeOp->getFieldChangeOps( 'delete' ) as $fieldChangeOp ) {
293
294
			// Replace s_id for subobjects etc. with the o_id
295
			if ( $tableChangeOp->isFixedPropertyOp() ) {
296
				$fieldChangeOp->set( 's_id', $fieldChangeOp->has( 'o_id' ) ? $fieldChangeOp->get( 'o_id' ) : $fieldChangeOp->get( 's_id' ) );
297 3
				$fieldChangeOp->set( 'p_id', $tableChangeOp->getFixedPropertyValueBy( 'p_id' ) );
298
			}
299 3
300
			if ( !$fieldChangeOp->has( 'p_id' ) ) {
301 1
				continue;
302
			}
303 1
304 1
			$this->searchTableUpdater->delete(
305
				$fieldChangeOp->get( 's_id' ),
306
				$fieldChangeOp->get( 'p_id' )
307 1
			);
308 1
		}
309 1
	}
310 1
311 1
	private function canPostUpdate( $compositePropertyTableDiffIterator ) {
312
313
		$searchTable = $this->searchTableUpdater->getSearchTable();
314
		$canPostUpdate = false;
315
316 1
		// Find out whether we should actual initiate an update
317
		foreach ( $compositePropertyTableDiffIterator->getCombinedIdListOfChangedEntities() as $id ) {
318
			if ( ( $dataItem = $searchTable->getDataItemById( $id ) ) instanceof DIWikiPage && $dataItem->getNamespace() === SMW_NS_PROPERTY ) {
319
				if ( !$searchTable->isExemptedPropertyById( $id ) ) {
320
					$canPostUpdate = true;
321
					break;
322
				}
323
			}
324
		}
325
326
		return $canPostUpdate;
327
	}
328
329
	private function log( $message, $context = array() ) {
330
331
		if ( $this->logger === null ) {
332
			return;
333
		}
334
335
		$this->logger->info( $message, $context );
336
	}
337
338
}
339