1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace SMW\SPARQLStore\QueryEngine; |
4
|
|
|
|
5
|
|
|
use RuntimeException; |
6
|
|
|
use SMW\CircularReferenceGuard; |
7
|
|
|
use SMW\DataTypeRegistry; |
8
|
|
|
use SMW\DIProperty; |
9
|
|
|
use SMW\DIWikiPage; |
10
|
|
|
use SMW\PropertyHierarchyLookup; |
11
|
|
|
use SMW\Query\Language\Description; |
12
|
|
|
use SMW\Query\Language\SomeProperty; |
13
|
|
|
use SMW\Query\Language\ThingDescription; |
14
|
|
|
use SMW\SPARQLStore\HierarchyFinder; |
15
|
|
|
use SMW\SPARQLStore\QueryEngine\Condition\Condition; |
16
|
|
|
use SMW\SPARQLStore\QueryEngine\Condition\SingletonCondition; |
17
|
|
|
use SMW\SPARQLStore\QueryEngine\Condition\TrueCondition; |
18
|
|
|
use SMWDataItem as DataItem; |
19
|
|
|
use SMWExpElement as ExpElement; |
20
|
|
|
use SMWExpNsResource as ExpNsResource; |
21
|
|
|
use SMWExporter as Exporter; |
22
|
|
|
use SMWTurtleSerializer as TurtleSerializer; |
23
|
|
|
use SMW\Query\DescriptionFactory; |
24
|
|
|
use SMW\DataValues\PropertyChainValue; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Build an internal representation for a SPARQL condition from individual query |
28
|
|
|
* descriptions |
29
|
|
|
* |
30
|
|
|
* @license GNU GPL v2+ |
31
|
|
|
* @since 2.0 |
32
|
|
|
* |
33
|
|
|
* @author Markus Krötzsch |
34
|
|
|
* @author mwjames |
35
|
|
|
*/ |
36
|
|
|
class CompoundConditionBuilder { |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* @var EngineOptions |
40
|
|
|
*/ |
41
|
|
|
private $engineOptions; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var DispatchingDescriptionInterpreter |
45
|
|
|
*/ |
46
|
|
|
private $dispatchingDescriptionInterpreter; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* @var CircularReferenceGuard |
50
|
|
|
*/ |
51
|
|
|
private $circularReferenceGuard; |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* @var PropertyHierarchyLookup |
55
|
|
|
*/ |
56
|
|
|
private $propertyHierarchyLookup; |
57
|
|
|
|
58
|
|
|
/** |
59
|
|
|
* @var DescriptionFactory |
60
|
|
|
*/ |
61
|
|
|
private $descriptionFactory; |
62
|
|
|
|
63
|
|
|
/** |
64
|
|
|
* @var array |
65
|
|
|
*/ |
66
|
|
|
private $errors = array(); |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* Counter used to generate globally fresh variables. |
70
|
|
|
* @var integer |
71
|
|
|
*/ |
72
|
|
|
private $variableCounter = 0; |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* sortKeys that are being used while building the query conditions |
76
|
|
|
* @var array |
77
|
|
|
*/ |
78
|
|
|
private $sortKeys = array(); |
79
|
|
|
|
80
|
|
|
/** |
81
|
|
|
* The name of the SPARQL variable that represents the query result |
82
|
|
|
* @var string |
83
|
|
|
*/ |
84
|
|
|
private $resultVariable = 'result'; |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* @var string |
88
|
|
|
*/ |
89
|
|
|
private $joinVariable; |
90
|
|
|
|
91
|
|
|
/** |
92
|
|
|
* @var DIProperty|null |
93
|
|
|
*/ |
94
|
|
|
private $orderByProperty; |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @var array |
98
|
|
|
*/ |
99
|
|
|
private $redirectByVariableReplacementMap = array(); |
100
|
33 |
|
|
101
|
33 |
|
/** |
102
|
33 |
|
* @since 2.2 |
103
|
|
|
* |
104
|
33 |
|
* @param DescriptionInterpreterFactory $descriptionInterpreterFactory |
105
|
28 |
|
* @param EngineOptions|null $engineOptions |
106
|
|
|
*/ |
107
|
33 |
|
public function __construct( DescriptionInterpreterFactory $descriptionInterpreterFactory, EngineOptions $engineOptions = null ) { |
108
|
|
|
$this->dispatchingDescriptionInterpreter = $descriptionInterpreterFactory->newDispatchingDescriptionInterpreter( $this ); |
|
|
|
|
109
|
|
|
$this->engineOptions = $engineOptions; |
110
|
|
|
|
111
|
|
|
if ( $this->engineOptions === null ) { |
112
|
|
|
$this->engineOptions = new EngineOptions(); |
113
|
|
|
} |
114
|
5 |
|
|
115
|
5 |
|
$this->descriptionFactory = new DescriptionFactory(); |
116
|
5 |
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* @since 2.0 |
120
|
|
|
* |
121
|
|
|
* @param string $resultVariable |
122
|
|
|
*/ |
123
|
|
|
public function setResultVariable( $resultVariable ) { |
124
|
24 |
|
$this->resultVariable = $resultVariable; |
125
|
24 |
|
return $this; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
/** |
129
|
|
|
* Get a fresh unused variable name for building SPARQL conditions. |
130
|
|
|
* |
131
|
|
|
* @return string |
132
|
|
|
*/ |
133
|
5 |
|
public function getNextVariable( $prefix = 'v' ) { |
134
|
5 |
|
return $prefix . ( ++$this->variableCounter ); |
135
|
5 |
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @since 2.0 |
139
|
|
|
* |
140
|
|
|
* @param array $sortKeys |
141
|
|
|
*/ |
142
|
|
|
public function setSortKeys( $sortKeys ) { |
143
|
19 |
|
$this->sortKeys = $sortKeys; |
144
|
19 |
|
return $this; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* @since 2.1 |
149
|
|
|
* |
150
|
|
|
* @return array |
151
|
|
|
*/ |
152
|
1 |
|
public function getSortKeys() { |
153
|
1 |
|
return $this->sortKeys; |
154
|
|
|
} |
155
|
|
|
|
156
|
|
|
/** |
157
|
|
|
* @since 2.2 |
158
|
|
|
* |
159
|
|
|
* @return array |
160
|
|
|
*/ |
161
|
|
|
public function getErrors() { |
162
|
|
|
return $this->errors; |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* @since 2.2 |
167
|
|
|
* |
168
|
|
|
* @param string $error |
169
|
|
|
*/ |
170
|
5 |
|
public function addError( $error ) { |
171
|
5 |
|
$this->errors[] = $error; |
172
|
5 |
|
} |
173
|
|
|
|
174
|
|
|
/** |
175
|
|
|
* @since 2.2 |
176
|
|
|
* |
177
|
|
|
* @param CircularReferenceGuard $circularReferenceGuard |
178
|
|
|
*/ |
179
|
|
|
public function setCircularReferenceGuard( CircularReferenceGuard $circularReferenceGuard ) { |
180
|
|
|
$this->circularReferenceGuard = $circularReferenceGuard; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
/** |
184
|
|
|
* @since 2.2 |
185
|
|
|
* |
186
|
|
|
* @return CircularReferenceGuard |
187
|
|
|
*/ |
188
|
5 |
|
public function getCircularReferenceGuard() { |
189
|
5 |
|
return $this->circularReferenceGuard; |
190
|
5 |
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* @since 2.3 |
194
|
|
|
* |
195
|
|
|
* @param PropertyHierarchyLookup $propertyHierarchyLookup |
196
|
|
|
*/ |
197
|
18 |
|
public function setPropertyHierarchyLookup( PropertyHierarchyLookup $propertyHierarchyLookup ) { |
198
|
18 |
|
$this->propertyHierarchyLookup = $propertyHierarchyLookup; |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* @since 2.3 |
203
|
|
|
* |
204
|
|
|
* @return PropertyHierarchyLookup |
205
|
|
|
*/ |
206
|
|
|
public function getPropertyHierarchyLookup() { |
207
|
24 |
|
return $this->propertyHierarchyLookup; |
208
|
24 |
|
} |
209
|
24 |
|
|
210
|
|
|
/** |
211
|
|
|
* @since 2.2 |
212
|
|
|
* |
213
|
|
|
* @param string $joinVariable name of the variable that conditions |
214
|
|
|
* will refer to |
215
|
|
|
*/ |
216
|
24 |
|
public function setJoinVariable( $joinVariable ) { |
217
|
24 |
|
$this->joinVariable = $joinVariable; |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
/** |
221
|
|
|
* @since 2.2 |
222
|
|
|
* |
223
|
|
|
* @return string |
224
|
|
|
*/ |
225
|
|
|
public function getJoinVariable() { |
226
|
|
|
return $this->joinVariable; |
227
|
24 |
|
} |
228
|
24 |
|
|
229
|
24 |
|
/** |
230
|
|
|
* @since 2.2 |
231
|
|
|
* |
232
|
|
|
* @param DIProperty|null $orderByProperty if given then |
233
|
|
|
* this is the property the values of which this condition will refer |
234
|
|
|
* to, and the condition should also enable ordering by this value |
235
|
|
|
*/ |
236
|
24 |
|
public function setOrderByProperty( $orderByProperty ) { |
237
|
24 |
|
$this->orderByProperty = $orderByProperty; |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
/** |
241
|
|
|
* @since 2.2 |
242
|
|
|
* |
243
|
|
|
* @return DIProperty|null |
244
|
|
|
*/ |
245
|
|
|
public function getOrderByProperty() { |
246
|
|
|
return $this->orderByProperty; |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Get a Condition object for a Description. |
251
|
|
|
* |
252
|
|
|
* This conversion is implemented by a number of recursive functions, |
253
|
|
|
* and this is the main entry point for this recursion. In particular, |
254
|
|
|
* it resets global variables that are used for the construction. |
255
|
24 |
|
* |
256
|
24 |
|
* If property value variables should be recorded for ordering results |
257
|
|
|
* later on, the keys of the respective properties need to be given in |
258
|
24 |
|
* sortKeys earlier. |
259
|
24 |
|
* |
260
|
|
|
* @param Description $description |
261
|
24 |
|
* |
262
|
|
|
* @return Condition |
263
|
24 |
|
*/ |
264
|
|
|
public function getConditionFrom( Description $description ) { |
265
|
|
|
$this->variableCounter = 0; |
266
|
|
|
|
267
|
23 |
|
$this->setJoinVariable( $this->resultVariable ); |
268
|
|
|
$this->setOrderByProperty( null ); |
269
|
|
|
|
270
|
|
|
$condition = $this->mapDescriptionToCondition( $description ); |
271
|
23 |
|
|
272
|
|
|
$this->addMissingOrderByConditions( |
273
|
|
|
$condition |
274
|
|
|
); |
275
|
23 |
|
|
276
|
|
|
$this->addPropertyPathToMatchRedirectTargets( |
277
|
|
|
$condition |
278
|
|
|
); |
279
|
|
|
|
280
|
|
|
$this->addFilterToRemoveEntitiesThatContainRedirectPredicate( |
281
|
|
|
$condition |
282
|
|
|
); |
283
|
|
|
|
284
|
|
|
return $condition; |
285
|
24 |
|
} |
286
|
24 |
|
|
287
|
|
|
/** |
288
|
|
|
* Recursively create a Condition from a Description |
289
|
|
|
* |
290
|
|
|
* @param Description $description |
291
|
|
|
* |
292
|
|
|
* @return Condition |
293
|
|
|
*/ |
294
|
|
|
public function mapDescriptionToCondition( Description $description ) { |
295
|
|
|
return $this->dispatchingDescriptionInterpreter->interpretDescription( $description ); |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
/** |
299
|
23 |
|
* Build the condition (WHERE) string for a given Condition. |
300
|
|
|
* The function also expresses the single value of |
301
|
23 |
|
* SingletonCondition objects in the condition, which may |
302
|
|
|
* lead to additional namespaces for serializing its URI. |
303
|
23 |
|
* |
304
|
1 |
|
* @param Condition $condition |
305
|
1 |
|
* |
306
|
|
|
* @return string |
307
|
|
|
*/ |
308
|
23 |
|
public function convertConditionToString( Condition &$condition ) { |
309
|
23 |
|
|
310
|
|
|
$conditionAsString = $condition->getWeakConditionString(); |
311
|
23 |
|
|
312
|
|
|
if ( ( $conditionAsString === '' ) && !$condition->isSafe() ) { |
313
|
2 |
|
$swivtPageResource = Exporter::getInstance()->getSpecialNsResource( 'swivt', 'page' ); |
314
|
|
|
$conditionAsString = '?' . $this->resultVariable . ' ' . $swivtPageResource->getQName() . " ?url .\n"; |
315
|
2 |
|
} |
316
|
1 |
|
|
317
|
|
|
$conditionAsString .= $condition->getCondition(); |
318
|
1 |
|
$conditionAsString .= $condition->getCogentConditionString(); |
319
|
|
|
|
320
|
|
|
if ( $condition instanceof SingletonCondition ) { // prepare for ASK, maybe rather use BIND? |
321
|
2 |
|
|
322
|
|
|
$matchElement = $condition->matchElement; |
323
|
|
|
|
324
|
|
|
if ( $matchElement instanceof ExpElement ) { |
|
|
|
|
325
|
2 |
|
$matchElementName = TurtleSerializer::getTurtleNameForExpElement( $matchElement ); |
326
|
|
|
} else { |
327
|
|
|
$matchElementName = $matchElement; |
328
|
23 |
|
} |
329
|
|
|
|
330
|
|
|
if ( $matchElement instanceof ExpNsResource ) { |
|
|
|
|
331
|
|
|
$condition->namespaces[$matchElement->getNamespaceId()] = $matchElement->getNamespace(); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
|
$conditionAsString = str_replace( '?' . $this->resultVariable . ' ', "$matchElementName ", $conditionAsString ); |
335
|
|
|
} |
336
|
|
|
|
337
|
|
|
return $conditionAsString; |
338
|
|
|
} |
339
|
|
|
|
340
|
9 |
|
/** |
341
|
9 |
|
* Create an Condition from an empty (true) description. |
342
|
9 |
|
* May still require helper conditions for ordering. |
343
|
9 |
|
* |
344
|
|
|
* @param $joinVariable string name, see mapDescriptionToCondition() |
345
|
|
|
* @param $orderByProperty mixed DIProperty or null, see mapDescriptionToCondition() |
346
|
|
|
* |
347
|
|
|
* @return Condition |
348
|
|
|
*/ |
349
|
|
|
public function newTrueCondition( $joinVariable, $orderByProperty ) { |
350
|
|
|
$result = new TrueCondition(); |
351
|
|
|
$this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty ); |
352
|
|
|
return $result; |
353
|
23 |
|
} |
354
|
|
|
|
355
|
23 |
|
/** |
356
|
7 |
|
* @since 2.3 |
357
|
|
|
* |
358
|
|
|
* @param DataItem|null $dataItem |
359
|
|
|
* |
360
|
|
|
* @return string|null |
361
|
21 |
|
*/ |
362
|
19 |
|
public function tryToFindRedirectVariableForDataItem( DataItem $dataItem = null ) { |
363
|
|
|
|
364
|
|
|
if ( !$dataItem instanceof DIWikiPage || !$this->canUseQFeature( SMW_SPARQL_QF_REDI ) ) { |
365
|
2 |
|
return null; |
366
|
|
|
} |
367
|
|
|
|
368
|
2 |
|
// Maybe there is a better way to verify the "isRedirect" state other |
369
|
|
|
// than by using the Title object |
370
|
|
|
if ( $dataItem->getTitle() === null || !$dataItem->getTitle()->isRedirect() ) { |
371
|
|
|
return null; |
372
|
2 |
|
} |
373
|
|
|
|
374
|
|
|
$redirectExpElement = Exporter::getInstance()->getResourceElementForWikiPage( $dataItem ); |
|
|
|
|
375
|
2 |
|
|
376
|
|
|
// If the resource was matched to an imported vocab then no redirect is required |
377
|
2 |
|
if ( $redirectExpElement->isImported() ) { |
378
|
2 |
|
return null; |
379
|
|
|
} |
380
|
2 |
|
|
381
|
2 |
|
$valueName = TurtleSerializer::getTurtleNameForExpElement( $redirectExpElement ); |
|
|
|
|
382
|
2 |
|
|
383
|
|
|
// Add unknow redirect target/variable for value |
384
|
|
|
if ( !isset( $this->redirectByVariableReplacementMap[$valueName] ) ) { |
385
|
|
|
|
386
|
|
|
$namespaces[$redirectExpElement->getNamespaceId()] = $redirectExpElement->getNamespace(); |
|
|
|
|
387
|
|
|
$redirectByVariable = '?' . $this->getNextVariable( 'r' ); |
388
|
2 |
|
|
389
|
|
|
$this->redirectByVariableReplacementMap[$valueName] = array( |
390
|
2 |
|
$redirectByVariable, |
391
|
|
|
$namespaces |
392
|
|
|
); |
393
|
|
|
} |
394
|
|
|
|
395
|
|
|
// Reuse an existing variable for the value to allow to be used more than |
396
|
|
|
// once when referring to the same property/value redirect |
397
|
|
|
list( $redirectByVariable, $namespaces ) = $this->redirectByVariableReplacementMap[$valueName]; |
|
|
|
|
398
|
|
|
|
399
|
|
|
return $redirectByVariable; |
400
|
21 |
|
} |
401
|
|
|
|
402
|
21 |
|
/** |
403
|
|
|
* @since 2.3 |
404
|
|
|
* |
405
|
21 |
|
* @param integer $queryFeatureFlag |
406
|
18 |
|
* |
407
|
|
|
* @return boolean |
408
|
|
|
*/ |
409
|
21 |
|
public function canUseQFeature( $queryFeatureFlag ) { |
410
|
2 |
|
|
411
|
|
|
$canUse = true; |
412
|
|
|
|
413
|
21 |
|
// Adhere additional condition |
414
|
|
|
if ( $queryFeatureFlag === SMW_SPARQL_QF_SUBP ) { |
415
|
|
|
$canUse = $this->engineOptions->get( 'smwgQSubpropertyDepth' ) > 0; |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
if ( $queryFeatureFlag === SMW_SPARQL_QF_SUBC ) { |
419
|
|
|
$canUse = $this->engineOptions->get( 'smwgQSubcategoryDepth' ) > 0; |
420
|
|
|
} |
421
|
|
|
|
422
|
|
|
return $this->engineOptions->get( 'smwgSparqlQFeatures' ) === ( $this->engineOptions->get( 'smwgSparqlQFeatures' ) | $queryFeatureFlag ) && $canUse; |
|
|
|
|
423
|
|
|
} |
424
|
|
|
|
425
|
24 |
|
/** |
426
|
24 |
|
* Extend the given SPARQL condition by a suitable order by variable, |
427
|
24 |
|
* if an order by property is set. |
428
|
|
|
* |
429
|
|
|
* @param Condition $sparqlCondition condition to modify |
430
|
2 |
|
* @param string $mainVariable the variable that represents the value to be ordered |
431
|
2 |
|
* @param mixed $orderByProperty DIProperty or null |
432
|
|
|
* @param integer $diType DataItem type id if known, or DataItem::TYPE_NOTYPE to determine it from the property |
433
|
|
|
*/ |
434
|
2 |
|
public function addOrderByDataForProperty( Condition &$sparqlCondition, $mainVariable, $orderByProperty, $diType = DataItem::TYPE_NOTYPE ) { |
435
|
2 |
|
if ( is_null( $orderByProperty ) ) { |
436
|
|
|
return; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
if ( $diType == DataItem::TYPE_NOTYPE ) { |
440
|
|
|
$diType = DataTypeRegistry::getInstance()->getDataItemId( $orderByProperty->findPropertyTypeID() ); |
441
|
|
|
} |
442
|
|
|
|
443
|
|
|
$this->addOrderByData( $sparqlCondition, $mainVariable, $diType ); |
444
|
|
|
} |
445
|
10 |
|
|
446
|
|
|
/** |
447
|
10 |
|
* Extend the given SPARQL condition by a suitable order by variable, |
448
|
3 |
|
* possibly adding conditions if required for the type of data. |
449
|
|
|
* |
450
|
|
|
* @param Condition $sparqlCondition condition to modify |
|
|
|
|
451
|
7 |
|
* @param string $mainVariable the variable that represents the value to be ordered |
452
|
7 |
|
* @param integer $diType DataItem type id |
453
|
|
|
*/ |
454
|
|
|
public function addOrderByData( Condition &$condition, $mainVariable, $diType ) { |
455
|
7 |
|
|
456
|
|
|
if ( $diType !== DataItem::TYPE_WIKIPAGE ) { |
457
|
|
|
return $condition->orderByVariable = $mainVariable; |
458
|
7 |
|
} |
459
|
7 |
|
|
460
|
|
|
$condition->orderByVariable = $mainVariable . 'sk'; |
461
|
|
|
$skeyExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_SKEY' ); |
462
|
|
|
|
463
|
|
|
$weakConditions = array( |
464
|
|
|
$condition->orderByVariable =>"?$mainVariable " . $skeyExpElement->getQName() . " ?{$condition->orderByVariable} .\n" |
465
|
|
|
); |
466
|
|
|
|
467
|
|
|
$condition->weakConditions += $weakConditions; |
468
|
|
|
} |
469
|
24 |
|
|
470
|
24 |
|
/** |
471
|
|
|
* Extend the given Condition with additional conditions to |
472
|
5 |
|
* ensure that it can be ordered by all requested properties. After |
473
|
1 |
|
* this operation, every key in sortKeys is assigned to a query |
474
|
|
|
* variable by $sparqlCondition->orderVariables. |
475
|
|
|
* |
476
|
4 |
|
* @param Condition $condition condition to modify |
477
|
|
|
*/ |
478
|
|
|
protected function addMissingOrderByConditions( Condition &$condition ) { |
479
|
|
|
foreach ( $this->sortKeys as $propertyKey => $order ) { |
480
|
4 |
|
|
481
|
4 |
|
if ( !is_string( $propertyKey ) ) { |
482
|
|
|
throw new RuntimeException( "Expected a string value as sortkey" ); |
483
|
|
|
} |
484
|
23 |
|
|
485
|
|
|
if ( strpos( $propertyKey, " " ) !== false ) { |
486
|
3 |
|
throw new RuntimeException( "Expected the canonical form of {$propertyKey} (without any whitespace)" ); |
487
|
|
|
} |
488
|
3 |
|
|
489
|
|
|
if ( !array_key_exists( $propertyKey, $condition->orderVariables ) ) { // Find missing property to sort by. |
490
|
2 |
|
$this->addOrderForUnknownPropertyKey( $condition, $propertyKey, $order ); |
491
|
|
|
} |
492
|
2 |
|
} |
493
|
2 |
|
} |
494
|
|
|
|
495
|
|
|
private function addOrderForUnknownPropertyKey( Condition &$condition, $propertyKey, $order ) { |
496
|
2 |
|
|
497
|
2 |
|
if ( $propertyKey === '' || $propertyKey === '#' ) { // order by result page sortkey |
498
|
|
|
|
499
|
|
|
$this->addOrderByData( |
500
|
1 |
|
$condition, |
501
|
1 |
|
$this->resultVariable, |
502
|
1 |
|
DataItem::TYPE_WIKIPAGE |
503
|
|
|
); |
504
|
|
|
|
505
|
1 |
|
$condition->orderVariables[$propertyKey] = $condition->orderByVariable; |
506
|
1 |
|
return; |
507
|
|
|
} elseif ( PropertyChainValue::isChained( $propertyKey ) ) { // Try to extend query. |
508
|
1 |
|
$propertyChainValue = new PropertyChainValue(); |
509
|
|
|
$propertyChainValue->setUserValue( $propertyKey ); |
510
|
|
|
|
511
|
|
|
if ( !$propertyChainValue->isValid() ) { |
512
|
|
|
return $description; |
|
|
|
|
513
|
1 |
|
} |
514
|
1 |
|
|
515
|
1 |
|
$lastDataItem = $propertyChainValue->getLastPropertyChainValue()->getDataItem(); |
516
|
1 |
|
|
517
|
|
|
$description = $this->descriptionFactory->newSomeProperty( |
518
|
|
|
$lastDataItem, |
|
|
|
|
519
|
|
|
$this->descriptionFactory->newThingDescription() |
520
|
|
|
); |
521
|
|
|
|
522
|
|
|
foreach ( $propertyChainValue->getPropertyChainValues() as $val ) { |
523
|
|
|
$description = $this->descriptionFactory->newSomeProperty( |
524
|
|
|
$val->getDataItem(), |
|
|
|
|
525
|
|
|
$description |
526
|
|
|
); |
527
|
|
|
} |
528
|
|
|
|
529
|
|
|
// Add and replace Foo.Bar=asc with Bar=asc as we ultimately only |
530
|
|
|
// order to the result of the last element |
531
|
|
|
$this->sortKeys[$lastDataItem->getKey()] = $order; |
532
|
|
|
unset( $this->sortKeys[$propertyKey] ); |
533
|
|
|
$propertyKey = $lastDataItem->getKey(); |
534
|
|
|
|
535
|
|
|
$auxDescription = $description; |
536
|
|
|
} else { |
537
|
|
|
$auxDescription = $this->descriptionFactory->newSomeProperty( |
538
|
|
|
new DIProperty( $propertyKey ), |
539
|
|
|
$this->descriptionFactory->newThingDescription() |
540
|
|
|
); |
541
|
|
|
} |
542
|
|
|
|
543
|
|
|
$this->setJoinVariable( $this->resultVariable ); |
544
|
|
|
$this->setOrderByProperty( null ); |
545
|
|
|
|
546
|
|
|
$auxCondition = $this->mapDescriptionToCondition( |
547
|
|
|
$auxDescription |
548
|
23 |
|
); |
549
|
|
|
|
550
|
23 |
|
// orderVariables MUST be set for $propertyKey -- or there is a bug; let it show! |
551
|
21 |
|
$condition->orderVariables[$propertyKey] = $auxCondition->orderVariables[$propertyKey]; |
552
|
|
|
$condition->weakConditions[$condition->orderVariables[$propertyKey]] = $auxCondition->getWeakConditionString() . $auxCondition->getCondition(); |
553
|
|
|
$condition->namespaces = array_merge( $condition->namespaces, $auxCondition->namespaces ); |
554
|
2 |
|
} |
555
|
2 |
|
|
556
|
|
|
/** |
557
|
2 |
|
* @see http://www.w3.org/TR/sparql11-query/#propertypaths |
558
|
2 |
|
* |
559
|
|
|
* Query of: |
560
|
2 |
|
* |
561
|
2 |
|
* SELECT DISTINCT ?result WHERE { |
562
|
2 |
|
* ?result swivt:wikiPageSortKey ?resultsk . |
563
|
2 |
|
* { |
564
|
|
|
* ?result property:FOO ?v1 . |
565
|
|
|
* FILTER( ?v1sk >= "=BAR" ) |
566
|
2 |
|
* ?v1 swivt:wikiPageSortKey ?v1sk . |
567
|
2 |
|
* } UNION { |
568
|
2 |
|
* ?result property:FOO ?v2 . |
569
|
|
|
* } |
570
|
|
|
* } |
571
|
|
|
* |
572
|
|
|
* results in: |
573
|
|
|
* |
574
|
|
|
* SELECT DISTINCT ?result WHERE { |
575
|
23 |
|
* ?result swivt:wikiPageSortKey ?resultsk . |
576
|
|
|
* ?r2 ^swivt:redirectsTo property:FOO . |
577
|
23 |
|
* { |
578
|
23 |
|
* ?result ?r2 ?v1 . |
579
|
|
|
* FILTER( ?v1sk >= "=BAR" ) |
580
|
23 |
|
* ?v1 swivt:wikiPageSortKey ?v1sk . |
581
|
23 |
|
* } UNION { |
582
|
|
|
* ?result ?r2 ?v3 . |
583
|
23 |
|
* } |
584
|
23 |
|
* } |
585
|
23 |
|
*/ |
586
|
|
|
private function addPropertyPathToMatchRedirectTargets( Condition &$condition ) { |
587
|
|
|
|
588
|
|
|
if ( $this->redirectByVariableReplacementMap === array() ) { |
589
|
|
|
return; |
590
|
|
|
} |
591
|
|
|
|
592
|
|
|
$weakConditions = array(); |
593
|
|
|
$namespaces = array(); |
594
|
|
|
|
595
|
|
|
$rediExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_REDI' ); |
596
|
|
|
$namespaces[$rediExpElement->getNamespaceId()] = $rediExpElement->getNamespace(); |
597
|
|
|
|
598
|
|
|
foreach ( $this->redirectByVariableReplacementMap as $valueName => $content ) { |
599
|
|
|
list( $redirectByVariable, $ns ) = $content; |
600
|
|
|
$weakConditions[] = "$redirectByVariable " . "^" . $rediExpElement->getQName() . " $valueName .\n"; |
601
|
|
|
$namespaces = array_merge( $namespaces, $ns ); |
602
|
|
|
} |
603
|
|
|
|
604
|
|
|
$condition->namespaces = array_merge( $condition->namespaces, $namespaces ); |
605
|
|
|
$condition->weakConditions += $weakConditions; |
606
|
|
|
} |
607
|
|
|
|
608
|
|
|
/** |
609
|
|
|
* @see https://www.w3.org/TR/rdf-sparql-query/#func-bound |
610
|
|
|
* |
611
|
|
|
* Remove entities that contain a "swivt:redirectsTo" predicate |
612
|
|
|
*/ |
613
|
|
|
private function addFilterToRemoveEntitiesThatContainRedirectPredicate( Condition &$condition ) { |
614
|
|
|
|
615
|
|
|
$rediExpElement = Exporter::getInstance()->getSpecialPropertyResource( '_REDI' ); |
616
|
|
|
$namespaces[$rediExpElement->getNamespaceId()] = $rediExpElement->getNamespace(); |
|
|
|
|
617
|
|
|
|
618
|
|
|
$boundVariable = '?' . $this->getNextVariable( 'o' ); |
619
|
|
|
$cogentCondition = " OPTIONAL { ?$this->resultVariable " . $rediExpElement->getQName() . " $boundVariable } .\n FILTER ( !bound( $boundVariable ) ) .\n"; |
620
|
|
|
|
621
|
|
|
$condition->addNamespaces( $namespaces ); |
622
|
|
|
$condition->cogentConditions[$boundVariable] = $cogentCondition; |
623
|
|
|
} |
624
|
|
|
|
625
|
|
|
} |
626
|
|
|
|
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..