diff options
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/includes/query/SMW_Query.php')
-rw-r--r-- | www/wiki/extensions/SemanticMediaWiki/includes/query/SMW_Query.php | 552 |
1 files changed, 552 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/includes/query/SMW_Query.php b/www/wiki/extensions/SemanticMediaWiki/includes/query/SMW_Query.php new file mode 100644 index 00000000..eadf97b0 --- /dev/null +++ b/www/wiki/extensions/SemanticMediaWiki/includes/query/SMW_Query.php @@ -0,0 +1,552 @@ +<?php + +use SMW\DIWikiPage; +use SMW\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 SMWRequestOptions (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 $description + * @param integer|boolean $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( SMWDescription $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 ( !is_null( $this->description ) ) { + 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|integer $key + * @param mixed $value + */ + public function setOption( $key, $value ) { + $this->options[$key] = $value; + } + + /** + * @since 2.5 + * + * @param string|integer $key + * + * @return mixed + */ + public function getOption( $key ) { + return isset( $this->options[$key] ) ? $this->options[$key] : false; + } + + /** + * @since 1.7 + * + * @param boolean $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 ( !is_null( $this->description ) ) { + 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; + } + + 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 integer $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 ( !is_null( $this->description ) ) { + 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(); + } + +} |