562 lines
13 KiB
PHP
562 lines
13 KiB
PHP
<?php
|
|
|
|
use SMW\DIWikiPage;
|
|
use SMW\Localizer\Message;
|
|
use SMW\Query\Language\Description;
|
|
use SMW\Query\PrintRequest;
|
|
use SMW\Query\QueryContext;
|
|
use SMW\Query\QueryStringifier;
|
|
use SMW\Query\QueryToken;
|
|
|
|
/**
|
|
* This file contains the class for representing queries in SMW, each
|
|
* consisting of a query description and possible query parameters.
|
|
* @ingroup SMWQuery
|
|
* @author Markus Krötzsch
|
|
*/
|
|
|
|
/**
|
|
* This group contains all parts of SMW that relate to processing semantic queries.
|
|
* SMW components that relate to plain storage access (for querying or otherwise)
|
|
* have their own group.
|
|
* @defgroup SMWQuery SMWQuery
|
|
* @ingroup SMW
|
|
*/
|
|
|
|
/**
|
|
* Representation of queries in SMW, each consisting of a query
|
|
* description and various parameters. Some settings might also lead to
|
|
* changes in the query description.
|
|
*
|
|
* Most additional query parameters (limit, sort, ascending, ...) are
|
|
* interpreted as in RequestOptions (though the latter contains some
|
|
* additional settings).
|
|
* @ingroup SMWQuery
|
|
*/
|
|
class SMWQuery implements QueryContext {
|
|
|
|
const ID_PREFIX = '_QUERY';
|
|
|
|
/**
|
|
* The time the QueryEngine required to answer a query condition
|
|
*/
|
|
const PROC_QUERY_TIME = 'proc.query.time';
|
|
|
|
/**
|
|
* The time a ResultPrinter required to build the final result including all
|
|
* PrintRequests
|
|
*/
|
|
const PROC_PRINT_TIME = 'proc.print.time';
|
|
|
|
/**
|
|
* The processing context in which the query is being executed
|
|
*/
|
|
const PROC_CONTEXT = 'proc.context';
|
|
|
|
/**
|
|
* Status code information
|
|
*/
|
|
const PROC_STATUS_CODE = 'proc.status.code';
|
|
|
|
/**
|
|
* The processing parameters
|
|
*/
|
|
const OPT_PARAMETERS = 'proc.parameters';
|
|
|
|
/**
|
|
* Suppress a possible cache request
|
|
*/
|
|
const NO_CACHE = 'no.cache';
|
|
|
|
/**
|
|
* Indicates no dependency trace
|
|
*/
|
|
const NO_DEPENDENCY_TRACE = 'no.dependency.trace';
|
|
|
|
/**
|
|
* Sort by score if the query engine supports it.
|
|
*/
|
|
const SCORE_SORT = 'score.sort';
|
|
|
|
public $sort = false;
|
|
public $sortkeys = []; // format: "Property key" => "ASC" / "DESC" (note: order of entries also matters)
|
|
public $querymode = self::MODE_INSTANCES;
|
|
|
|
private $limit;
|
|
private $offset = 0;
|
|
private $description;
|
|
private $errors = []; // keep any errors that occurred so far
|
|
private $queryString = false; // string (inline query) version (if fixed and known)
|
|
private $isInline; // query used inline? (required for finding right default parameters)
|
|
private $isUsedInConcept; // query used in concept? (required for finding right default parameters)
|
|
|
|
/**
|
|
* @var PrintRequest[]
|
|
*/
|
|
private $m_extraprintouts = []; // SMWPrintoutRequest objects supplied outside querystring
|
|
private $m_mainlabel = ''; // Since 1.6
|
|
|
|
/**
|
|
* @var DIWikiPage|null
|
|
*/
|
|
private $contextPage;
|
|
|
|
/**
|
|
* Describes a non-local (remote) query source
|
|
*
|
|
* @var string|null
|
|
*/
|
|
private $querySource = null;
|
|
|
|
/**
|
|
* @var QueryToken|null
|
|
*/
|
|
private $queryToken;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
private $options = [];
|
|
|
|
/**
|
|
* @since 1.6
|
|
*
|
|
* @param Description|null $description
|
|
* @param int|bool $context
|
|
*/
|
|
public function __construct( ?Description $description = null, $context = false ) {
|
|
$inline = false;
|
|
$concept = false;
|
|
|
|
// stating whether this query runs in an inline context; used to
|
|
// determine proper default parameters (e.g. the default limit)
|
|
if ( $context === self::INLINE_QUERY || $context === self::DEFERRED_QUERY ) {
|
|
$inline = true;
|
|
}
|
|
|
|
// stating whether this query belongs to a concept; used to determine
|
|
// proper default parameters (concepts usually have less restrictions)
|
|
if ( $context === self::CONCEPT_DESC ) {
|
|
$concept = true;
|
|
}
|
|
|
|
$this->limit = $inline ? $GLOBALS['smwgQMaxInlineLimit'] : $GLOBALS['smwgQMaxLimit'];
|
|
$this->isInline = $inline;
|
|
$this->isUsedInConcept = $concept;
|
|
$this->description = $description;
|
|
$this->applyRestrictions();
|
|
}
|
|
|
|
/**
|
|
* @since 3.0
|
|
*
|
|
* @param boolean
|
|
*/
|
|
public function isEmbedded() {
|
|
return $this->isInline;
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @param integer
|
|
*/
|
|
public function setQueryMode( $queryMode ) {
|
|
// FIXME 3.0; $this->querymode is a public property
|
|
// declare it private and rename it to $this->queryMode
|
|
$this->querymode = $queryMode;
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @param integer
|
|
*/
|
|
public function getQueryMode() {
|
|
return $this->querymode;
|
|
}
|
|
|
|
/**
|
|
* @since 2.3
|
|
*
|
|
* @param DIWikiPage|null $contextPage
|
|
*/
|
|
public function setContextPage( ?DIWikiPage $contextPage = null ) {
|
|
$this->contextPage = $contextPage;
|
|
}
|
|
|
|
/**
|
|
* @since 2.3
|
|
*
|
|
* @return DIWikiPage|null
|
|
*/
|
|
public function getContextPage() {
|
|
return $this->contextPage;
|
|
}
|
|
|
|
/**
|
|
* @since 2.4
|
|
*
|
|
* @param string
|
|
*/
|
|
public function setQuerySource( $querySource ) {
|
|
$this->querySource = $querySource;
|
|
}
|
|
|
|
/**
|
|
* @since 2.4
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getQuerySource() {
|
|
return $this->querySource;
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @param QueryToken|null $queryToken
|
|
*/
|
|
public function setQueryToken( ?QueryToken $queryToken = null ) {
|
|
$this->queryToken = $queryToken;
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @return QueryToken|null
|
|
*/
|
|
public function getQueryToken() {
|
|
return $this->queryToken;
|
|
}
|
|
|
|
/**
|
|
* Sets the mainlabel.
|
|
*
|
|
* @since 1.6.
|
|
*
|
|
* @param string $mainlabel
|
|
*/
|
|
public function setMainLabel( $mainlabel ) {
|
|
$this->m_mainlabel = $mainlabel;
|
|
}
|
|
|
|
/**
|
|
* Gets the mainlabel.
|
|
*
|
|
* @since 1.6.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getMainLabel() {
|
|
return $this->m_mainlabel;
|
|
}
|
|
|
|
public function setDescription( Description $description ) {
|
|
$this->description = $description;
|
|
$this->queryString = false;
|
|
|
|
foreach ( $this->m_extraprintouts as $printout ) {
|
|
$this->description->addPrintRequest( $printout );
|
|
}
|
|
$this->applyRestrictions();
|
|
}
|
|
|
|
public function getDescription() {
|
|
return $this->description;
|
|
}
|
|
|
|
public function setExtraPrintouts( $extraprintouts ) {
|
|
$this->m_extraprintouts = $extraprintouts;
|
|
|
|
if ( $this->description !== null ) {
|
|
foreach ( $extraprintouts as $printout ) {
|
|
$this->description->addPrintRequest( $printout );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return PrintRequest[]
|
|
*/
|
|
public function getExtraPrintouts() {
|
|
return $this->m_extraprintouts;
|
|
}
|
|
|
|
/**
|
|
* @since 3.0
|
|
*/
|
|
public function clearErrors() {
|
|
$this->errors = [];
|
|
}
|
|
|
|
public function getErrors() {
|
|
return $this->errors;
|
|
}
|
|
|
|
public function addErrors( $errors ) {
|
|
$this->errors = array_merge( $this->errors, $errors );
|
|
}
|
|
|
|
public function setQueryString( $querystring ) {
|
|
$this->queryString = $querystring;
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @param string|int $key
|
|
* @param mixed $value
|
|
*/
|
|
public function setOption( $key, $value ) {
|
|
$this->options[$key] = $value;
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @param string|int $key
|
|
*
|
|
* @return mixed
|
|
*/
|
|
public function getOption( $key ) {
|
|
return isset( $this->options[$key] ) ? $this->options[$key] : false;
|
|
}
|
|
|
|
/**
|
|
* @since 1.7
|
|
*
|
|
* @param bool $fresh
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getQueryString( $fresh = false ) {
|
|
// Mostly relevant on requesting a further results link to
|
|
// ensure that localized values are transformed into a canonical
|
|
// representation
|
|
if ( $fresh && $this->description !== null ) {
|
|
return $this->description->getQueryString();
|
|
}
|
|
|
|
if ( $this->queryString !== false ) {
|
|
return $this->queryString;
|
|
} elseif ( $this->description !== null ) {
|
|
return $this->description->getQueryString();
|
|
} else {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
public function getOffset() {
|
|
return $this->offset;
|
|
}
|
|
|
|
/**
|
|
* Set an offset for the returned query results. No offset beyond the maximal query
|
|
* limit will be set, and the current query limit might be reduced in order to ensure
|
|
* that no results beyond the maximal limit are returned.
|
|
* The function returns the chosen offset.
|
|
* @todo The function should be extended to take into account whether or not we
|
|
* are in inline mode (not critical, since offsets are usually not applicable inline).
|
|
*/
|
|
public function setOffset( $offset ) {
|
|
global $smwgQMaxLimit;
|
|
$this->offset = min( $smwgQMaxLimit, $offset ); // select integer between 0 and maximal limit;
|
|
$this->limit = min( $smwgQMaxLimit - $this->offset, $this->limit ); // note that limit might become 0 here
|
|
return $this->offset;
|
|
}
|
|
|
|
/**
|
|
* @note Sets an unbound offset that is independent from GLOBAL settings
|
|
*
|
|
* @since 3.0
|
|
*
|
|
* @param int $offset
|
|
*/
|
|
public function setUnboundOffset( $offset ) {
|
|
$this->offset = (int)$offset;
|
|
}
|
|
|
|
public function getLimit() {
|
|
return $this->limit;
|
|
}
|
|
|
|
/**
|
|
* Set a limit for number of query results. The set limit might be restricted by the
|
|
* current offset so as to ensure that the number of the last considered result does not
|
|
* exceed the maximum amount of supported results.
|
|
* The function returns the chosen limit.
|
|
* @note It makes sense to have limit==0, e.g. to only show a link to the search special
|
|
*/
|
|
public function setLimit( $limit, $restrictinline = true ) {
|
|
global $smwgQMaxLimit, $smwgQMaxInlineLimit;
|
|
$maxlimit = ( $this->isInline && $restrictinline ) ? $smwgQMaxInlineLimit : $smwgQMaxLimit;
|
|
$this->limit = min( $smwgQMaxLimit - $this->offset, $limit, $maxlimit );
|
|
return $this->limit;
|
|
}
|
|
|
|
/**
|
|
* @note Sets an unbound limit that is independent from GLOBAL settings
|
|
*
|
|
* @since 2.0
|
|
*
|
|
* @param int $limit
|
|
*/
|
|
public function setUnboundLimit( $limit ) {
|
|
$this->limit = (int)$limit;
|
|
}
|
|
|
|
/**
|
|
* @note format: "Property key" => "ASC" / "DESC" (note: order of entries also matters)
|
|
*
|
|
* @since 2.2
|
|
*
|
|
* @param array $sortKeys
|
|
*/
|
|
public function setSortKeys( array $sortKeys ) {
|
|
$this->sortkeys = $sortKeys;
|
|
}
|
|
|
|
/**
|
|
* @since 2.2
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getSortKeys() {
|
|
return $this->sortkeys;
|
|
}
|
|
|
|
/**
|
|
* Apply structural restrictions to the current description.
|
|
*/
|
|
public function applyRestrictions() {
|
|
global $smwgQMaxSize, $smwgQMaxDepth, $smwgQConceptMaxSize, $smwgQConceptMaxDepth;
|
|
|
|
if ( $this->description !== null ) {
|
|
if ( $this->isUsedInConcept ) {
|
|
$maxsize = $smwgQConceptMaxSize;
|
|
$maxdepth = $smwgQConceptMaxDepth;
|
|
} else {
|
|
$maxsize = $smwgQMaxSize;
|
|
$maxdepth = $smwgQMaxDepth;
|
|
}
|
|
|
|
$log = [];
|
|
$this->description = $this->description->prune( $maxsize, $maxdepth, $log );
|
|
|
|
if ( count( $log ) > 0 ) {
|
|
$this->errors[] = Message::encode( [
|
|
'smw_querytoolarge',
|
|
str_replace( '[', '[', implode( ', ', $log ) ),
|
|
count( $log )
|
|
] );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns serialized query details
|
|
*
|
|
* The output is following the askargs api module convention
|
|
*
|
|
* conditions The query conditions (requirements for a subject to be included)
|
|
* printouts The query printouts (which properties to show per subject)
|
|
* parameters The query parameters (non-condition and non-printout arguments)
|
|
*
|
|
* @since 1.9
|
|
*
|
|
* @return array
|
|
*/
|
|
public function toArray() {
|
|
$serialized = [];
|
|
|
|
$serialized['conditions'] = $this->getQueryString();
|
|
|
|
// This can be extended but for the current use cases that is
|
|
// sufficient since most printer related parameters have to be sourced
|
|
// in the result printer class
|
|
$serialized['parameters'] = [
|
|
'limit' => $this->limit,
|
|
'offset' => $this->offset,
|
|
'sortkeys' => $this->sortkeys,
|
|
'mainlabel' => $this->m_mainlabel,
|
|
'querymode' => $this->querymode
|
|
];
|
|
|
|
// @2.4 Keep the queryID stable with previous versions unless
|
|
// a query source is selected. The "same" query executed on different
|
|
// remote systems requires a different queryID
|
|
if ( $this->querySource !== null && $this->querySource !== '' ) {
|
|
$serialized['parameters']['source'] = $this->querySource;
|
|
}
|
|
|
|
foreach ( $this->getExtraPrintouts() as $printout ) {
|
|
if ( ( $serialisation = $printout->getSerialisation() ) !== '' ) {
|
|
$serialized['printouts'][] = $serialisation;
|
|
}
|
|
}
|
|
|
|
return $serialized;
|
|
}
|
|
|
|
/**
|
|
* @note Before 2.5, toArray was used to generate the content, as of 2.5
|
|
* only parameters that influence the result of an query is included.
|
|
*
|
|
* @since 2.1
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getHash() {
|
|
// Only use elements that directly influence the result list
|
|
$serialized = [];
|
|
|
|
// Don't use the QueryString, use the canonized fingerprint to ensure that
|
|
// [[Foo::123]][[Bar::abc]] returns the same ID as [[Bar::abc]][[Foo::123]]
|
|
// given that limit, offset, and sort/order are the same
|
|
if ( $this->description !== null ) {
|
|
$serialized['fingerprint'] = $this->description->getFingerprint();
|
|
} else {
|
|
$serialized['conditions'] = $this->getQueryString();
|
|
}
|
|
|
|
$serialized['parameters'] = [
|
|
'limit' => $this->limit,
|
|
'offset' => $this->offset,
|
|
'sortkeys' => $this->sortkeys,
|
|
|
|
// COUNT, DEBUG ...
|
|
'querymode' => $this->querymode
|
|
];
|
|
|
|
// Make to sure to distinguish queries and results from a foreign repository
|
|
if ( $this->querySource !== null && $this->querySource !== '' ) {
|
|
$serialized['parameters']['source'] = $this->querySource;
|
|
}
|
|
|
|
// Printouts are avoided as part of the hash as they not influence the
|
|
// list of entities and are only resolved after the query result has
|
|
// been retrieved
|
|
return md5( json_encode( $serialized ) );
|
|
}
|
|
|
|
/**
|
|
* @since 2.5
|
|
*
|
|
* @return string
|
|
*/
|
|
public function toString() {
|
|
return QueryStringifier::toString( $this );
|
|
}
|
|
|
|
/**
|
|
* @since 2.3
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getQueryId() {
|
|
return self::ID_PREFIX . $this->getHash();
|
|
}
|
|
|
|
}
|