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 | |||
20 | /** |
||
21 | * Class that implements query answering for SQLStore. |
||
22 | * |
||
23 | * @license GNU GPL v2+ |
||
24 | * @since 2.2 |
||
25 | * |
||
26 | * @author Markus Krötzsch |
||
27 | * @author Jeroen De Dauw |
||
28 | * @author mwjames |
||
29 | */ |
||
30 | class QueryEngine implements QueryEngineInterface { |
||
31 | |||
32 | /** |
||
33 | * @var SQLStore |
||
34 | */ |
||
35 | private $store; |
||
36 | |||
37 | /** |
||
38 | * Query mode copied from given query. Some submethods act differently when |
||
39 | * in Query::MODE_DEBUG. |
||
40 | * |
||
41 | * @var int |
||
42 | */ |
||
43 | private $queryMode; |
||
44 | |||
45 | /** |
||
46 | * Array of generated QuerySegment query descriptions (index => object) |
||
47 | * |
||
48 | * @var QuerySegment[] |
||
49 | */ |
||
50 | private $querySegmentList = array(); |
||
51 | |||
52 | /** |
||
53 | * Array of sorting requests ("Property_name" => "ASC"/"DESC"). Used during |
||
54 | * query processing (where these property names are searched while compiling |
||
55 | * the query conditions). |
||
56 | * |
||
57 | * @var string[] |
||
58 | */ |
||
59 | private $sortKeys; |
||
60 | |||
61 | /** |
||
62 | * Local collection of error strings, passed on to callers if possible. |
||
63 | * |
||
64 | * @var string[] |
||
65 | */ |
||
66 | private $errors = array(); |
||
67 | |||
68 | /** |
||
69 | * @var QuerySegmentListBuilder |
||
70 | */ |
||
71 | private $querySegmentListBuilder = null; |
||
72 | |||
73 | /** |
||
74 | * @var QuerySegmentListProcessor |
||
75 | */ |
||
76 | private $querySegmentListProcessor = null; |
||
77 | |||
78 | /** |
||
79 | * @var EngineOptions |
||
80 | */ |
||
81 | private $engineOptions = null; |
||
82 | |||
83 | /** |
||
84 | * @since 2.2 |
||
85 | * |
||
86 | * @param SQLStore $parentStore |
||
87 | * @param QuerySegmentListBuilder $querySegmentListBuilder |
||
88 | * @param QuerySegmentListProcessor $querySegmentListProcessor |
||
89 | * @param EngineOptions $engineOptions |
||
90 | */ |
||
91 | 178 | public function __construct( SQLStore $parentStore, QuerySegmentListBuilder $querySegmentListBuilder, QuerySegmentListProcessor $querySegmentListProcessor, EngineOptions $engineOptions ) { |
|
92 | 178 | $this->store = $parentStore; |
|
93 | 178 | $this->querySegmentListBuilder = $querySegmentListBuilder; |
|
94 | 178 | $this->querySegmentListProcessor = $querySegmentListProcessor; |
|
95 | 178 | $this->engineOptions = $engineOptions; |
|
96 | 178 | } |
|
97 | |||
98 | /** |
||
99 | * @since 2.2 |
||
100 | * |
||
101 | * @return QuerySegmentListBuilder |
||
102 | */ |
||
103 | 5 | public function getQuerySegmentListBuilder() { |
|
104 | 5 | return $this->querySegmentListBuilder; |
|
105 | } |
||
106 | |||
107 | /** |
||
108 | * @since 2.2 |
||
109 | * |
||
110 | * @return QuerySegmentListProcessor |
||
111 | */ |
||
112 | 5 | public function getQuerySegmentListProcessor() { |
|
113 | 5 | return $this->querySegmentListProcessor; |
|
114 | } |
||
115 | |||
116 | /** |
||
117 | * The new SQL store's implementation of query answering. This function |
||
118 | * works in two stages: First, the nested conditions of the given query |
||
119 | * object are preprocessed to compute an abstract representation of the |
||
120 | * SQL query that is to be executed. Since query conditions correspond to |
||
121 | * joins with property tables in most cases, this abstract representation |
||
122 | * is essentially graph-like description of how property tables are joined. |
||
123 | * Moreover, this graph is tree-shaped, since all query conditions are |
||
124 | * tree-shaped. Each part of this abstract query structure is represented |
||
125 | * by an QuerySegment object in the array m_queries. |
||
126 | * |
||
127 | * As a second stage of processing, the thus prepared SQL query is actually |
||
128 | * executed. Typically, this means that the joins are collapsed into one |
||
129 | * SQL query to retrieve results. In some cases, such as in dbug mode, the |
||
130 | * execution might be restricted and not actually perform the whole query. |
||
131 | * |
||
132 | * The two-stage process helps to separate tasks, and it also allows for |
||
133 | * better optimisations: it is left to the execution engine how exactly the |
||
134 | * query result is to be obtained. For example, one could pre-compute |
||
135 | * partial suib-results in temporary tables (or even cache them somewhere), |
||
136 | * instead of passing one large join query to the DB (of course, it might |
||
137 | * be large only if the configuration of SMW allows it). For some DBMS, a |
||
138 | * step-wise execution of the query might lead to better performance, since |
||
139 | * it exploits the tree-structure of the joins, which is important for fast |
||
140 | * processing -- not all DBMS might be able in seeing this by themselves. |
||
141 | * |
||
142 | * @param Query $query |
||
143 | * |
||
144 | * @return mixed depends on $query->querymode |
||
145 | */ |
||
146 | 171 | public function getQueryResult( Query $query ) { |
|
147 | |||
148 | 171 | if ( ( !$this->engineOptions->get( 'smwgIgnoreQueryErrors' ) || $query->getDescription() instanceof ThingDescription ) && |
|
149 | 171 | $query->querymode != Query::MODE_DEBUG && |
|
150 | 171 | count( $query->getErrors() ) > 0 ) { |
|
151 | 3 | return new QueryResult( $query->getDescription()->getPrintrequests(), $query, array(), $this->store, false ); |
|
152 | // NOTE: we check this here to prevent unnecessary work, but we check |
||
153 | // it after query processing below again in case more errors occurred. |
||
154 | 169 | } elseif ( $query->querymode == Query::MODE_NONE || $query->getLimit() < 1 ) { |
|
155 | // don't query, but return something to printer |
||
156 | 9 | return new QueryResult( $query->getDescription()->getPrintrequests(), $query, array(), $this->store, true ); |
|
157 | } |
||
158 | |||
159 | 164 | $db = $this->store->getConnection( 'mw.db.queryengine' ); |
|
160 | |||
161 | 164 | $this->queryMode = $query->querymode; |
|
162 | 164 | $this->querySegmentList = array(); |
|
163 | |||
164 | 164 | $this->errors = array(); |
|
165 | 164 | QuerySegment::$qnum = 0; |
|
166 | 164 | $this->sortKeys = $query->sortkeys; |
|
167 | |||
168 | // Anchor IT_TABLE as root element |
||
169 | 164 | $rootSegmentNumber = QuerySegment::$qnum; |
|
170 | 164 | $rootSegment = new QuerySegment(); |
|
171 | 164 | $rootSegment->joinTable = SMWSql3SmwIds::TABLE_NAME; |
|
172 | 164 | $rootSegment->joinfield = "$rootSegment->alias.smw_id"; |
|
173 | |||
174 | 164 | $this->querySegmentListBuilder->addQuerySegment( $rootSegment ); |
|
175 | |||
176 | // *** First compute abstract representation of the query (compilation) ***// |
||
177 | 164 | $this->querySegmentListBuilder->setSortKeys( $this->sortKeys ); |
|
178 | 164 | $this->querySegmentListBuilder->getQuerySegmentFrom( $query->getDescription() ); // compile query, build query "plan" |
|
0 ignored issues
–
show
|
|||
179 | |||
180 | 164 | $qid = $this->querySegmentListBuilder->getLastQuerySegmentId(); |
|
181 | 164 | $this->querySegmentList = $this->querySegmentListBuilder->getQuerySegmentList(); |
|
182 | 164 | $this->errors = $this->querySegmentListBuilder->getErrors(); |
|
183 | |||
184 | 164 | if ( $qid < 0 ) { // no valid/supported condition; ensure that at least only proper pages are delivered |
|
185 | $qid = $rootSegmentNumber; |
||
186 | $q = $this->querySegmentList[$rootSegmentNumber]; |
||
187 | $q->where = "$q->alias.smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWIW_OUTDATED ) . " AND $q->alias.smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWREDIIW ) . " AND $q->alias.smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWBORDERIW ) . " AND $q->alias.smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWINTDEFIW ); |
||
188 | $this->querySegmentList[$rootSegmentNumber] = $q; |
||
189 | } |
||
190 | |||
191 | 164 | if ( isset( $this->querySegmentList[$qid]->joinTable ) && $this->querySegmentList[$qid]->joinTable != SMWSql3SmwIds::TABLE_NAME ) { |
|
192 | // manually make final root query (to retrieve namespace,title): |
||
193 | 161 | $rootid = $rootSegmentNumber; |
|
194 | 161 | $qobj = $this->querySegmentList[$rootSegmentNumber]; |
|
195 | 161 | $qobj->components = array( $qid => "$qobj->alias.smw_id" ); |
|
196 | 161 | $qobj->sortfields = $this->querySegmentList[$qid]->sortfields; |
|
197 | 161 | $this->querySegmentList[$rootSegmentNumber] = $qobj; |
|
198 | } else { // not such a common case, but worth avoiding the additional inner join: |
||
199 | 7 | $rootid = $qid; |
|
200 | } |
||
201 | |||
202 | // var_dump( json_encode( $this->querySegmentList, JSON_PRETTY_PRINT ) ); |
||
0 ignored issues
–
show
Unused Code
Comprehensibility
introduced
by
45% 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...
|
|||
203 | // Include order conditions (may extend query if needed for sorting): |
||
204 | 164 | if ( $this->engineOptions->get( 'smwgQSortingSupport' ) ) { |
|
205 | 163 | $this->applyOrderConditions( $rootid ); |
|
206 | } |
||
207 | |||
208 | // Possibly stop if new errors happened: |
||
209 | 164 | if ( !$this->engineOptions->get( 'smwgIgnoreQueryErrors' ) && |
|
210 | 164 | $query->querymode != Query::MODE_DEBUG && |
|
211 | 164 | count( $this->errors ) > 0 ) { |
|
212 | $query->addErrors( $this->errors ); |
||
213 | return new QueryResult( $query->getDescription()->getPrintrequests(), $query, array(), $this->store, false ); |
||
214 | } |
||
215 | |||
216 | // *** Now execute the computed query ***// |
||
217 | 164 | $this->querySegmentListProcessor->setQueryMode( $this->queryMode ); |
|
218 | 164 | $this->querySegmentListProcessor->setQuerySegmentList( $this->querySegmentList ); |
|
219 | |||
220 | // execute query tree, resolve all dependencies |
||
221 | 164 | $this->querySegmentListProcessor->doResolveQueryDependenciesById( $rootid ); |
|
222 | |||
223 | 164 | $this->applyExtraWhereCondition( $rootid ); |
|
224 | |||
225 | 164 | switch ( $query->querymode ) { |
|
226 | 164 | case Query::MODE_DEBUG: |
|
227 | 2 | $result = $this->getDebugQueryResult( $query, $rootid ); |
|
228 | 2 | break; |
|
229 | 162 | case Query::MODE_COUNT: |
|
230 | 2 | $result = $this->getCountQueryResult( $query, $rootid ); |
|
0 ignored issues
–
show
|
|||
231 | 2 | break; |
|
232 | default: |
||
233 | 162 | $result = $this->getInstanceQueryResult( $query, $rootid ); |
|
234 | 162 | break; |
|
235 | } |
||
236 | |||
237 | 164 | $this->querySegmentListProcessor->cleanUp(); |
|
238 | 164 | $query->addErrors( $this->errors ); |
|
239 | |||
240 | 164 | return $result; |
|
241 | } |
||
242 | |||
243 | /** |
||
244 | * Using a preprocessed internal query description referenced by $rootid, compute |
||
245 | * the proper debug output for the given query. |
||
246 | * |
||
247 | * @param Query $query |
||
248 | * @param integer $rootid |
||
249 | * |
||
250 | * @return string |
||
251 | */ |
||
252 | 2 | private function getDebugQueryResult( Query $query, $rootid ) { |
|
253 | |||
254 | 2 | $qobj = $this->querySegmentList[$rootid]; |
|
255 | 2 | $entries = array(); |
|
256 | |||
257 | 2 | $sqlOptions = $this->getSQLOptions( $query, $rootid ); |
|
258 | |||
259 | 2 | $entries['SQL Query'] = ''; |
|
260 | 2 | $entries['SQL Explain'] = ''; |
|
261 | |||
262 | 2 | $this->doExecuteDebugQueryResult( $qobj, $sqlOptions, $entries ); |
|
263 | 2 | $auxtables = ''; |
|
264 | |||
265 | 2 | foreach ( $this->querySegmentListProcessor->getListOfResolvedQueries() as $table => $log ) { |
|
266 | $auxtables .= "<li>Temporary table $table"; |
||
267 | foreach ( $log as $q ) { |
||
268 | $auxtables .= "<br />  <tt>$q</tt>"; |
||
269 | } |
||
270 | $auxtables .= '</li>'; |
||
271 | } |
||
272 | |||
273 | 2 | if ( $auxtables ) { |
|
274 | $entries['Auxilliary Tables'] = "<ul>$auxtables</ul>"; |
||
275 | } else { |
||
276 | 2 | $entries['Auxilliary Tables'] = 'No auxilliary tables used.'; |
|
277 | } |
||
278 | |||
279 | 2 | return QueryDebugOutputFormatter::getStringFrom( 'SQLStore', $entries, $query ); |
|
280 | } |
||
281 | |||
282 | 2 | private function doExecuteDebugQueryResult( $qobj, $sqlOptions, &$entries ) { |
|
283 | |||
284 | 2 | if ( !isset( $qobj->joinfield ) || $qobj->joinfield === '' ) { |
|
285 | 1 | return $entries['SQL Query'] = 'Empty result, no SQL query created.'; |
|
286 | } |
||
287 | |||
288 | 1 | $db = $this->store->getConnection( 'mw.db.queryengine' ); |
|
289 | 1 | list( $startOpts, $useIndex, $tailOpts ) = $db->makeSelectOptions( $sqlOptions ); |
|
290 | |||
291 | $sql = "SELECT DISTINCT ". |
||
292 | 1 | "$qobj->alias.smw_id AS id," . |
|
293 | 1 | "$qobj->alias.smw_title AS t," . |
|
294 | 1 | "$qobj->alias.smw_namespace AS ns," . |
|
295 | 1 | "$qobj->alias.smw_iw AS iw," . |
|
296 | 1 | "$qobj->alias.smw_subobject AS so," . |
|
297 | 1 | "$qobj->alias.smw_sortkey AS sortkey " . |
|
298 | 1 | "FROM " . |
|
299 | 1 | $db->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from . |
|
300 | 1 | ( $qobj->where === '' ? '':' WHERE ' ) . $qobj->where . "$tailOpts $startOpts $useIndex ". |
|
301 | 1 | "LIMIT " . $sqlOptions['LIMIT'] . ' ' . |
|
302 | 1 | "OFFSET " . $sqlOptions['OFFSET']; |
|
303 | |||
304 | 1 | $res = $db->query( |
|
305 | 1 | 'EXPLAIN '. $sql, |
|
306 | 1 | __METHOD__ |
|
307 | ); |
||
308 | |||
309 | 1 | $entries['SQL Explain'] = QueryDebugOutputFormatter::doFormatSQLExplainOutput( $db->getType(), $res ); |
|
310 | 1 | $entries['SQL Query'] = QueryDebugOutputFormatter::doFormatSQLStatement( $sql, $qobj->alias ); |
|
311 | |||
312 | 1 | $db->freeResult( $res ); |
|
313 | 1 | } |
|
314 | |||
315 | /** |
||
316 | * Using a preprocessed internal query description referenced by $rootid, compute |
||
317 | * the proper counting output for the given query. |
||
318 | * |
||
319 | * @param Query $query |
||
320 | * @param integer $rootid |
||
321 | * |
||
322 | * @return integer |
||
323 | */ |
||
324 | 2 | private function getCountQueryResult( Query $query, $rootid ) { |
|
325 | |||
326 | 2 | $queryResult = new QueryResult( |
|
327 | 2 | $query->getDescription()->getPrintrequests(), |
|
328 | $query, |
||
329 | 2 | array(), |
|
330 | 2 | $this->store, |
|
331 | 2 | false |
|
332 | ); |
||
333 | |||
334 | 2 | $queryResult->setCountValue( 0 ); |
|
335 | |||
336 | 2 | $qobj = $this->querySegmentList[$rootid]; |
|
337 | |||
338 | 2 | if ( $qobj->joinfield === '' ) { // empty result, no query needed |
|
339 | return 0; |
||
340 | } |
||
341 | |||
342 | 2 | $db = $this->store->getConnection( 'mw.db.queryengine' ); |
|
343 | |||
344 | 2 | $sql_options = array( 'LIMIT' => $query->getLimit() + 1, 'OFFSET' => $query->getOffset() ); |
|
345 | |||
346 | 2 | $res = $db->select( |
|
347 | 2 | $db->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from, |
|
348 | 2 | "COUNT(DISTINCT $qobj->alias.smw_id) AS count", |
|
349 | 2 | $qobj->where, |
|
350 | 2 | __METHOD__, |
|
351 | $sql_options |
||
352 | ); |
||
353 | |||
354 | 2 | $row = $db->fetchObject( $res ); |
|
355 | |||
356 | 2 | $count = $row->count; |
|
357 | 2 | $db->freeResult( $res ); |
|
358 | |||
359 | 2 | $queryResult->setCountValue( $count ); |
|
360 | |||
361 | 2 | return $queryResult; |
|
362 | } |
||
363 | |||
364 | /** |
||
365 | * Using a preprocessed internal query description referenced by $rootid, |
||
366 | * compute the proper result instance output for the given query. |
||
367 | * @todo The SQL standard requires us to select all fields by which we sort, leading |
||
368 | * to wrong results regarding the given limit: the user expects limit to be applied to |
||
369 | * the number of distinct pages, but we can use DISTINCT only to whole rows. Thus, if |
||
370 | * rows contain sortfields, then pages with multiple values for that field are distinct |
||
371 | * and appear multiple times in the result. Filtering duplicates in post processing |
||
372 | * would still allow such duplicates to push aside wanted values, leading to less than |
||
373 | * "limit" results although there would have been "limit" really distinct results. For |
||
374 | * this reason, we select sortfields only for POSTGRES. MySQL is able to perform what |
||
375 | * we want here. It would be nice if we could eliminate the bug in POSTGRES as well. |
||
376 | * |
||
377 | * @param Query $query |
||
378 | * @param integer $rootid |
||
379 | * |
||
380 | * @return QueryResult |
||
381 | */ |
||
382 | 162 | private function getInstanceQueryResult( Query $query, $rootid ) { |
|
383 | |||
384 | 162 | $db = $this->store->getConnection( 'mw.db.queryengine' ); |
|
385 | 162 | $dbType = $db->getType(); |
|
386 | |||
387 | 162 | $qobj = $this->querySegmentList[$rootid]; |
|
388 | |||
389 | 162 | if ( $qobj->joinfield === '' ) { // empty result, no query needed |
|
390 | 5 | $result = new QueryResult( $query->getDescription()->getPrintrequests(), $query, array(), $this->store, false ); |
|
391 | 5 | return $result; |
|
392 | } |
||
393 | |||
394 | 161 | $sql_options = $this->getSQLOptions( $query, $rootid ); |
|
395 | |||
396 | // Selecting those is required in standard SQL (but MySQL does not require it). |
||
397 | 161 | $sortfields = implode( $qobj->sortfields, ',' ); |
|
398 | |||
399 | 161 | $res = $db->select( |
|
400 | 161 | $db->tableName( $qobj->joinTable ) . " AS $qobj->alias" . $qobj->from, |
|
401 | 161 | "DISTINCT $qobj->alias.smw_id AS id,$qobj->alias.smw_title AS t,$qobj->alias.smw_namespace AS ns,$qobj->alias.smw_iw AS iw,$qobj->alias.smw_subobject AS so,$qobj->alias.smw_sortkey AS sortkey" . |
|
402 | 161 | ( $dbType == 'postgres' ? ( ( $sortfields ? ',' : '' ) . $sortfields ) : '' ), |
|
403 | 161 | $qobj->where, |
|
404 | 161 | __METHOD__, |
|
405 | $sql_options |
||
406 | ); |
||
407 | |||
408 | 161 | $qr = array(); |
|
409 | |||
410 | 161 | $count = 0; // the number of fetched results ( != number of valid results in array $qr) |
|
411 | 161 | $missedCount = 0; |
|
412 | 161 | $dataItemCache = array(); |
|
413 | 161 | $logToTable = array(); |
|
414 | 161 | $hasFurtherResults = false; |
|
415 | |||
416 | 161 | $prs = $query->getDescription()->getPrintrequests(); |
|
417 | |||
418 | 161 | $diHandler = $this->store->getDataItemHandlerForDIType( DataItem::TYPE_WIKIPAGE ); |
|
419 | |||
420 | 161 | while ( ( $count < $query->getLimit() ) && ( $row = $db->fetchObject( $res ) ) ) { |
|
421 | 156 | if ( $row->iw === '' || $row->iw{0} != ':' ) { |
|
422 | |||
423 | // Catch exception for non-existing predefined properties that |
||
424 | // still registered within non-updated pages (@see bug 48711) |
||
425 | try { |
||
426 | 156 | $dataItem = $diHandler->dataItemFromDBKeys( array( |
|
427 | 156 | $row->t, |
|
428 | 156 | intval( $row->ns ), |
|
429 | 156 | $row->iw, |
|
430 | 156 | '', |
|
431 | 156 | $row->so |
|
432 | ) ); |
||
433 | } catch ( InvalidPredefinedPropertyException $e ) { |
||
434 | $logToTable[$row->t] = "issue creating a {$row->t} dataitem from a database row"; |
||
435 | wfDebugLog( 'smw', __METHOD__ . ' ' . $e->getMessage() . "\n" ); |
||
436 | $dataItem = ''; |
||
437 | } |
||
438 | |||
439 | 156 | if ( $dataItem instanceof DIWikiPage && !isset( $dataItemCache[$dataItem->getHash()] ) ) { |
|
440 | 156 | $count++; |
|
441 | 156 | $dataItemCache[$dataItem->getHash()] = true; |
|
442 | 156 | $qr[] = $dataItem; |
|
443 | // These IDs are usually needed for displaying the page (esp. if more property values are displayed): |
||
444 | 156 | $this->store->smwIds->setCache( $row->t, $row->ns, $row->iw, $row->so, $row->id, $row->sortkey ); |
|
445 | } else { |
||
446 | $missedCount++; |
||
447 | 156 | $logToTable[$row->t] = "skip result for {$row->t} existing cache entry / query " . $query->getHash(); |
|
448 | } |
||
449 | } else { |
||
450 | $missedCount++; |
||
451 | $logToTable[$row->t] = "skip result for {$row->t} due to an internal `{$row->iw}` pointer / query " . $query->getHash(); |
||
452 | } |
||
453 | } |
||
454 | |||
455 | 161 | if ( $db->fetchObject( $res ) ) { |
|
456 | 5 | $count++; |
|
457 | } |
||
458 | |||
459 | 161 | if ( $logToTable !== array() ) { |
|
460 | wfDebugLog( 'smw', __METHOD__ . ' ' . implode( ',', $logToTable ) . "\n" ); |
||
461 | } |
||
462 | |||
463 | 161 | if ( $count > $query->getLimit() || ( $count + $missedCount ) > $query->getLimit() ) { |
|
464 | 5 | $hasFurtherResults = true; |
|
465 | }; |
||
466 | |||
467 | 161 | $db->freeResult( $res ); |
|
468 | 161 | $result = new QueryResult( $prs, $query, $qr, $this->store, $hasFurtherResults ); |
|
469 | |||
470 | 161 | return $result; |
|
471 | } |
||
472 | |||
473 | /** |
||
474 | * This function modifies the given query object at $qid to account for all ordering conditions |
||
475 | * in the Query $query. It is always required that $qid is the id of a query that joins with |
||
476 | * SMW IDs table so that the field alias.smw_title is $available for default sorting. |
||
477 | * |
||
478 | * @param integer $qid |
||
479 | */ |
||
480 | 163 | private function applyOrderConditions( $qid ) { |
|
481 | 163 | $qobj = $this->querySegmentList[$qid]; |
|
482 | |||
483 | 163 | $extraProperties = $this->collectedRequiredExtraPropertyDescriptions( $qobj ); |
|
484 | |||
485 | 163 | if ( count( $extraProperties ) > 0 ) { |
|
486 | 5 | $this->compileAccordingConditionsAndHackThemIntoQobj( $extraProperties, $qobj, $qid ); |
|
487 | } |
||
488 | 163 | } |
|
489 | |||
490 | 164 | private function applyExtraWhereCondition( $qid ) { |
|
491 | |||
492 | 164 | $db = $this->store->getConnection( 'mw.db.queryengine' ); |
|
493 | |||
494 | 164 | if ( !isset( $this->querySegmentList[$qid] ) ) { |
|
495 | 1 | return null; |
|
496 | } |
||
497 | |||
498 | 163 | $qobj = $this->querySegmentList[$qid]; |
|
499 | |||
500 | // Filter elements that should never appear in a result set |
||
501 | $extraWhereCondition = array( |
||
502 | 163 | 'del' => "$qobj->alias.smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWIW_OUTDATED ) . " AND $qobj->alias.smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWDELETEIW ), |
|
503 | 163 | 'redi' => "$qobj->alias.smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWREDIIW ) |
|
504 | ); |
||
505 | |||
506 | 163 | if ( strpos( $qobj->where, SMW_SQL3_SMWIW_OUTDATED ) === false ) { |
|
507 | 163 | $qobj->where .= $qobj->where === '' ? $extraWhereCondition['del'] : " AND " . $extraWhereCondition['del']; |
|
508 | } |
||
509 | |||
510 | 163 | if ( strpos( $qobj->where, SMW_SQL3_SMWREDIIW ) === false ) { |
|
511 | 163 | $qobj->where .= $qobj->where === '' ? $extraWhereCondition['redi'] : " AND " . $extraWhereCondition['redi']; |
|
512 | } |
||
513 | |||
514 | 163 | $this->querySegmentList[$qid] = $qobj; |
|
515 | 163 | } |
|
516 | |||
517 | 163 | private function collectedRequiredExtraPropertyDescriptions( $qobj ) { |
|
518 | 163 | $extraProperties = array(); |
|
519 | |||
520 | 163 | foreach ( $this->sortKeys as $propkey => $order ) { |
|
521 | |||
522 | 89 | if ( !is_string( $propkey ) ) { |
|
523 | throw new RuntimeException( "Expected a string value as sortkey" ); |
||
524 | } |
||
525 | |||
526 | 89 | if ( !array_key_exists( $propkey, $qobj->sortfields ) ) { // Find missing property to sort by. |
|
527 | 81 | if ( $propkey === '' ) { // Sort by first result column (page titles). |
|
528 | 77 | $qobj->sortfields[$propkey] = "$qobj->alias.smw_sortkey"; |
|
529 | 7 | } elseif ( $propkey === '#' ) { // Sort by first result column (page titles). |
|
530 | // PHP7 showed a rather erratic behaviour where in cases |
||
531 | // the sortkey contains the same string for comparison, the |
||
532 | // result returned from the DB was mixed in order therefore |
||
533 | // using # as indicator to search for additional fields if |
||
534 | // no specific property is given (see test cases in #1534) |
||
535 | 2 | $qobj->sortfields[$propkey] = "$qobj->alias.smw_sortkey,$qobj->alias.smw_title,$qobj->alias.smw_subobject"; |
|
536 | } else { // Try to extend query. |
||
537 | 5 | $sortprop = PropertyValue::makeUserProperty( $propkey ); |
|
538 | |||
539 | 5 | if ( $sortprop->isValid() ) { |
|
540 | 89 | $extraProperties[] = new SomeProperty( $sortprop->getDataItem(), new ThingDescription() ); |
|
0 ignored issues
–
show
$sortprop->getDataItem() of type object<SMWDataItem> is not a sub-type of object<SMW\DIProperty> . It seems like you assume a child class of the class SMWDataItem to be always present.
This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass. Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.
Loading history...
|
|||
541 | } |
||
542 | } |
||
543 | } |
||
544 | } |
||
545 | |||
546 | 163 | return $extraProperties; |
|
547 | } |
||
548 | |||
549 | 5 | private function compileAccordingConditionsAndHackThemIntoQobj( array $extraProperties, $qobj, $qid ) { |
|
550 | 5 | $this->querySegmentListBuilder->setSortKeys( $this->sortKeys ); |
|
551 | 5 | $this->querySegmentListBuilder->getQuerySegmentFrom( new Conjunction( $extraProperties ) ); |
|
552 | |||
553 | 5 | $newQuerySegmentId = $this->querySegmentListBuilder->getLastQuerySegmentId(); |
|
554 | 5 | $this->querySegmentList = $this->querySegmentListBuilder->getQuerySegmentList(); |
|
555 | 5 | $this->errors = $this->querySegmentListBuilder->getErrors(); |
|
556 | |||
557 | 5 | $newQuerySegment = $this->querySegmentList[$newQuerySegmentId]; // This is always an QuerySegment::Q_CONJUNCTION ... |
|
558 | |||
559 | 5 | foreach ( $newQuerySegment->components as $cid => $field ) { // ... so just re-wire its dependencies |
|
560 | 5 | $qobj->components[$cid] = $qobj->joinfield; |
|
561 | 5 | $qobj->sortfields = array_merge( $qobj->sortfields, $this->querySegmentList[$cid]->sortfields ); |
|
562 | } |
||
563 | |||
564 | 5 | $this->querySegmentList[$qid] = $qobj; |
|
565 | 5 | } |
|
566 | |||
567 | /** |
||
568 | * Get a SQL option array for the given query and preprocessed query object at given id. |
||
569 | * |
||
570 | * @param Query $query |
||
571 | * @param integer $rootId |
||
572 | * |
||
573 | * @return array |
||
574 | */ |
||
575 | 163 | private function getSQLOptions( Query $query, $rootId ) { |
|
576 | |||
577 | 163 | $result = array( 'LIMIT' => $query->getLimit() + 5, 'OFFSET' => $query->getOffset() ); |
|
578 | |||
579 | // Build ORDER BY options using discovered sorting fields. |
||
580 | 163 | if ( $this->engineOptions->get( 'smwgQSortingSupport' ) ) { |
|
581 | 162 | $qobj = $this->querySegmentList[$rootId]; |
|
582 | 162 | $type = $this->store->getConnection( 'mw.db.queryengine' )->getType(); |
|
583 | |||
584 | 162 | foreach ( $this->sortKeys as $propkey => $order ) { |
|
585 | |||
586 | 89 | if ( !is_string( $propkey ) ) { |
|
587 | throw new RuntimeException( "Expected a string value as sortkey" ); |
||
588 | } |
||
589 | |||
590 | // #835 |
||
591 | // SELECT DISTINCT and ORDER BY RANDOM causes an issue for postgres |
||
592 | // Disable RANDOM support for postgres |
||
593 | 89 | if ( $type === 'postgres' ) { |
|
594 | $this->engineOptions->set( 'smwgQRandSortingSupport', false ); |
||
595 | } |
||
596 | |||
597 | 89 | if ( ( $order != 'RANDOM' ) && array_key_exists( $propkey, $qobj->sortfields ) ) { // Field was successfully added. |
|
598 | 88 | $result['ORDER BY'] = ( array_key_exists( 'ORDER BY', $result ) ? $result['ORDER BY'] . ', ' : '' ) . $qobj->sortfields[$propkey] . " $order "; |
|
599 | 1 | } elseif ( ( $order == 'RANDOM' ) && $this->engineOptions->get( 'smwgQRandSortingSupport' ) ) { |
|
600 | 89 | $result['ORDER BY'] = ( array_key_exists( 'ORDER BY', $result ) ? $result['ORDER BY'] . ', ' : '' ) . ' RAND() '; |
|
601 | } |
||
602 | } |
||
603 | } |
||
604 | 163 | return $result; |
|
605 | } |
||
606 | |||
607 | } |
||
608 |
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.