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 |
|
$this->querymode = $queryMode; |
132
|
108 |
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @since 2.3 |
136
|
|
|
* |
137
|
|
|
* @param DIWikiPage|null $contextPage |
138
|
|
|
*/ |
139
|
151 |
|
public function setContextPage( DIWikiPage $contextPage = null ) { |
140
|
151 |
|
$this->contextPage = $contextPage; |
141
|
151 |
|
} |
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* @since 2.3 |
145
|
|
|
* |
146
|
|
|
* @return DIWikiPage|null |
147
|
|
|
*/ |
148
|
145 |
|
public function getContextPage() { |
149
|
145 |
|
return $this->contextPage; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* @since 2.4 |
154
|
|
|
* |
155
|
|
|
* @param string |
156
|
|
|
*/ |
157
|
108 |
|
public function setQuerySource( $querySource ) { |
158
|
108 |
|
$this->querySource = $querySource; |
159
|
108 |
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @since 2.4 |
163
|
|
|
* |
164
|
|
|
* @return string |
165
|
|
|
*/ |
166
|
93 |
|
public function getQuerySource() { |
167
|
93 |
|
return $this->querySource; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
/** |
171
|
|
|
* Sets the mainlabel. |
172
|
|
|
* |
173
|
|
|
* @since 1.6. |
174
|
|
|
* |
175
|
|
|
* @param string $mainlabel |
176
|
|
|
*/ |
177
|
108 |
|
public function setMainLabel( $mainlabel ) { |
178
|
108 |
|
$this->m_mainlabel = $mainlabel; |
179
|
108 |
|
} |
180
|
|
|
|
181
|
|
|
/** |
182
|
|
|
* Gets the mainlabel. |
183
|
|
|
* |
184
|
|
|
* @since 1.6. |
185
|
|
|
* |
186
|
|
|
* @return string |
187
|
|
|
*/ |
188
|
13 |
|
public function getMainLabel() { |
189
|
13 |
|
return $this->m_mainlabel; |
190
|
|
|
} |
191
|
|
|
|
192
|
3 |
|
public function setDescription( SMWDescription $description ) { |
193
|
3 |
|
$this->description = $description; |
194
|
3 |
|
$this->queryString = false; |
195
|
|
|
|
196
|
3 |
|
foreach ( $this->m_extraprintouts as $printout ) { |
197
|
|
|
$this->description->addPrintRequest( $printout ); |
198
|
|
|
} |
199
|
3 |
|
$this->applyRestrictions(); |
200
|
3 |
|
} |
201
|
|
|
|
202
|
180 |
|
public function getDescription() { |
203
|
180 |
|
return $this->description; |
204
|
|
|
} |
205
|
|
|
|
206
|
154 |
|
public function setExtraPrintouts( $extraprintouts ) { |
207
|
154 |
|
$this->m_extraprintouts = $extraprintouts; |
208
|
|
|
|
209
|
154 |
|
if ( !is_null( $this->description ) ) { |
210
|
154 |
|
foreach ( $extraprintouts as $printout ) { |
211
|
125 |
|
$this->description->addPrintRequest( $printout ); |
212
|
|
|
} |
213
|
|
|
} |
214
|
154 |
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* @return PrintRequest[] |
218
|
|
|
*/ |
219
|
122 |
|
public function getExtraPrintouts() { |
220
|
122 |
|
return $this->m_extraprintouts; |
221
|
|
|
} |
222
|
|
|
|
223
|
170 |
|
public function getErrors() { |
224
|
170 |
|
return $this->errors; |
225
|
|
|
} |
226
|
|
|
|
227
|
186 |
|
public function addErrors( $errors ) { |
228
|
186 |
|
$this->errors = array_merge( $this->errors, $errors ); |
229
|
186 |
|
} |
230
|
|
|
|
231
|
108 |
|
public function setQueryString( $querystring ) { |
232
|
108 |
|
$this->queryString = $querystring; |
233
|
108 |
|
} |
234
|
|
|
|
235
|
|
|
/** |
236
|
|
|
* @since 2.5 |
237
|
|
|
* |
238
|
|
|
* @param string|integer $key |
239
|
|
|
* @param mixed $value |
240
|
|
|
*/ |
241
|
182 |
|
public function setOption( $key, $value ) { |
242
|
182 |
|
$this->options[$key] = $value; |
243
|
182 |
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @since 2.5 |
247
|
|
|
* |
248
|
|
|
* @param string|integer $key |
249
|
|
|
* |
250
|
|
|
* @return mixed |
251
|
|
|
*/ |
252
|
91 |
|
public function getOptionBy( $key ) { |
253
|
91 |
|
return isset( $this->options[$key] ) ? $this->options[$key] : false; |
254
|
|
|
} |
255
|
|
|
|
256
|
|
|
/** |
257
|
|
|
* @since 1.7 |
258
|
|
|
* |
259
|
|
|
* @param boolean $fresh |
260
|
|
|
* |
261
|
|
|
* @return string |
262
|
|
|
*/ |
263
|
122 |
|
public function getQueryString( $fresh = false ) { |
264
|
|
|
|
265
|
|
|
// Mostly relevant on requesting a further results link to |
266
|
|
|
// ensure that localized values are transformed into a canonical |
267
|
|
|
// representation |
268
|
122 |
|
if ( $fresh && $this->description !== null ) { |
269
|
13 |
|
return $this->description->getQueryString(); |
270
|
|
|
} |
271
|
|
|
|
272
|
121 |
|
if ( $this->queryString !== false ) { |
273
|
95 |
|
return $this->queryString; |
|
|
|
|
274
|
29 |
|
} elseif ( !is_null( $this->description ) ) { |
275
|
29 |
|
return $this->description->getQueryString(); |
276
|
|
|
} else { |
277
|
|
|
return ''; |
278
|
|
|
} |
279
|
|
|
} |
280
|
|
|
|
281
|
173 |
|
public function getOffset() { |
282
|
173 |
|
return $this->offset; |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
/** |
286
|
|
|
* Set an offset for the returned query results. No offset beyond the maximal query |
287
|
|
|
* limit will be set, and the current query limit might be reduced in order to ensure |
288
|
|
|
* that no results beyond the maximal limit are returned. |
289
|
|
|
* The function returns the chosen offset. |
290
|
|
|
* @todo The function should be extended to take into account whether or not we |
291
|
|
|
* are in inline mode (not critical, since offsets are usually not applicable inline). |
292
|
|
|
*/ |
293
|
152 |
|
public function setOffset( $offset ) { |
294
|
152 |
|
global $smwgQMaxLimit; |
|
|
|
|
295
|
152 |
|
$this->offset = min( $smwgQMaxLimit, $offset ); // select integer between 0 and maximal limit; |
296
|
152 |
|
$this->limit = min( $smwgQMaxLimit - $this->offset, $this->limit ); // note that limit might become 0 here |
297
|
152 |
|
return $this->offset; |
298
|
|
|
} |
299
|
|
|
|
300
|
179 |
|
public function getLimit() { |
301
|
179 |
|
return $this->limit; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Set a limit for number of query results. The set limit might be restricted by the |
306
|
|
|
* current offset so as to ensure that the number of the last considered result does not |
307
|
|
|
* exceed the maximum amount of supported results. |
308
|
|
|
* The function returns the chosen limit. |
309
|
|
|
* @note It makes sense to have limit==0, e.g. to only show a link to the search special |
310
|
|
|
*/ |
311
|
159 |
|
public function setLimit( $limit, $restrictinline = true ) { |
312
|
159 |
|
global $smwgQMaxLimit, $smwgQMaxInlineLimit; |
|
|
|
|
313
|
159 |
|
$maxlimit = ( $this->isInline && $restrictinline ) ? $smwgQMaxInlineLimit : $smwgQMaxLimit; |
314
|
159 |
|
$this->limit = min( $smwgQMaxLimit - $this->offset, $limit, $maxlimit ); |
315
|
159 |
|
return $this->limit; |
316
|
|
|
} |
317
|
|
|
|
318
|
|
|
/** |
319
|
|
|
* @note Sets an unbound limit that is independent from GLOBAL settings |
320
|
|
|
* |
321
|
|
|
* @since 2.0 |
322
|
|
|
* |
323
|
|
|
* @param integer $limit |
324
|
|
|
* |
325
|
|
|
* @return Query |
326
|
|
|
*/ |
327
|
4 |
|
public function setUnboundLimit( $limit ) { |
328
|
4 |
|
$this->limit = (int)$limit; |
329
|
4 |
|
return $this; |
330
|
|
|
} |
331
|
|
|
|
332
|
|
|
/** |
333
|
|
|
* @note format: "Property key" => "ASC" / "DESC" (note: order of entries also matters) |
334
|
|
|
* |
335
|
|
|
* @since 2.2 |
336
|
|
|
* |
337
|
|
|
* @param array $sortKeys |
338
|
|
|
*/ |
339
|
151 |
|
public function setSortKeys( array $sortKeys ) { |
340
|
151 |
|
$this->sortkeys = $sortKeys; |
341
|
151 |
|
} |
342
|
|
|
|
343
|
|
|
/** |
344
|
|
|
* @since 2.2 |
345
|
|
|
* |
346
|
|
|
* @return array |
347
|
|
|
*/ |
348
|
|
|
public function getSortKeys() { |
349
|
|
|
return $this->sortkeys; |
350
|
|
|
} |
351
|
|
|
|
352
|
|
|
/** |
353
|
|
|
* Apply structural restrictions to the current description. |
354
|
|
|
*/ |
355
|
192 |
|
public function applyRestrictions() { |
356
|
192 |
|
global $smwgQMaxSize, $smwgQMaxDepth, $smwgQConceptMaxSize, $smwgQConceptMaxDepth; |
|
|
|
|
357
|
|
|
|
358
|
192 |
|
if ( !is_null( $this->description ) ) { |
359
|
192 |
|
if ( $this->isUsedInConcept ) { |
360
|
6 |
|
$maxsize = $smwgQConceptMaxSize; |
361
|
6 |
|
$maxdepth = $smwgQConceptMaxDepth; |
362
|
|
|
} else { |
363
|
188 |
|
$maxsize = $smwgQMaxSize; |
364
|
188 |
|
$maxdepth = $smwgQMaxDepth; |
365
|
|
|
} |
366
|
|
|
|
367
|
192 |
|
$log = array(); |
368
|
192 |
|
$this->description = $this->description->prune( $maxsize, $maxdepth, $log ); |
369
|
|
|
|
370
|
192 |
|
if ( count( $log ) > 0 ) { |
371
|
|
|
$this->errors[] = Message::encode( array( |
372
|
|
|
'smw_querytoolarge', |
373
|
|
|
str_replace( '[', '[', implode( ', ', $log ) ), |
374
|
|
|
count( $log ) |
375
|
|
|
) ); |
376
|
|
|
} |
377
|
|
|
} |
378
|
192 |
|
} |
379
|
|
|
|
380
|
|
|
/** |
381
|
|
|
* Returns serialized query details |
382
|
|
|
* |
383
|
|
|
* The output is following the askargs api module convention |
384
|
|
|
* |
385
|
|
|
* conditions The query conditions (requirements for a subject to be included) |
386
|
|
|
* printouts The query printouts (which properties to show per subject) |
387
|
|
|
* parameters The query parameters (non-condition and non-printout arguments) |
388
|
|
|
* |
389
|
|
|
* @since 1.9 |
390
|
|
|
* |
391
|
|
|
* @return array |
392
|
|
|
*/ |
393
|
121 |
|
public function toArray() { |
394
|
121 |
|
$serialized = array(); |
395
|
|
|
|
396
|
121 |
|
$serialized['conditions'] = $this->getQueryString(); |
397
|
|
|
|
398
|
|
|
// This can be extended but for the current use cases that is |
399
|
|
|
// sufficient since most printer related parameters have to be sourced |
400
|
|
|
// in the result printer class |
401
|
121 |
|
$serialized['parameters'] = array( |
402
|
121 |
|
'limit' => $this->limit, |
403
|
121 |
|
'offset' => $this->offset, |
404
|
121 |
|
'sortkeys' => $this->sortkeys, |
405
|
121 |
|
'mainlabel' => $this->m_mainlabel, |
406
|
121 |
|
'querymode' => $this->querymode |
407
|
|
|
); |
408
|
|
|
|
409
|
|
|
// @2.4 Keep the queryID stable with previous versions unless |
410
|
|
|
// a query source is selected. The "same" query executed on different |
411
|
|
|
// remote systems requires a different queryID |
412
|
121 |
|
if ( $this->querySource !== null && $this->querySource !== '' ) { |
413
|
|
|
$serialized['parameters']['source'] = $this->querySource; |
414
|
|
|
} |
415
|
|
|
|
416
|
121 |
|
foreach ( $this->getExtraPrintouts() as $printout ) { |
417
|
112 |
|
$serialization = $printout->getSerialisation(); |
418
|
112 |
|
if ( $serialization !== '?#' ) { |
419
|
112 |
|
$serialized['printouts'][] = $serialization; |
420
|
|
|
} |
421
|
|
|
} |
422
|
|
|
|
423
|
121 |
|
return $serialized; |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
/** |
427
|
|
|
* @note Before 2.5, toArray was used to generate the content, as of 2.5 |
428
|
|
|
* only parameters that influence the result of an query is included. |
429
|
|
|
* |
430
|
|
|
* @since 2.1 |
431
|
|
|
* |
432
|
|
|
* @return string |
433
|
|
|
*/ |
434
|
124 |
|
public function getHash() { |
|
|
|
|
435
|
|
|
|
436
|
|
|
// For an optimal (less fragmentation) use of the cache, only use |
437
|
|
|
// elements that directly influence the result list |
438
|
124 |
|
$expectFingerprint = ($GLOBALS['smwgQueryResultCacheType'] !== false && $GLOBALS['smwgQueryResultCacheType'] !== CACHE_NONE ) || $GLOBALS['smwgQFilterDuplicates'] !== false; |
439
|
|
|
|
440
|
124 |
|
if ( $this->description !== null && $expectFingerprint ) { |
441
|
4 |
|
return $this->createFromFingerprint( $this->description->getFingerprint() ); |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
// FIXME 3.0 Leave the hash unchanged to avoid unnecessary BC issues in |
445
|
|
|
// case the cache is not used. |
446
|
120 |
|
return HashBuilder::createFromArray( $this->toArray() ); |
447
|
|
|
} |
448
|
|
|
|
449
|
|
|
/** |
450
|
|
|
* @since 2.5 |
451
|
|
|
* |
452
|
|
|
* @return string |
453
|
|
|
*/ |
454
|
|
|
public function getAsString() { |
455
|
|
|
return QueryStringifier::get( $this ); |
456
|
|
|
} |
457
|
|
|
|
458
|
|
|
/** |
459
|
|
|
* @since 2.3 |
460
|
|
|
* |
461
|
|
|
* @return string |
462
|
|
|
*/ |
463
|
123 |
|
public function getQueryId() { |
464
|
123 |
|
return self::ID_PREFIX . $this->getHash(); |
465
|
|
|
} |
466
|
|
|
|
467
|
4 |
|
private function createFromFingerprint( $fingerprint ) { |
468
|
|
|
|
469
|
4 |
|
$serialized = array(); |
470
|
|
|
|
471
|
|
|
// Don't use the QueryString, use the canonized fingerprint to ensure that |
472
|
|
|
// [[Foo::123]][[Bar::abc]] returns the same ID as [[Bar::abc]][[Foo::123]] |
|
|
|
|
473
|
|
|
// given that limit, offset, and sort/order are the same |
474
|
4 |
|
$serialized['fingerprint'] = $fingerprint; |
475
|
|
|
|
476
|
4 |
|
$serialized['parameters'] = array( |
477
|
4 |
|
'limit' => $this->limit, |
478
|
4 |
|
'offset' => $this->offset, |
479
|
4 |
|
'sortkeys' => $this->sortkeys, |
480
|
4 |
|
'querymode' => $this->querymode // COUNT, DEBUG ... |
481
|
|
|
); |
482
|
|
|
|
483
|
|
|
// Make to sure to distinguish queries and results from a foreign repository |
484
|
4 |
|
if ( $this->querySource !== null && $this->querySource !== '' ) { |
485
|
|
|
$serialized['parameters']['source'] = $this->querySource; |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
// Printouts are avoided as part of the hash as they not influence the |
489
|
|
|
// result match process and are only resolved after the query result has |
490
|
|
|
// been retrieved |
491
|
4 |
|
return HashBuilder::createFromArray( $serialized ); |
492
|
|
|
} |
493
|
|
|
|
494
|
|
|
} |
495
|
|
|
|
You can fix this by adding a namespace to your class:
When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.