Completed
Push — master ( 5f9e66...1f3475 )
by mw
123:57 queued 89:01
created

includes/query/SMW_Query.php (1 issue)

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
use SMW\DIWikiPage;
4
use SMW\HashBuilder;
5
use SMW\Query\PrintRequest;
6
use SMW\Query\Language\Description;
7
use SMW\Query\QueryContext;
8
use SMW\Query\QueryStringifier;
9
use SMW\Message;
10
11
/**
12
 * This file contains the class for representing queries in SMW, each
13
 * consisting of a query description and possible query parameters.
14
 * @ingroup SMWQuery
15
 * @author Markus Krötzsch
16
 */
17
18
/**
19
 * This group contains all parts of SMW that relate to processing semantic queries.
20
 * SMW components that relate to plain storage access (for querying or otherwise)
21
 * have their own group.
22
 * @defgroup SMWQuery SMWQuery
23
 * @ingroup SMW
24
 */
25
26
/**
27
 * Representation of queries in SMW, each consisting of a query
28
 * description and various parameters. Some settings might also lead to
29
 * changes in the query description.
30
 *
31
 * Most additional query parameters (limit, sort, ascending, ...) are
32
 * interpreted as in SMWRequestOptions (though the latter contains some
33
 * additional settings).
34
 * @ingroup SMWQuery
35
 */
