Completed
Push — master ( 3184dc...186529 )
by mw
36:28
created

src/SQLStore/QueryEngine/QueryEngine.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace SMW\SQLStore\QueryEngine;
4
5
use RuntimeException;
6
use SMW\DIWikiPage;
7
use SMW\InvalidPredefinedPropertyException;
8
use SMW\Query\DebugOutputFormatter as QueryDebugOutputFormatter;
9
use SMW\Query\Language\Conjunction;
10
use SMW\Query\Language\SomeProperty;
11
use SMW\Query\Language\ThingDescription;
12
use SMWDataItem as DataItem;
13
use SMWPropertyValue as PropertyValue;
14
use SMWQuery as Query;
15
use SMWQueryResult as QueryResult;
16
use SMWSql3SmwIds;
17
use SMWSQLStore3 as SQLStore;
18
use SMW\QueryEngine as QueryEngineInterface;
19
use SMW\QueryFactory;
20
21
/**
22
 * Class that implements query answering for SQLStore.
23
 *
24
 * @license GNU GPL v2+
25
 * @since 2.2
26
 *
27
 * @author Markus Krötzsch
28
 * @author Jeroen De Dauw
29
 * @author mwjames
30
 */
31
class QueryEngine implements QueryEngineInterface {
32
33
	/**
34
	 * @var SQLStore
35
	 */
36
	private $store;
37
38
	/**
39
	 * Query mode copied from given query. Some submethods act differently when
40
	 * in Query::MODE_DEBUG.
41
	 *
42
	 * @var int
43
	 */
44
	private $queryMode;
45
46
	/**
47
	 * Array of generated QuerySegment query descriptions (index => object)
48
	 *
49
	 * @var QuerySegment[]
50
	 */
51
	private $querySegmentList = array();
52
53
	/**
54
	 * Array of sorting requests ("Property_name" => "ASC"/"DESC"). Used during
55
	 * query processing (where these property names are searched while compiling
56
	 * the query conditions).
57
	 *
58
	 * @var string[]
59
	 */
60
	private $sortKeys;
61
62
	/**
63
	 * Local collection of error strings, passed on to callers if possible.
64
	 *
65
	 * @var string[]
66
	 */
67
	private $errors = array();
68
69
	/**
70
	 * @var QuerySegmentListBuilder
71
	 */
72
	private $querySegmentListBuilder;
73
74
	/**
75
	 * @var QuerySegmentListProcessor
76
	 */
77
	private $querySegmentListProcessor;
78
79
	/**
80
	 * @var EngineOptions
81
	 */
82
	private $engineOptions;
83
84
	/**
85
	 * @var OrderConditionsComplementor
86
	 */
87
	private $orderConditionsComplementor;
88
89
	/**
90
	 * @var QueryFactory
91 178
	 */
92 178
	private $queryFactory;
93 178
94 178
	/**
95 178
	 * @since 2.2
96 178
	 *
97
	 * @param SQLStore $parentStore
98
	 * @param QuerySegmentListBuilder $querySegmentListBuilder
99
	 * @param QuerySegmentListProcessor $querySegmentListProcessor
100
	 * @param EngineOptions $engineOptions
101
	 */
102
	public function __construct( SQLStore $parentStore, QuerySegmentListBuilder $querySegmentListBuilder, QuerySegmentListProcessor $querySegmentListProcessor, EngineOptions $engineOptions ) {
103 5
		$this->store = $parentStore;
104 5
		$this->querySegmentListBuilder = $querySegmentListBuilder;
105
		$this->querySegmentListProcessor = $querySegmentListProcessor;
106
		$this->engineOptions = $engineOptions;
107
		$this->orderConditionsComplementor = new OrderConditionsComplementor( $querySegmentListBuilder );
108
		$this->queryFactory = new QueryFactory();
109
	}
110
111
	/**
112 5
	 * @since 2.2
113 5
	 *
114
	 * @return QuerySegmentListBuilder
115
	 */
116
	public function getQuerySegmentListBuilder() {
117
		return $this->querySegmentListBuilder;
118
	}
119
120
	/**
121
	 * @since 2.2
122
	 *
123
	 * @return QuerySegmentListProcessor
124
	 */
125
	public function getQuerySegmentListProcessor() {
126
		return $this->querySegmentListProcessor;
127
	}
128
129
	/**
130
	 * The new SQL store's implementation of query answering. This function
131
	 * works in two stages: First, the nested conditions of the given query
132
	 * object are preprocessed to compute an abstract representation of the
133
	 * SQL query that is to be executed. Since query conditions correspond to
134
	 * joins with property tables in most cases, this abstract representation
135
	 * is essentially graph-like description of how property tables are joined.
136
	 * Moreover, this graph is tree-shaped, since all query conditions are
137
	 * tree-shaped. Each part of this abstract query structure is represented
138
	 * by an QuerySegment object in the array querySegmentList.
139
	 *
140
	 * As a second stage of processing, the thus prepared SQL query is actually
141
	 * executed. Typically, this means that the joins are collapsed into one
142
	 * SQL query to retrieve results. In some cases, such as in dbug mode, the
143
	 * execution might be restricted and not actually perform the whole query.
144
	 *
145
	 * The two-stage process helps to separate tasks, and it also allows for
146 171
	 * better optimisations: it is left to the execution engine how exactly the
147
	 * query result is to be obtained. For example, one could pre-compute
148 171
	 * partial suib-results in temporary tables (or even cache them somewhere),
149 171
	 * instead of passing one large join query to the DB (of course, it might
150 171
	 * be large only if the configuration of SMW allows it). For some DBMS, a
151 3
	 * step-wise execution of the query might lead to better performance, since
152
	 * it exploits the tree-structure of the joins, which is important for fast
153
	 * processing -- not all DBMS might be able in seeing this by themselves.
154 169
	 *
155
	 * @param Query $query
156 9
	 *
157
	 * @return mixed depends on $query->querymode
158
	 */
159 164
	public function getQueryResult( Query $query ) {
160
161 164
		if ( ( !$this->engineOptions->get( 'smwgIgnoreQueryErrors' ) || $query->getDescription() instanceof ThingDescription ) &&
162 164
		     $query->querymode != Query::MODE_DEBUG &&
163
		     count( $query->getErrors() ) > 0 ) {
164 164
			return $this->queryFactory->newQueryResult( $this->store, $query, array(), false );
165 164
			// NOTE: we check this here to prevent unnecessary work, but we check
166 164
			// it after query processing below again in case more errors occurred.
167
		} elseif ( $query->querymode == Query::MODE_NONE || $query->getLimit() < 1 ) {
168
			// don't query, but return something to printer
169 164
			return $this->queryFactory->newQueryResult( $this->store, $query, array(), true );
170 164
		}
171 164
172 164
		$connection = $this->store->getConnection( 'mw.db.queryengine' );
173
174 164
		$this->queryMode = $query->querymode;
175
		$this->querySegmentList = array();
176
177 164
		$this->errors = array();
178 164
		QuerySegment::$qnum = 0;
179
		$this->sortKeys = $query->sortkeys;
180 164
181 164
		$rootid = $this->getQuerySegmentFrom(
182 164
			$connection,
0 ignored issues
show
$connection is of type object<SMW\MediaWiki\Database>, but the function expects a object<SMW\SQLStore\QueryEngine\Database>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
183
			$query
184 164
		);
185
186
		// Possibly stop if new errors happened:
187
		if ( !$this->engineOptions->get( 'smwgIgnoreQueryErrors' ) &&
188
				$query->querymode != Query::MODE_DEBUG &&
189
				count( $this->errors ) > 0 ) {
190
			$query->addErrors( $this->errors );
191 164
			return $this->queryFactory->newQueryResult( $this->store, $query, array(), false );
192
		}
193 161
194 161
		// *** Now execute the computed query ***//
195 161
		$this->querySegmentListProcessor->setQueryMode( $this->queryMode );
196 161
		$this->querySegmentListProcessor->setQuerySegmentList( $this->querySegmentList );
197 161
198
		// execute query tree, resolve all dependencies
199 7
		$this->querySegmentListProcessor->doResolveQueryDependenciesById(
200
			$rootid
201
		);
202
203
		$this->applyExtraWhereCondition(
204 164
			$connection,
205 163
			$rootid
206
		);
207
208
		// #835
209 164
		// SELECT DISTINCT and ORDER BY RANDOM causes an issue for postgres
210 164
		// Disable RANDOM support for postgres
211 164
		if ( $connection->isType( 'postgres' ) ) {
212
			$this->engineOptions->set( 'smwgQRandSortingSupport', false );
213
		}
214
215
		switch ( $query->querymode ) {
216
			case Query::MODE_DEBUG:
217 164
				$result = $this->getDebugQueryResult( $query, $rootid );
218 164
			break;
219
			case Query::MODE_COUNT:
220
				$result = $this->getCountQueryResult( $query, $rootid );
221 164
			break;
222
			default:
223 164
				$result = $this->getInstanceQueryResult( $query, $rootid );
224
			break;
225 164
		}
226 164
227 2
		$this->querySegmentListProcessor->cleanUp();
228 2
		$query->addErrors( $this->errors );
229 162
230 2
		return $result;
231 2
	}
232
233 162
	/**
234 162
	 * Compute abstract representation of the query (compilation)
235
	 *
236
	 * @param Database $connection
237 164
	 * @param Query $query
238 164
	 *
239
	 * @return integer
240 164
	 */
241
	private function getQuerySegmentFrom( $connection, $query ) {
242
243
		// Anchor IT_TABLE as root element
244
		$rootSegmentNumber = QuerySegment::$qnum;
245
		$rootSegment = new QuerySegment();
246
		$rootSegment->joinTable = SQLStore::ID_TABLE;
247
		$rootSegment->joinfield = "$rootSegment->alias.smw_id";
248
249
		$this->querySegmentListBuilder->addQuerySegment(
250
			$rootSegment
251
		);
252 2
253
		$this->querySegmentListBuilder->setSortKeys(
254 2
			$this->sortKeys
255 2
		);
256
257 2
		// compile query, build query "plan"
258
		$this->querySegmentListBuilder->getQuerySegmentFrom(
259 2
			$query->getDescription()
0 ignored issues
show
It seems like $query->getDescription() targeting SMWQuery::getDescription() can also be of type null; however, SMW\SQLStore\QueryEngine...::getQuerySegmentFrom() does only seem to accept object<SMW\Query\Language\Description>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
260 2
		);
261
262 2
		$qid = $this->querySegmentListBuilder->getLastQuerySegmentId();
263 2
		$this->querySegmentList = $this->querySegmentListBuilder->getQuerySegmentList();
264
		$this->errors = $this->querySegmentListBuilder->getErrors();
265 2
266
		// no valid/supported condition; ensure that at least only proper pages
267
		// are delivered
268
		if ( $qid < 0 ) {
269
			$qid = $rootSegmentNumber;
270
			$qobj = $this->querySegmentList[$rootSegmentNumber];
271
			$qobj->where = "$qobj->alias.smw_iw!=" . $connection->addQuotes( SMW_SQL3_SMWIW_OUTDATED ) .
272
				" AND $qobj->alias.smw_iw!=" . $connection->addQuotes( SMW_SQL3_SMWREDIIW ) .
273 2
				" AND $qobj->alias.smw_iw!=" . $connection->addQuotes( SMW_SQL3_SMWBORDERIW ) .
274
				" AND $qobj->alias.smw_iw!=" . $connection->addQuotes( SMW_SQL3_SMWINTDEFIW );
275
			$this->querySegmentListBuilder->addQuerySegment( $qobj );
276 2
		}
277
278
		if ( isset( $this->querySegmentList[$qid]->joinTable ) && $this->querySegmentList[$qid]->joinTable != SQLStore::ID_TABLE ) {
279 2
			// manually make final root query (to retrieve namespace,title):
280
			$rootid = $rootSegmentNumber;
281
			$qobj = $this->querySegmentList[$rootSegmentNumber];
282 2
			$qobj->components = array( $qid => "$qobj->alias.smw_id" );
283
			$qobj->sortfields = $this->querySegmentList[$qid]->sortfields;
284 2
			$this->querySegmentListBuilder->addQuerySegment( $qobj );
285 1
		} else { // not such a common case, but worth avoiding the additional inner join:
286
			$rootid = $qid;
287
		}
288 1
289 1
		// Include order conditions (may extend query if needed for sorting):
290
		$this->orderConditionsComplementor->isSupported(
291
			$this->engineOptions->get( 'smwgQSortingSupport' )
0 ignored issues
show
$this->engineOptions->get('smwgQSortingSupport') is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
292 1
		);
293 1
294 1
		$this->orderConditionsComplementor->setSortKeys(
295 1
			$this->sortKeys
296 1
		);
297 1
298 1
		$this->querySegmentList = $this->orderConditionsComplementor->applyOrderConditions(
299 1
			$rootid
300 1
		);
301 1
302 1
		$this->sortKeys = $this->orderConditionsComplementor->getSortKeys();
303
		$this->errors = $this->orderConditionsComplementor->getErrors();
304 1
305 1
		return $rootid;
306 1
	}
307
308
	/**
309 1
	 * Using a preprocessed internal query description referenced by $rootid, compute
310 1
	 * the proper debug output for the given query.
311
	 *
312 1
	 * @param Query $query
313 1
	 * @param integer $rootid
314
	 *
315
	 * @return string
316
	 */
317
	private function getDebugQueryResult( Query $query, $rootid ) {
318
319
		$qobj = $this->querySegmentList[$rootid];
320
		$entries = array();
321
322
		$sqlOptions = $this->getSQLOptions( $query, $rootid );
323
324 2
		$entries['SQL Query'] = '';
325
		$entries['SQL Explain'] = '';
326 2
327 2
		$this->doExecuteDebugQueryResult( $qobj, $sqlOptions, $entries );
328
		$auxtables = '';
329 2
330 2
		foreach ( $this->querySegmentListProcessor->getListOfResolvedQueries() as $table => $log ) {
331 2
			$auxtables .= "<li>Temporary table $table";
332
			foreach ( $log as $q ) {
333
				$auxtables .= "<br />&#160;&#160;<tt>$q</tt>";
334 2
			}
335
			$auxtables .= '</li>';
336 2
		}
337
338 2
		if ( $auxtables ) {
339
			$entries['Auxilliary Tables'] = "<ul>$auxtables</ul>";
340
		} else {
341
			$entries['Auxilliary Tables'] = 'No auxilliary tables used.';
342 2
		}
343
344 2
		return QueryDebugOutputFormatter::getStringFrom( 'SQLStore', $entries, $query );
345
	}
346 2
347 2
	private function doExecuteDebugQueryResult( $qobj, $sqlOptions, &$entries ) {
348 2
349 2
		if ( !isset( $qobj->joinfield ) || $qobj->joinfield === '' ) {
350 2
			return $entries['SQL Query'] = 'Empty result, no SQL query created.';
351
		}
352
353
		$connection = $this->store->getConnection( 'mw.db.queryengine' );
354 2
		list( $startOpts, $useIndex, $tailOpts ) = $connection->makeSelectOptions( $sqlOptions );
355
356 2
		$sql = "SELECT DISTINCT ".
357 2
			"$qobj->alias.smw_id AS id," .
358
			"$qobj->alias.smw_title AS t," .
359 2
			"$qobj->alias.smw_namespace AS ns," .
360
			"$qobj->alias.smw_iw AS iw," .
361 2
			"$qobj->alias.smw_subobject AS so," .
362
			"$qobj->alias.smw_sortkey AS sortkey " .
363
			"FROM " .
364
			$connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from .
365
			( $qobj->where === '' ? '':' WHERE ' ) . $qobj->where . "$tailOpts $startOpts $useIndex ".
366
			"LIMIT " . $sqlOptions['LIMIT'] . ' ' .
367
			"OFFSET " . $sqlOptions['OFFSET'];
368
369
		$res = $connection->query(
370
			'EXPLAIN '. $sql,
371
			__METHOD__
372
		);
373
374
		$entries['SQL Explain'] = QueryDebugOutputFormatter::doFormatSQLExplainOutput( $connection->getType(), $res );
375
		$entries['SQL Query'] = QueryDebugOutputFormatter::doFormatSQLStatement( $sql, $qobj->alias );
376
377
		$connection->freeResult( $res );
378
	}
379
380
	/**
381
	 * Using a preprocessed internal query description referenced by $rootid, compute
382 162
	 * the proper counting output for the given query.
383
	 *
384 162
	 * @param Query $query
385 162
	 * @param integer $rootid
386
	 *
387 162
	 * @return integer
388
	 */
389 162
	private function getCountQueryResult( Query $query, $rootid ) {
390 5
391 5
		$queryResult = $this->queryFactory->newQueryResult(
392
			$this->store,
393
			$query,
394 161
			array(),
395
			false
396
		);
397 161
398
		$queryResult->setCountValue( 0 );
399 161
400 161
		$qobj = $this->querySegmentList[$rootid];
401 161
402 161
		if ( $qobj->joinfield === '' ) { // empty result, no query needed
403 161
			return $queryResult;
404 161
		}
405
406
		$connection = $this->store->getConnection( 'mw.db.queryengine' );
407
408 161
		$sql_options = array( 'LIMIT' => $query->getLimit() + 1, 'OFFSET' => $query->getOffset() );
409
410 161
		$res = $connection->select(
411 161
			$connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from,
412 161
			"COUNT(DISTINCT $qobj->alias.smw_id) AS count",
413 161
			$qobj->where,
414 161
			__METHOD__,
415
			$sql_options
416 161
		);
417
418 161
		$row = $connection->fetchObject( $res );
419
420 161
		$count = $row->count;
421 156
		$connection->freeResult( $res );
422
423
		$queryResult->setCountValue( $count );
424
425
		return $queryResult;
426 156
	}
427 156
428 156
	/**
429 156
	 * Using a preprocessed internal query description referenced by $rootid,
430 156
	 * compute the proper result instance output for the given query.
431 156
	 * @todo The SQL standard requires us to select all fields by which we sort, leading
432
	 * to wrong results regarding the given limit: the user expects limit to be applied to
433
	 * the number of distinct pages, but we can use DISTINCT only to whole rows. Thus, if
434
	 * rows contain sortfields, then pages with multiple values for that field are distinct
435
	 * and appear multiple times in the result. Filtering duplicates in post processing
436
	 * would still allow such duplicates to push aside wanted values, leading to less than
437
	 * "limit" results although there would have been "limit" really distinct results. For
438
	 * this reason, we select sortfields only for POSTGRES. MySQL is able to perform what
439 156
	 * we want here. It would be nice if we could eliminate the bug in POSTGRES as well.
440 156
	 *
441 156
	 * @param Query $query
442 156
	 * @param integer $rootid
443
	 *
444 156
	 * @return QueryResult
445
	 */
446
	private function getInstanceQueryResult( Query $query, $rootid ) {
447 156
448
		$connection = $this->store->getConnection( 'mw.db.queryengine' );
449
		$qobj = $this->querySegmentList[$rootid];
450
451
		// Empty result, no query needed
452
		if ( $qobj->joinfield === '' ) {
453
			return $this->queryFactory->newQueryResult(
454
				$this->store,
455 161
				$query,
456 5
				array(),
457
				false
458
			);
459 161
		}
460
461
		$sql_options = $this->getSQLOptions( $query, $rootid );
462
463 161
		// Selecting those is required in standard SQL (but MySQL does not require it).
464 5
		$sortfields = implode( $qobj->sortfields, ',' );
465
		$sortfields = $connection->isType( 'postgres' ) ? ( ( $sortfields ? ',' : '' ) . $sortfields ) : '';
466
467 161
		$res = $connection->select(
468 161
			$connection->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from,
469
			"DISTINCT ".
470 161
			"$qobj->alias.smw_id AS id," .
471
			"$qobj->alias.smw_title AS t," .
472
			"$qobj->alias.smw_namespace AS ns," .
473
			"$qobj->alias.smw_iw AS iw," .
474
			"$qobj->alias.smw_subobject AS so," .
475
			"$qobj->alias.smw_sortkey AS sortkey" .
476
			"$sortfields",
477
			$qobj->where,
478
			__METHOD__,
479
			$sql_options
480 163
		);
481 163
482
		$results = array();
483 163
		$dataItemCache = array();
484
485 163
		$logToTable = array();
486 5
		$hasFurtherResults = false;
487
488 163
		 // Number of fetched results ( != number of valid results in
489
		 // array $results)
490 164
		$count = 0;
491
		$missedCount = 0;
492 164
493
		$diHandler = $this->store->getDataItemHandlerForDIType(
494 164
			DataItem::TYPE_WIKIPAGE
495 1
		);
496
497
		while ( ( $count < $query->getLimit() ) && ( $row = $connection->fetchObject( $res ) ) ) {
498 163
			if ( $row->iw === '' || $row->iw{0} != ':' )  {
499
500
				// Catch exception for non-existing predefined properties that
501
				// still registered within non-updated pages (@see bug 48711)
502 163
				try {
503 163
					$dataItem = $diHandler->dataItemFromDBKeys( array(
504
						$row->t,
505
						intval( $row->ns ),
506 163
						$row->iw,
507 163
						'',
508
						$row->so
509
					) );
510 163
				} catch ( InvalidPredefinedPropertyException $e ) {
511 163
					$logToTable[$row->t] = "issue creating a {$row->t} dataitem from a database row";
512
					wfDebugLog( 'smw', __METHOD__ . ' ' . $e->getMessage() . "\n" );
513
					$dataItem = '';
514 163
				}
515 163
516
				if ( $dataItem instanceof DIWikiPage && !isset( $dataItemCache[$dataItem->getHash()] ) ) {
517 163
					$count++;
518 163
					$dataItemCache[$dataItem->getHash()] = true;
519
					$results[] = $dataItem;
520 163
					// These IDs are usually needed for displaying the page (esp. if more property values are displayed):
521
					$this->store->smwIds->setCache( $row->t, $row->ns, $row->iw, $row->so, $row->id, $row->sortkey );
522 89
				} else {
523
					$missedCount++;
524
					$logToTable[$row->t] = "skip result for {$row->t} existing cache entry / query " . $query->getHash();
525
				}
526 89
			} else {
527 81
				$missedCount++;
528 77
				$logToTable[$row->t] = "skip result for {$row->t} due to an internal `{$row->iw}` pointer / query " . $query->getHash();
529 7
			}
530
		}
531
532
		if ( $connection->fetchObject( $res ) ) {
533
			$count++;
534
		}
535 2
536
		if ( $logToTable !== array() ) {
537 5
			wfDebugLog( 'smw', __METHOD__ . ' ' . implode( ',', $logToTable ) . "\n" );
538
		}
539 5
540 89
		if ( $count > $query->getLimit() || ( $count + $missedCount ) > $query->getLimit() ) {
541
			$hasFurtherResults = true;
542
		};
543
544
		$connection->freeResult( $res );
545
546 163
		$queryResult = $this->queryFactory->newQueryResult(
547
			$this->store,
548
			$query,
549 5
			$results,
550 5
			$hasFurtherResults
551 5
		);
552
553 5
		return $queryResult;
554 5
	}
555 5
556
	private function applyExtraWhereCondition( $connection, $qid ) {
557 5
558
		if ( !isset( $this->querySegmentList[$qid] ) ) {
559 5
			return null;
560 5
		}
561 5
562
		$qobj = $this->querySegmentList[$qid];
563
564 5
		// Filter elements that should never appear in a result set
565 5
		$extraWhereCondition = array(
566
			'del'  => "$qobj->alias.smw_iw!=" . $connection->addQuotes( SMW_SQL3_SMWIW_OUTDATED ) . " AND $qobj->alias.smw_iw!=" . $connection->addQuotes( SMW_SQL3_SMWDELETEIW ),
567
			'redi' => "$qobj->alias.smw_iw!=" . $connection->addQuotes( SMW_SQL3_SMWREDIIW )
568
		);
569
570
		if ( strpos( $qobj->where, SMW_SQL3_SMWIW_OUTDATED ) === false ) {
571
			$qobj->where .= $qobj->where === '' ? $extraWhereCondition['del'] : " AND " . $extraWhereCondition['del'];
572
		}
573
574
		if ( strpos( $qobj->where, SMW_SQL3_SMWREDIIW ) === false ) {
575 163
			$qobj->where .= $qobj->where === '' ? $extraWhereCondition['redi'] : " AND " . $extraWhereCondition['redi'];
576
		}
577 163
578
		$this->querySegmentList[$qid] = $qobj;
579
	}
580 163
581 162
	/**
582 162
	 * Get a SQL option array for the given query and preprocessed query object at given id.
583
	 *
584 162
	 * @param Query $query
585
	 * @param integer $rootId
586 89
	 *
587
	 * @return array
588
	 */
589
	private function getSQLOptions( Query $query, $rootId ) {
590
591
		$result = array(
592
			'LIMIT' => $query->getLimit() + 5,
593 89
			'OFFSET' => $query->getOffset()
594
		);
595
596
		if ( !$this->engineOptions->get( 'smwgQSortingSupport' ) ) {
597 89
			return $result;
598 88
		}
599 1
600 89
		// Build ORDER BY options using discovered sorting fields.
601
		$qobj = $this->querySegmentList[$rootId];
602
603
		foreach ( $this->sortKeys as $propkey => $order ) {
604 163
605
			if ( !is_string( $propkey ) ) {
606
				throw new RuntimeException( "Expected a string value as sortkey" );
607
			}
608
609
			if ( ( $order != 'RANDOM' ) && array_key_exists( $propkey, $qobj->sortfields ) ) { // Field was successfully added.
610
				$result['ORDER BY'] = ( array_key_exists( 'ORDER BY', $result ) ? $result['ORDER BY'] . ', ' : '' ) . $qobj->sortfields[$propkey] . " $order ";
611
			} elseif ( ( $order == 'RANDOM' ) && $this->engineOptions->get( 'smwgQRandSortingSupport' ) ) {
612
				$result['ORDER BY'] = ( array_key_exists( 'ORDER BY', $result ) ? $result['ORDER BY'] . ', ' : '' ) . ' RAND() ';
613
			}
614
		}
615
616
		return $result;
617
	}
618
619
}
620