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
|
|||
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 />  <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 |
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: