Index: trunk/extensions/SemanticMediaWiki/includes/storage/SMW_SparqlStoreQueryEngine.php |
— | — | @@ -32,7 +32,7 @@ |
33 | 33 | * typically for ordering. For instance, selecting the sortkey of a |
34 | 34 | * page needs only be done once per query. The array is indexed by the |
35 | 35 | * name of the (main) selected variable, e.g. "v42sortkey" to allow |
36 | | - * elimination of duplicate weak conditions that aimm to introduce this |
| 36 | + * elimination of duplicate weak conditions that aim to introduce this |
37 | 37 | * variable. |
38 | 38 | * @var array of format "condition identifier" => "condition" |
39 | 39 | */ |
— | — | @@ -226,6 +226,9 @@ |
227 | 227 | */ |
228 | 228 | class SMWSparqlStoreQueryEngine { |
229 | 229 | |
| 230 | + /// The name of the SPARQL variable that represents the query result. |
| 231 | + const RESULT_VARIABLE = 'result'; |
| 232 | + |
230 | 233 | /** |
231 | 234 | * Counter used to generate globally fresh variables. |
232 | 235 | * @var integer |
— | — | @@ -233,6 +236,21 @@ |
234 | 237 | protected $m_variableCounter = 0; |
235 | 238 | |
236 | 239 | /** |
| 240 | + * Array that relates sortkeys (given by the users, i.e. property |
| 241 | + * names) to variable names in the generated SPARQL query. |
| 242 | + * Format sortkey => variable name |
| 243 | + * @var array |
| 244 | + */ |
| 245 | + protected $m_orderVariables; |
| 246 | + |
| 247 | + /** |
| 248 | + * Copy of the SMWQuery sortkeys array to be used while building the |
| 249 | + * SPARQL query conditions. |
| 250 | + * @var array |
| 251 | + */ |
| 252 | + protected $m_sortKeys; |
| 253 | + |
| 254 | + /** |
237 | 255 | * The store that we work for. |
238 | 256 | * @var SMWStore |
239 | 257 | */ |
— | — | @@ -243,6 +261,7 @@ |
244 | 262 | } |
245 | 263 | |
246 | 264 | public function getInstanceQueryResult( SMWQuery $query ) { |
| 265 | + $this->m_sortkeys = $query->sortkeys; |
247 | 266 | $sparqlCondition = $this->getSparqlCondition( $query->getDescription() ); |
248 | 267 | |
249 | 268 | //debug_zval_dump($sparqlCondition); |
— | — | @@ -250,12 +269,11 @@ |
251 | 270 | $condition = $sparqlCondition->getWeakConditionString(); |
252 | 271 | if ( ( $condition == '' ) && !$sparqlCondition->isSafe() ) { |
253 | 272 | $swivtPageResource = SMWExporter::getSpecialNsResource( 'swivt', 'page' ); |
254 | | - $condition = '?result ' . $swivtPageResource->getQName() . " ?url .\n"; |
| 273 | + $condition = '?' . self::RESULT_VARIABLE . $swivtPageResource->getQName() . " ?url .\n"; |
255 | 274 | } |
256 | 275 | $condition .= $sparqlCondition->getCondition(); |
257 | 276 | |
258 | 277 | if ( $sparqlCondition instanceof SMWSparqlSingletonCondition ) { |
259 | | - // TODO use ASK to handle this |
260 | 278 | $matchElement = $sparqlCondition->matchElement; |
261 | 279 | if ( $sparqlCondition->condition == '' ) { |
262 | 280 | $results = array( array ( $matchElement ) ); |
— | — | @@ -264,7 +282,7 @@ |
265 | 283 | if ( $matchElement instanceof SMWExpNsResource ) { |
266 | 284 | $namespaces[$matchElement->getNamespaceId()] = $matchElement->getNamespace(); |
267 | 285 | } |
268 | | - $condition = str_replace( '?result ', "$matchElementName ", $condition ); |
| 286 | + $condition = str_replace( '?' . self::RESULT_VARIABLE, "$matchElementName ", $condition ); |
269 | 287 | $askQueryResult = smwfGetSparqlDatabase()->ask( $condition, $namespaces ); |
270 | 288 | if ( $askQueryResult->isBooleanTrue() ) { |
271 | 289 | $results = array( array ( $matchElement ) ); |
— | — | @@ -272,14 +290,14 @@ |
273 | 291 | $results = array(); |
274 | 292 | } |
275 | 293 | } |
276 | | - $sparqlResult = new SMWSparqlResultWrapper( array( 'result' => 0 ), $results ); //TODO |
| 294 | + $sparqlResult = new SMWSparqlResultWrapper( array( self::RESULT_VARIABLE => 0 ), $results ); |
277 | 295 | } elseif ( $sparqlCondition instanceof SMWSparqlFalseCondition ) { |
278 | | - $sparqlResult = new SMWSparqlResultWrapper( array( 'result' => 0 ), array() ); |
| 296 | + $sparqlResult = new SMWSparqlResultWrapper( array( self::RESULT_VARIABLE => 0 ), array() ); |
279 | 297 | } else { |
280 | 298 | //debug_zval_dump( $condition ); |
281 | 299 | $options = $this->getSparqlOptions( $query ); |
282 | 300 | $options['DISTINCT'] = true; |
283 | | - $sparqlResult = smwfGetSparqlDatabase()->select( '?result', |
| 301 | + $sparqlResult = smwfGetSparqlDatabase()->select( '?' . self::RESULT_VARIABLE, |
284 | 302 | $condition, $options, $namespaces ); |
285 | 303 | } |
286 | 304 | |
— | — | @@ -308,9 +326,11 @@ |
309 | 327 | return $result; |
310 | 328 | } |
311 | 329 | |
312 | | - public function getSparqlCondition( SMWDescription $description ) { |
| 330 | + protected function getSparqlCondition( SMWDescription $description ) { |
313 | 331 | $this->m_variableCounter = 0; |
314 | | - $sparqlCondition = $this->buildSparqlCondition( $description, 'result', null ); |
| 332 | + $this->m_orderVariables = array(); |
| 333 | + $sparqlCondition = $this->buildSparqlCondition( $description, self::RESULT_VARIABLE, null ); |
| 334 | + $this->addMissingOrderByConditions( $sparqlCondition ); |
315 | 335 | return $sparqlCondition; |
316 | 336 | } |
317 | 337 | |
— | — | @@ -333,15 +353,14 @@ |
334 | 354 | } elseif ( $description instanceof SMWConceptDescription ) { |
335 | 355 | return new SMWSparqlTrueCondition(); ///TODO Implement concept queries |
336 | 356 | } else { // (e.g. SMWThingDescription) |
337 | | - return new SMWSparqlTrueCondition(); |
| 357 | + return $this->buildTrueCondition( $joinVariable, $orderByProperty ); |
338 | 358 | } |
339 | 359 | } |
340 | 360 | |
341 | 361 | protected function buildConjunctionCondition( SMWConjunction $description, $joinVariable, $orderByProperty ) { |
342 | 362 | $subDescriptions = $description->getDescriptions(); |
343 | 363 | if ( count( $subDescriptions ) == 0 ) { // empty conjunction: true |
344 | | - /// FIXME Ordering is missing |
345 | | - return new SMWSparqlTrueCondition(); |
| 364 | + return $this->buildTrueCondition( $joinVariable, $orderByProperty ); |
346 | 365 | } elseif ( count( $subDescriptions ) == 1 ) { // conjunction with one element |
347 | 366 | return $this->buildSparqlCondition( reset( $subDescriptions ), $joinVariable, $orderByProperty ); |
348 | 367 | } |
— | — | @@ -399,13 +418,7 @@ |
400 | 419 | |
401 | 420 | $result->weakConditions = $weakConditions; |
402 | 421 | |
403 | | - //*** Possibly add conditions for ordering ***// |
404 | | - if ( $orderByProperty !== null ) { |
405 | | - /// TODO Depends on datatype |
406 | | -// $result->orderByVariable = $joinVariable . 'sk'; |
407 | | -// $skeyExpElement = SMWExporter::getSpecialPropertyResource( '_SKEY' ); |
408 | | -// $result->weakConditions = array( $result->orderByVariable => "?$joinVariable " . $skeyExpElement->getQName() . " ?{$result->orderByVariable} .\n" ); |
409 | | - } |
| 422 | + $this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty ); |
410 | 423 | |
411 | 424 | return $result; |
412 | 425 | } |
— | — | @@ -427,8 +440,7 @@ |
428 | 441 | if ( $subCondition instanceof SMWSparqlFalseCondition ) { |
429 | 442 | // empty parts in a disjunction can be ignored |
430 | 443 | } elseif ( $subCondition instanceof SMWSparqlTrueCondition ) { |
431 | | - /// FIXME Take ordering into account. |
432 | | - return new SMWSparqlTrueCondition(); |
| 444 | + return $this->buildTrueCondition( $joinVariable, $orderByProperty ); |
433 | 445 | } elseif ( $subCondition instanceof SMWSparqlWhereCondition ) { |
434 | 446 | $hasSafeSubconditions = $hasSafeSubconditions || $subCondition->isSafe(); |
435 | 447 | $unionCondition .= ( $unionCondition ? ' UNION ' : '' ) . |
— | — | @@ -468,13 +480,7 @@ |
469 | 481 | |
470 | 482 | $result->weakConditions = $weakConditions; |
471 | 483 | |
472 | | - //*** Possibly add conditions for ordering ***// |
473 | | - if ( $orderByProperty !== null ) { |
474 | | - /// TODO Depends on datatype |
475 | | -// $result->orderByVariable = $joinVariable . 'sk'; |
476 | | -// $skeyExpElement = SMWExporter::getSpecialPropertyResource( '_SKEY' ); |
477 | | -// $result->weakConditions = array( $result->orderByVariable => "?$joinVariable " . $skeyExpElement->getQName() . " ?{$result->orderByVariable} .\n" ); |
478 | | - } |
| 484 | + $this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty ); |
479 | 485 | |
480 | 486 | return $result; |
481 | 487 | |
— | — | @@ -484,7 +490,11 @@ |
485 | 491 | $diProperty = $description->getProperty(); |
486 | 492 | |
487 | 493 | //*** Find out if we should order by the values of this property ***// |
488 | | - $innerOrderByProperty = null; //TODO, also add to some global register if found below ... |
| 494 | + if ( array_key_exists( $diProperty->getKey(), $this->m_sortkeys ) ) { |
| 495 | + $innerOrderByProperty = $diProperty; |
| 496 | + } else { |
| 497 | + $innerOrderByProperty = null; |
| 498 | + } |
489 | 499 | |
490 | 500 | //*** Prepare inner condition ***// |
491 | 501 | $innerJoinVariable = $this->getNextVariable(); |
— | — | @@ -503,6 +513,11 @@ |
504 | 514 | $objectName = '?' . $innerJoinVariable; |
505 | 515 | } |
506 | 516 | |
| 517 | + //*** Record inner ordering variable if found ***// |
| 518 | + if ( ( $innerOrderByProperty !== null ) && ( $innerCondition->orderByVariable != '' ) ) { |
| 519 | + $this->m_orderVariables[$diProperty->getKey()] = $innerCondition->orderByVariable; |
| 520 | + } |
| 521 | + |
507 | 522 | //*** Exchange arguments when property is inverse ***// |
508 | 523 | if ( $diProperty->isInverse() ) { // don't check if this really makes sense |
509 | 524 | $subjectName = $objectName; |
— | — | @@ -524,12 +539,7 @@ |
525 | 540 | } |
526 | 541 | $result = new SMWSparqlWhereCondition( $condition, true, $namespaces ); |
527 | 542 | |
528 | | - //*** Possibly add conditions for ordering ***// |
529 | | - if ( $orderByProperty !== null ) { |
530 | | - $result->orderByVariable = $joinVariable . 'sk'; |
531 | | - $skeyExpElement = SMWExporter::getSpecialPropertyResource( '_SKEY' ); |
532 | | - $result->weakConditions = array( $result->orderByVariable => "?$joinVariable " . $skeyExpElement->getQName() . " ?{$result->orderByVariable} .\n" ); |
533 | | - } |
| 543 | + $this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty, SMWDataItem::TYPE_WIKIPAGE ); |
534 | 544 | |
535 | 545 | return $result; |
536 | 546 | } |
— | — | @@ -556,11 +566,7 @@ |
557 | 567 | |
558 | 568 | $result = new SMWSparqlWhereCondition( $condition, true, $namespaces ); |
559 | 569 | |
560 | | - if ( $orderByProperty !== null ) { |
561 | | - $result->orderByVariable = $joinVariable . 'sk'; |
562 | | - $skeyExpElement = SMWExporter::getSpecialPropertyResource( '_SKEY' ); |
563 | | - $result->weakConditions = array( $result->orderByVariable => "?$joinVariable " . $skeyExpElement->getQName() . " ?{$result->orderByVariable} .\n" ); |
564 | | - } |
| 570 | + $this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty, SMWDataItem::TYPE_WIKIPAGE ); |
565 | 571 | |
566 | 572 | return $result; |
567 | 573 | } |
— | — | @@ -595,22 +601,88 @@ |
596 | 602 | $result = new SMWSparqlFilterCondition( $filter, $namespaces ); |
597 | 603 | } |
598 | 604 | |
599 | | - //*** Possibly add conditions for ordering ***// |
600 | | - if ( $orderByProperty !== null ) { |
601 | | - /// TODO Depends on datatype |
602 | | -// $result->orderByVariable = $joinVariable . 'sk'; |
603 | | -// $skeyExpElement = SMWExporter::getSpecialPropertyResource( '_SKEY' ); |
604 | | -// $result->weakConditions = array( $result->orderByVariable => "?$joinVariable " . $skeyExpElement->getQName() . " ?{$result->orderByVariable} .\n" ); |
605 | | - } |
| 605 | + $this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty, $dataItem->getDIType() ); |
606 | 606 | |
607 | 607 | return $result; |
608 | 608 | } |
609 | 609 | |
| 610 | + protected function buildTrueCondition( $joinVariable, $orderByProperty ) { |
| 611 | + $result = new SMWSparqlTrueCondition(); |
| 612 | + $this->addOrderByDataForProperty( $result, $joinVariable, $orderByProperty ); |
| 613 | + return $result; |
| 614 | + } |
| 615 | + |
610 | 616 | protected function getNextVariable() { |
611 | 617 | return 'v' . ( ++$this->m_variableCounter ); |
612 | 618 | } |
613 | 619 | |
614 | 620 | /** |
| 621 | + * Extend the given SPARQL condition by a suitable order by variable, |
| 622 | + * if an order by property is set. |
| 623 | + * |
| 624 | + * @param SMWSparqlCondition $sparqlCondition condition to modify |
| 625 | + * @param string $mainVariable the variable that represents the value to be ordered |
| 626 | + * @param mixed $orderByProperty SMWDIProperty or null |
| 627 | + * @param integer $diType DataItem type id if known, or SMWDataItem::TYPE_NOTYPE to determine it from the property |
| 628 | + */ |
| 629 | + protected function addOrderByDataForProperty( SMWSparqlCondition &$sparqlCondition, $mainVariable, $orderByProperty, $diType = SMWDataItem::TYPE_NOTYPE ) { |
| 630 | + if ( $orderByProperty === null ) { |
| 631 | + return; |
| 632 | + } |
| 633 | + |
| 634 | + if ( $diType == SMWDataItem::TYPE_NOTYPE ) { |
| 635 | + $typeId = $orderByProperty->findPropertyTypeID(); |
| 636 | + $diType = SMWDataValueFactory::getDataItemId( $typeId ); |
| 637 | + } |
| 638 | + |
| 639 | + $this->addOrderByData( $sparqlCondition, $mainVariable, $diType ); |
| 640 | + } |
| 641 | + |
| 642 | + /** |
| 643 | + * Extend the given SPARQL condition by a suitable order by variable, |
| 644 | + * possibly adding conditions if required for the type of data. |
| 645 | + * |
| 646 | + * @param SMWSparqlCondition $sparqlCondition condition to modify |
| 647 | + * @param string $mainVariable the variable that represents the value to be ordered |
| 648 | + * @param integer $diType DataItem type id |
| 649 | + */ |
| 650 | + protected function addOrderByData( SMWSparqlCondition &$sparqlCondition, $mainVariable, $diType ) { |
| 651 | + if ( $diType == SMWDataItem::TYPE_WIKIPAGE ) { |
| 652 | + $sparqlCondition->orderByVariable = $mainVariable . 'sk'; |
| 653 | + $skeyExpElement = SMWExporter::getSpecialPropertyResource( '_SKEY' ); |
| 654 | + $sparqlCondition->weakConditions = array( $sparqlCondition->orderByVariable => |
| 655 | + "?$mainVariable " . $skeyExpElement->getQName() . " ?{$sparqlCondition->orderByVariable} .\n" ); |
| 656 | + } else { |
| 657 | + $sparqlCondition->orderByVariable = $mainVariable; |
| 658 | + } |
| 659 | + } |
| 660 | + |
| 661 | + /** |
| 662 | + * Extend the given SMWSparqlCondition with additional conditions to |
| 663 | + * ensure that it can be ordered by all requested properties. After |
| 664 | + * this operation, every key in m_sortkeys is assigned to a query |
| 665 | + * variable by m_orderVariables. |
| 666 | + * |
| 667 | + * @param SMWSparqlCondition $sparqlCondition condition to modify |
| 668 | + */ |
| 669 | + protected function addMissingOrderByConditions( SMWSparqlCondition &$sparqlCondition ) { |
| 670 | + foreach ( $this->m_sortkeys as $propkey => $order ) { |
| 671 | + if ( !array_key_exists( $propkey, $this->m_orderVariables ) ) { // Find missing property to sort by. |
| 672 | + if ( $propkey == '' ) { // order by result page sortkey |
| 673 | + $this->addOrderByData( $sparqlCondition, self::RESULT_VARIABLE, SMWDataItem::TYPE_WIKIPAGE ); |
| 674 | + $this->m_orderVariables[$propkey] = $sparqlCondition->orderByVariable; |
| 675 | + } else { // extend query to order by other property values |
| 676 | + $diProperty = new SMWDIProperty( $propkey ); |
| 677 | + $auxDescription = new SMWSomeProperty( $diProperty, new SMWThingDescription() ); |
| 678 | + $auxSparqlCondition = $this->buildSparqlCondition( $auxDescription, self::RESULT_VARIABLE, null ); |
| 679 | + // m_orderVariables MUST be set for $propkey -- or there is a bug; let it show! |
| 680 | + $sparqlCondition->weakConditions[$this->m_orderVariables[$propkey]] = $auxSparqlCondition->getWeakConditionString() . $auxSparqlCondition->getCondition(); |
| 681 | + } |
| 682 | + } |
| 683 | + } |
| 684 | + } |
| 685 | + |
| 686 | + /** |
615 | 687 | * Get a SPARQL option array for the given query. |
616 | 688 | * |
617 | 689 | * @param SMWQuery $query |
— | — | @@ -622,17 +694,19 @@ |
623 | 695 | $result = array( 'LIMIT' => $query->getLimit() + 1, 'OFFSET' => $query->getOffset() ); |
624 | 696 | |
625 | 697 | // Build ORDER BY options using discovered sorting fields. |
626 | | -// if ( $smwgQSortingSupport ) { |
627 | | -// $qobj = $this->m_queries[$rootid]; |
628 | | -// |
629 | | -// foreach ( $this->m_sortkeys as $propkey => $order ) { |
630 | | -// if ( ( $order != 'RANDOM' ) && array_key_exists( $propkey, $qobj->sortfields ) ) { // Field was successfully added. |
631 | | -// $result['ORDER BY'] = ( array_key_exists( 'ORDER BY', $result ) ? $result['ORDER BY'] . ', ' : '' ) . $qobj->sortfields[$propkey] . " $order "; |
632 | | -// } elseif ( ( $order == 'RANDOM' ) && $smwgQRandSortingSupport ) { |
633 | | -// $result['ORDER BY'] = ( array_key_exists( 'ORDER BY', $result ) ? $result['ORDER BY'] . ', ' : '' ) . ' RAND() '; |
634 | | -// } |
635 | | -// } |
636 | | -// } |
| 698 | + if ( $smwgQSortingSupport ) { |
| 699 | + $orderByString = ''; |
| 700 | + foreach ( $this->m_sortkeys as $propkey => $order ) { |
| 701 | + if ( ( $order != 'RANDOM' ) && array_key_exists( $propkey, $this->m_orderVariables ) ) { |
| 702 | + $orderByString .= "$order(?" . $this->m_orderVariables[$propkey] . ") "; |
| 703 | + } elseif ( ( $order == 'RANDOM' ) && $smwgQRandSortingSupport ) { |
| 704 | + // not supported in SPARQL; might be possible via function calls in some stores |
| 705 | + } |
| 706 | + } |
| 707 | + if ( $orderByString != '' ) { |
| 708 | + $result['ORDER BY'] = $orderByString; |
| 709 | + } |
| 710 | + } |
637 | 711 | return $result; |
638 | 712 | } |
639 | 713 | |