36
class SMWQuery implements QueryContext {
37
38
	const ID_PREFIX = '_QUERY';
39
40
	/**
41
	 * The time the QueryEngine required to answer a query condition
42
	 */
43
	const PROC_QUERY_TIME = 'proc.query.time';
44
45
	/**
46
	 * The time a ResultPrinter required to build the final result including all
47
	 * PrintRequests
48
	 */
49
	const PROC_PRINT_TIME = 'proc.print.time';
50
51
	/**
52
	 * The processing context in which the query is being executed
53
	 */
54
	const PROC_CONTEXT = 'proc.context';
55
56
	/**
57
	 * Suppress a possible cache request
58
	 */
59
	const NO_CACHE = 'no.cache';
60
61
	public $sort = false;
62
	public $sortkeys = array(); // format: "Property key" => "ASC" / "DESC" (note: order of entries also matters)
63
	public $querymode = self::MODE_INSTANCES;
64
65
	private $limit;
66
	private $offset = 0;
67
	private $description;
68
	private $errors = array(); // keep any errors that occurred so far
69
	private $queryString = false; // string (inline query) version (if fixed and known)
70
	private $isInline; // query used inline? (required for finding right default parameters)
71
	private $isUsedInConcept; // query used in concept? (required for finding right default parameters)
72
73
	/**
74
	 * @var PrintRequest[]
75
	 */
76
	private $m_extraprintouts = array(); // SMWPrintoutRequest objects supplied outside querystring
77
	private $m_mainlabel = ''; // Since 1.6
78
79
	/**
80
	 * @var DIWikiPage|null
81
	 */
82
	private $contextPage;
83
84
	/**
85
	 * Describes a non-local (remote) query source
86
	 *
87
	 * @var string|null
88
	 */
89
	private $querySource = null;
90
91
	/**
92
	 * @var array
93
	 */
94
	private $options = array();
95
96
	/**
97
	 * @since 1.6
98
	 *
99
	 * @param Description $description
100
	 * @param integer|boolean $context
101
	 */
102 192
	public function __construct( Description $description = null, $context = false ) {
103 192
		$inline = false;
104 192
		$concept = false;
105
106
		// stating whether this query runs in an inline context; used to
107
		// determine proper default parameters (e.g. the default limit)
108 192
		if ( $context === self::INLINE_QUERY ) {
109 104
			$inline = true;
110
		}
111
112
		// stating whether this query belongs to a concept; used to determine
113
		// proper default parameters (concepts usually have less restrictions)
114 192
		if ( $context === self::CONCEPT_DESC ) {
115 6
			$concept = true;
116
		}
117
118 192
		$this->limit = $inline ? $GLOBALS['smwgQMaxInlineLimit'] : $GLOBALS['smwgQMaxLimit'];
119 192
		$this->isInline = $inline;
120 192
		$this->isUsedInConcept = $concept;
121 192
		$this->description = $description;
122 192
		$this->applyRestrictions();
123 192
	}
124
125
	/**
126
	 * @since 2.5
127
	 *
128
	 * @param integer
129
	 */
130 108
	public function setQueryMode( $queryMode ) {
131 108
		// FIXME 3.0; $this->querymode is a public property
132 108
		// delcare it private and rename it to $this->queryMode
133
		$this->querymode = $queryMode;
134
	}
135
136
	/**
137
	 * @since 2.5
138
	 *
139 151
	 * @param integer
140 151
	 */
141 151
	public function getQueryMode() {
142
		return $this->querymode;
143
	}
144
145
	/**
146
	 * @since 2.3
147
	 *
148 145
	 * @param DIWikiPage|null $contextPage
149 145
	 */
150
	public function setContextPage( DIWikiPage $contextPage = null ) {
151
		$this->contextPage = $contextPage;
152
	}
153
154
	/**
155
	 * @since 2.3
156
	 *
157 108
	 * @return DIWikiPage|null
158 108
	 */
159 108
	public function getContextPage() {
160
		return $this->contextPage;
161
	}
162
163
	/**
164
	 * @since 2.4
165
	 *
166 93
	 * @param string
167 93
	 */
168
	public function setQuerySource( $querySource ) {
169
		$this->querySource = $querySource;
170
	}
171
172
	/**
173
	 * @since 2.4
174
	 *
175
	 * @return string
176
	 */
177 108
	public function getQuerySource() {
178 108
		return $this->querySource;
179 108
	}
180
181
	/**
182
	 * Sets the mainlabel.
183
	 *
184
	 * @since 1.6.
185
	 *
186
	 * @param string $mainlabel
187
	 */
188 13
	public function setMainLabel( $mainlabel ) {
189 13
		$this->m_mainlabel = $mainlabel;
190
	}
191
192 3
	/**
193 3
	 * Gets the mainlabel.
194 3
	 *
195
	 * @since 1.6.
196 3
	 *
197
	 * @return string
198
	 */
199 3
	public function getMainLabel() {
200 3
		return $this->m_mainlabel;
201
	}
202 180
203 180
	public function setDescription( SMWDescription $description ) {
204
		$this->description = $description;
205
		$this->queryString = false;
206 154
207 154
		foreach ( $this->m_extraprintouts as $printout ) {
208
			$this->description->addPrintRequest( $printout );
209 154
		}
210 154
		$this->applyRestrictions();
211 125
	}
212
213
	public function getDescription() {
214 154
		return $this->description;
215
	}
216
217
	public function setExtraPrintouts( $extraprintouts ) {
218
		$this->m_extraprintouts = $extraprintouts;
219 122
220 122
		if ( !is_null( $this->description ) ) {
221
			foreach ( $extraprintouts as $printout ) {
222
				$this->description->addPrintRequest( $printout );
223 170
			}
224 170
		}
225
	}
226
227 186
	/**
228 186
	 * @return PrintRequest[]
229 186
	 */
230
	public function getExtraPrintouts() {
231 108
		return $this->m_extraprintouts;
232 108
	}
233 108
234
	public function getErrors() {
235
		return $this->errors;
236
	}
237
238
	public function addErrors( $errors ) {
239
		$this->errors = array_merge( $this->errors, $errors );
240
	}
241 182
242 182
	public function setQueryString( $querystring ) {
243 182
		$this->queryString = $querystring;
244
	}
245
246
	/**
247
	 * @since 2.5
248
	 *
249
	 * @param string|integer $key
250
	 * @param mixed $value
251
	 */
252 91
	public function setOption( $key, $value ) {
253 91
		$this->options[$key] = $value;
254
	}
255
256
	/**
257
	 * @since 2.5
258
	 *
259
	 * @param string|integer $key
260
	 *
261
	 * @return mixed
262
	 */
263 122
	public function getOption( $key ) {
264
		return isset( $this->options[$key] ) ? $this->options[$key] : false;
265
	}
266
267
	/**
268 122
	 * @since 1.7
269 13
	 *
270
	 * @param  boolean $fresh
271
	 *
272 121
	 * @return string
273 95
	 */
274 29
	public function getQueryString( $fresh = false ) {
275 29
276
		// Mostly relevant on requesting a further results link to
277
		// ensure that localized values are transformed into a canonical
278
		// representation
279
		if ( $fresh && $this->description !== null ) {
280
			return $this->description->getQueryString();
281 173
		}
282 173
283
		if ( $this->queryString !== false ) {
284
			return $this->queryString;
285
		} elseif ( !is_null( $this->description ) ) {
286
			return $this->description->getQueryString();
287
		} else {
288
			return '';
289
		}
290
	}
291
292
	public function getOffset() {
293 152
		return $this->offset;
294 152
	}
295 152
296 152
	/**
297 152
	 * Set an offset for the returned query results. No offset beyond the maximal query
298
	 * limit will be set, and the current query limit might be reduced in order to ensure
299
	 * that no results beyond the maximal limit are returned.
300 179
	 * The function returns the chosen offset.
301 179
	 * @todo The function should be extended to take into account whether or not we
302
	 * are in inline mode (not critical, since offsets are usually not applicable inline).
303
	 */
304
	public function setOffset( $offset ) {
305
		global $smwgQMaxLimit;
306
		$this->offset = min( $smwgQMaxLimit, $offset ); // select integer between 0 and maximal limit;
307
		$this->limit = min( $smwgQMaxLimit - $this->offset, $this->limit ); // note that limit might become 0 here
308
		return $this->offset;
309
	}
310
311 159
	public function getLimit() {
312 159
		return $this->limit;
313 159
	}
314 159
315 159
	/**
316
	 * Set a limit for number of query results. The set limit might be restricted by the
317
	 * current offset so as to ensure that the number of the last considered result does not
318
	 * exceed the maximum amount of supported results.
319
	 * The function returns the chosen limit.
320
	 * @note It makes sense to have limit==0, e.g. to only show a link to the search special
321
	 */
322
	public function setLimit( $limit, $restrictinline = true ) {
323
		global $smwgQMaxLimit, $smwgQMaxInlineLimit;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
324
		$maxlimit = ( $this->isInline && $restrictinline ) ? $smwgQMaxInlineLimit : $smwgQMaxLimit;
325
		$this->limit = min( $smwgQMaxLimit - $this->offset, $limit, $maxlimit );
326
		return $this->limit;
327 4
	}
328 4
329 4
	/**
330
	 * @note Sets an unbound limit that is independent from GLOBAL settings
331
	 *
332
	 * @since 2.0
333
	 *
334
	 * @param integer $limit
335
	 *
336
	 * @return Query
337
	 */
338
	public function setUnboundLimit( $limit ) {
339 151
		$this->limit = (int)$limit;
340 151
		return $this;
341 151
	}
342
343
	/**
344
	 * @note format: "Property key" => "ASC" / "DESC" (note: order of entries also matters)
345
	 *
346
	 * @since 2.2
347
	 *
348
	 * @param array $sortKeys
349
	 */
350
	public function setSortKeys( array $sortKeys ) {
351
		$this->sortkeys = $sortKeys;
352
	}
353
354
	/**
355 192
	 * @since 2.2
356 192
	 *
357
	 * @return array
358 192
	 */
359 192
	public function getSortKeys() {
360 6
		return $this->sortkeys;
361 6
	}
362
363 188
	/**
364 188
	 * Apply structural restrictions to the current description.
365
	 */
366
	public function applyRestrictions() {
367 192
		global $smwgQMaxSize, $smwgQMaxDepth, $smwgQConceptMaxSize, $smwgQConceptMaxDepth;
368 192
369
		if ( !is_null( $this->description ) ) {
370 192
			if ( $this->isUsedInConcept ) {
371
				$maxsize = $smwgQConceptMaxSize;
372
				$maxdepth = $smwgQConceptMaxDepth;
373
			} else {
374
				$maxsize = $smwgQMaxSize;
375
				$maxdepth = $smwgQMaxDepth;
376
			}
377
378 192
			$log = array();
379
			$this->description = $this->description->prune( $maxsize, $maxdepth, $log );
380
381
			if ( count( $log ) > 0 ) {
382
				$this->errors[] = Message::encode( array(
383
					'smw_querytoolarge',
384
					str_replace( '[', '&#91;', implode( ', ', $log ) ),
385
					count( $log )
386
				) );
387
			}
388
		}
389
	}
390
391
	/**
392
	 * Returns serialized query details
393 121
	 *
394 121
	 * The output is following the askargs api module convention
395
	 *
396 121
	 * conditions The query conditions (requirements for a subject to be included)
397
	 * printouts The query printouts (which properties to show per subject)
398
	 * parameters The query parameters (non-condition and non-printout arguments)
399
	 *
400
	 * @since 1.9
401 121
	 *
402 121
	 * @return array
403 121
	 */
404 121
	public function toArray() {
405 121
		$serialized = array();
406 121
407
		$serialized['conditions'] = $this->getQueryString();
408
409
		// This can be extended but for the current use cases that is
410
		// sufficient since most printer related parameters have to be sourced
411
		// in the result printer class
412 121
		$serialized['parameters'] = array(
413
				'limit'     => $this->limit,
414
				'offset'    => $this->offset,
415
				'sortkeys'  => $this->sortkeys,
416 121
				'mainlabel' => $this->m_mainlabel,
417 112
				'querymode' => $this->querymode
418 112
		);
419 112
420
		// @2.4 Keep the queryID stable with previous versions unless
421
		// a query source is selected. The "same" query executed on different
422
		// remote systems requires a different queryID
423 121
		if ( $this->querySource !== null && $this->querySource !== '' ) {
424
			$serialized['parameters']['source'] = $this->querySource;
425
		}
426
427
		foreach ( $this->getExtraPrintouts() as $printout ) {
428
			$serialization = $printout->getSerialisation();
429
			if ( $serialization !== '?#' ) {
430
				$serialized['printouts'][] = $serialization;
431
			}
432
		}
433
434 124
		return $serialized;
435
	}
436
437
	/**
438 124
	 * @note Before 2.5, toArray was used to generate the content, as of 2.5
439
	 * only parameters that influence the result of an query is included.
440 124
	 *
441 4
	 * @since 2.1
442
	 *
443
	 * @return string
444
	 */
445
	public function getHash() {
446 120
447
		// For an optimal (less fragmentation) use of the cache, only use
448
		// elements that directly influence the result list
449
		$expectFingerprint = ($GLOBALS['smwgQueryResultCacheType'] !== false && $GLOBALS['smwgQueryResultCacheType'] !== CACHE_NONE ) || $GLOBALS['smwgQFilterDuplicates'] !== false;
450
451
		if ( $this->description !== null && $expectFingerprint ) {
452
			return $this->createFromFingerprint( $this->description->getFingerprint() );
453
		}
454
455
		// FIXME 3.0 Leave the hash unchanged to avoid unnecessary BC issues in
456
		// case the cache is not used.
457
		return HashBuilder::createFromArray( $this->toArray() );
458
	}
459
460
	/**
461
	 * @since 2.5
462
	 *
463 123
	 * @return string
464 123
	 */
465
	public function getAsString() {
466
		return QueryStringifier::get( $this );
467 4
	}
468
469 4
	/**
470
	 * @since 2.3
471
	 *
472
	 * @return string
473
	 */
474 4
	public function getQueryId() {
475
		return self::ID_PREFIX . $this->getHash();
476 4
	}
477 4
478 4
	private function createFromFingerprint( $fingerprint ) {
479 4
480 4
		$serialized = array();
481
482
		// Don't use the QueryString, use the canonized fingerprint to ensure that
483
		// [[Foo::123]][[Bar::abc]] returns the same ID as [[Bar::abc]][[Foo::123]]
484 4
		// given that limit, offset, and sort/order are the same
485
		$serialized['fingerprint'] = $fingerprint;
486
487
		$serialized['parameters'] = array(
488
			'limit'     => $this->limit,
489
			'offset'    => $this->offset,
490
			'sortkeys'  => $this->sortkeys,
491 4
			'querymode' => $this->querymode // COUNT, DEBUG ...
492
		);
493
494
		// Make to sure to distinguish queries and results from a foreign repository
495
		if ( $this->querySource !== null && $this->querySource !== '' ) {
496
			$serialized['parameters']['source'] = $this->querySource;
497
		}
498
499
		// Printouts are avoided as part of the hash as they not influence the
500
		// result match process and are only resolved after the query result has
501
		// been retrieved
502
		return HashBuilder::createFromArray( $serialized );
503
	}
504
505
}
506