summaryrefslogtreecommitdiff
path: root/www/wiki/extensions/SemanticMediaWiki/src/Elastic/QueryEngine/DescriptionInterpreters/SomePropertyInterpreter.php
diff options
context:
space:
mode:
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/src/Elastic/QueryEngine/DescriptionInterpreters/SomePropertyInterpreter.php')
-rw-r--r--www/wiki/extensions/SemanticMediaWiki/src/Elastic/QueryEngine/DescriptionInterpreters/SomePropertyInterpreter.php464
1 files changed, 464 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/src/Elastic/QueryEngine/DescriptionInterpreters/SomePropertyInterpreter.php b/www/wiki/extensions/SemanticMediaWiki/src/Elastic/QueryEngine/DescriptionInterpreters/SomePropertyInterpreter.php
new file mode 100644
index 00000000..85d0f646
--- /dev/null
+++ b/www/wiki/extensions/SemanticMediaWiki/src/Elastic/QueryEngine/DescriptionInterpreters/SomePropertyInterpreter.php
@@ -0,0 +1,464 @@
+<?php
+
+namespace SMW\Elastic\QueryEngine\DescriptionInterpreters;
+
+use Maps\Semantic\ValueDescriptions\AreaDescription;
+use SMW\DataTypeRegistry;
+use SMW\DIWikiPage;
+use SMW\Elastic\QueryEngine\ConditionBuilder;
+use SMW\Elastic\QueryEngine\Condition;
+use SMW\Elastic\QueryEngine\FieldMapper;
+use SMW\Elastic\QueryEngine\QueryBuilder;
+use SMW\Query\Language\Conjunction;
+use SMW\Query\Language\Disjunction;
+use SMW\Query\Language\SomeProperty;
+use SMW\Query\Language\ThingDescription;
+use SMW\Query\Language\ValueDescription;
+use SMW\Query\Language\ClassDescription;
+use SMW\Query\Language\NamespaceDescription;
+use SMWDataItem as DataItem;
+use SMWDIBlob as DIBlob;
+use SMWDIBoolean as DIBoolean;
+use SMWDIGeoCoord as DIGeoCoord;
+use SMWDInumber as DINumber;
+use SMWDITime as DITime;
+use SMWDIUri as DIUri;
+
+/**
+ * @license GNU GPL v2+
+ * @since 3.0
+ *
+ * @author mwjames
+ */
+class SomePropertyInterpreter {
+
+ /**
+ * @var ConditionBuilder
+ */
+ private $conditionBuilder;
+
+ /**
+ * @var FieldMapper
+ */
+ private $fieldMapper;
+
+ /**
+ * @var TermsLookup
+ */
+ private $termsLookup;
+
+ /**
+ * @since 3.0
+ *
+ * @param ConditionBuilder $conditionBuilder
+ */
+ public function __construct( ConditionBuilder $conditionBuilder ) {
+ $this->conditionBuilder = $conditionBuilder;
+ }
+
+ /**
+ * @since 3.0
+ *
+ * @param SomeProperty $description
+ *
+ * @return array
+ */
+ public function interpretDescription( SomeProperty $description, $isConjunction = false, $isChain = false ) {
+
+ // Query types
+ //
+ // - term: query matches a single term as it is, the value is not
+ // analyzed
+ // - match_phrase: query will analyze the input, all the terms must
+ // appear in the field, they must have the same order as the input
+ // value
+
+ // Bool types
+ //
+ // - must: query must appear in matching documents and will contribute
+ // to the score
+ // - filter: query must appear in matching documents, the score
+ // of the query will be ignored
+ // - should: query should appear in the matching document
+
+ $this->fieldMapper = $this->conditionBuilder->getFieldMapper();
+ $this->termsLookup = $this->conditionBuilder->getTermsLookup();
+
+ $property = $description->getProperty();
+ $pid = $this->fieldMapper->getPID( $this->conditionBuilder->getID( $property ) );
+
+ $hierarchy = $this->conditionBuilder->findHierarchyMembers(
+ $property,
+ $description->getHierarchyDepth()
+ );
+
+ $desc = $description->getDescription();
+
+ // Copy the context
+ if ( isset( $description->isPartOfDisjunction ) ) {
+ $desc->isPartOfDisjunction = true;
+ }
+
+ $field = 'wpgID';
+ $opType = Condition::TYPE_MUST;
+
+ $field = $this->fieldMapper->getField( $property, 'Field' );
+ $params = [];
+
+ // [[Foo::Bar]]
+ if ( $desc instanceof ValueDescription ) {
+ $params = $this->interpretValueDescription( $desc, $property, $pid, $field, $opType );
+ }
+
+ // [[Foo::+]]
+ if ( $desc instanceof ThingDescription ) {
+ $params = $this->interpretThingDescription( $desc, $property, $pid, $field, $opType );
+ }
+
+ if ( $params !== [] ) {
+ $params = $this->fieldMapper->hierarchy( $params, $pid, $hierarchy );
+ }
+
+ if ( $desc instanceof ClassDescription ) {
+ $params = $this->interpretClassDescription( $desc, $property, $pid, $field );
+ }
+
+ if ( $desc instanceof NamespaceDescription ) {
+ $params = $this->interpretNamespaceDescription( $desc, $property, $pid, $field );
+ }
+
+ // [[-Person:: <q>[[Person.-Has friend.Person::Andy Mars]] [[Age::>>32]]</q> ]]
+ if ( $desc instanceof Conjunction ) {
+ $params = $this->interpretConjunction( $desc, $property, $pid, $field );
+ }
+
+ // Use case: `[[Has page-2:: <q>[[Has page-1::Value 1||Value 2]]
+ // [[Has text-1::Value 1||Value 2]]</q> || <q> [[Has page-2::Value 1||Value 2]]</q> ]]`
+ if ( $desc instanceof Disjunction ) {
+ $params = $this->interpretDisjunction( $desc, $property, $pid, $field, $opType );
+ }
+
+ if ( !$params instanceof Condition ) {
+ $condition = $this->conditionBuilder->newCondition( $params );
+ } else {
+ $condition = $params;
+ }
+
+ $condition->type( $opType );
+ $condition->log( [ 'SomeProperty' => $description->getQueryString() ] );
+
+ // [[Foo.Bar::Foobar]], [[Foo.Bar::<q>[[Foo::Bar]] OR [[Fobar::Foo]]</q>]]
+ if ( $desc instanceof SomeProperty ) {
+ $condition = $this->interpretChain( $desc, $property, $pid, $field );
+ }
+
+ if ( $condition === [] ) {
+ return [];
+ }
+
+ // Build an extra condition to restore strictness by making sure
+ // the property exist on those matched entities
+ // `[[Has text::!~foo*]]` becomes `[[Has text::!~foo*]] [[Has text::+]`
+ if ( $opType === Condition::TYPE_MUST_NOT && !$desc instanceof ThingDescription ) {
+
+ // Use case: `[[Category:Q0905]] [[!Example/Q0905/1]] <q>[[Has page::123]]
+ // OR [[Has page::!ABCD]]</q>`
+ $params = [ $this->fieldMapper->exists( "$pid.$field" ), $condition ];
+ $condition = $this->conditionBuilder->newCondition( $params );
+ $condition->type( '' );
+
+ if ( $this->conditionBuilder->getOption( 'must_not.property.exists' ) ) {
+ $description->notConditionField = "$pid.$field";
+ }
+
+ // Use case: `[[Has telephone number::!~*123*]]`
+ if ( !$isConjunction ) {
+ $condition->type( 'must' );
+ }
+ }
+
+ if ( $isChain === false ) {
+ return $condition;
+ }
+
+ if ( !isset( $description->sourceChainMemberField ) ) {
+ throw new RuntimeException( "Missing `sourceChainMemberField`" );
+ }
+
+ $parameters = $this->termsLookup->newParameters(
+ [
+ 'terms_filter.field' => $description->sourceChainMemberField,
+ 'query.string' => $description->getQueryString(),
+ 'property.key' => $property->getKey(),
+ 'params' => $condition->toArray()
+ ]
+ );
+
+ $params = $this->termsLookup->lookup( 'chain', $parameters );
+ $this->conditionBuilder->addQueryInfo( $parameters->get( 'query.info' ) );
+
+ // Let it fail for a conjunction when the subquery returns empty!
+ if ( $params === [] && !isset( $desc->isPartOfDisjunction ) ) {
+ // Fail with a non existing condition to avoid a " ...
+ // query malformed, must start with start_object ..."
+ $params = $this->fieldMapper->exists( "empty.lookup_query" );
+ }
+
+ $condition = $this->conditionBuilder->newCondition( $params );
+ $condition->log( [ 'SomeProperty' => [ 'Chain' => $description->getQueryString() ] ] );
+
+ return $condition;
+ }
+
+ private function interpretDisjunction( $description, $property, $pid, $field, &$opType ) {
+
+ $p = [];
+ $opType = Condition::TYPE_SHOULD;
+
+ foreach ( $description->getDescriptions() as $desc ) {
+
+ $d = new SomeProperty(
+ $property,
+ $desc
+ );
+
+ $d->sourceChainMemberField = "$pid.wpgID";
+ $t = $this->conditionBuilder->interpretDescription( $d, true, true );
+
+ if ( $t !== [] ) {
+ $p[] = $t->toArray();
+ }
+ }
+
+ if ( $p === [] ) {
+ return [];
+ }
+
+ //$this->fieldMapper->bool( 'should', $p );
+ $condition = $this->conditionBuilder->newCondition( $p );
+
+ return $condition;
+ }
+
+ private function interpretClassDescription( $description, $property, $pid, $field ) {
+
+ $queryString = $description->getQueryString();
+ $condition = $this->conditionBuilder->interpretDescription( $description );
+
+ $parameters = $this->termsLookup->newParameters(
+ [
+ 'query.string' => $queryString,
+ 'field' => "$pid.wpgID",
+ 'params' => $condition
+ ]
+ );
+
+ $params = $this->termsLookup->lookup( 'predef', $parameters );
+ $this->conditionBuilder->addQueryInfo( $parameters->get( 'query.info' ) );
+
+ if ( $params === [] ) {
+ return [];
+ }
+
+ $condition = $this->conditionBuilder->newCondition( $params );
+ $condition->type( Condition::TYPE_MUST );
+ $condition->log( [ 'SomeProperty' => [ 'ClassDescription' => $queryString ] ] );
+
+ return $condition;
+ }
+
+ private function interpretNamespaceDescription( $description, $property, $pid, $field ) {
+
+ $queryString = $description->getQueryString();
+ $condition = $this->conditionBuilder->interpretDescription( $description );
+
+ $parameters = $this->termsLookup->newParameters(
+ [
+ 'query.string' => $queryString,
+ 'field' => "$pid.wpgID",
+ 'params' => $condition
+ ]
+ );
+
+ $params = $this->termsLookup->lookup( 'predef', $parameters );
+ $this->conditionBuilder->addQueryInfo( $parameters->get( 'query.info' ) );
+
+ if ( $params === [] ) {
+ return [];
+ }
+
+ $condition = $this->conditionBuilder->newCondition( $params );
+ $condition->type( Condition::TYPE_MUST );
+ $condition->log( [ 'SomeProperty' => [ 'NamespaceDescription' => $queryString ] ] );
+
+ return $condition;
+ }
+
+ private function interpretConjunction( $description, $property, $pid, $field ) {
+
+ $p = [];
+ $logs = [];
+ $queryString = $description->getQueryString();
+ $logs[] = $queryString;
+ $opType = Condition::TYPE_MUST;
+
+ foreach ( $description->getDescriptions() as $desc ) {
+ $params = $this->conditionBuilder->interpretDescription( $desc, true );
+
+ if ( $params !== [] ) {
+ $p[] = $params->toArray();
+ $logs = array_merge( $logs, $params->getLogs() );
+ }
+ }
+
+ if ( $p !== [] ) {
+ // We match IDs using the term lookup which is either a resource or
+ // a document field (on a txtField etc.)
+ $f = strpos( $field, 'wpg' ) !== false ? "$pid.wpgID" : "_id";
+
+ $parameters = $this->termsLookup->newParameters(
+ [
+ 'query.string' => $queryString,
+ 'field' => $f,
+ 'params' => $p
+ ]
+ );
+
+ $p = $this->termsLookup->lookup( 'predef', $parameters );
+ $this->conditionBuilder->addQueryInfo( $parameters->get( 'query.info' ) );
+ }
+
+ // Inverse matches are always resource (aka wpgID) related
+ if ( $property->isInverse() ) {
+ $parameters = $this->termsLookup->newParameters(
+ [
+ 'query.string' => $desc->getQueryString(),
+ 'property.key' => $property->getKey(),
+ 'field' => "$pid.wpgID",
+ 'params' => $this->fieldMapper->field_filter( "$pid.wpgID", $p )
+ ]
+ );
+
+ $p = $this->termsLookup->lookup( 'inverse', $parameters );
+ $this->conditionBuilder->addQueryInfo( $parameters->get( 'query.info' ) );
+ }
+
+ if ( $p === [] ) {
+ return [];
+ }
+
+ $condition = $this->conditionBuilder->newCondition( $p );
+ $condition->type( '' );
+
+ $condition->log( [ 'SomeProperty' => [ 'Conjunction' => $logs ] ] );
+
+ return $condition;
+ }
+
+ private function interpretChain( $desc, $property, $pid, $field ) {
+
+ $desc->sourceChainMemberField = "$pid.wpgID";
+ $p = [];
+
+ // Use case: `[[Category:Sample-1]][[Has page-1.Has page-2:: <q>
+ // [[Has text-1::Value 1]] OR <q>[[Has text-2::Value 2]]
+ // [[Has page-2::Value 2]]</q></q> ]]`
+ if ( $desc->getDescription() instanceof Disjunction ) {
+
+ foreach ( $desc->getDescription()->getDescriptions() as $d ) {
+ $d = new SomeProperty(
+ $desc->getProperty(),
+ $d
+ );
+ $d->setMembership( $desc->getFingerprint() );
+ $d->sourceChainMemberField = "$pid.wpgID";
+
+ if ( isset( $desc->isPartOfDisjunction ) ) {
+ $d->isPartOfDisjunction = true;
+ }
+
+ $t = $this->interpretDescription( $d, true, true );
+
+ if ( $t !== [] ) {
+ $p[] = $t->toArray();
+ }
+ }
+
+ $p = $this->fieldMapper->bool( 'should', $p );
+ } else {
+ $p = $this->interpretDescription( $desc, true, true );
+ }
+
+ if ( $property->isInverse() ) {
+ $parameters = $this->termsLookup->newParameters(
+ [
+ 'query.string' => $desc->getQueryString(),
+ 'property.key' => $property->getKey(),
+ 'field' => "$pid.wpgID",
+ 'params' => $this->fieldMapper->field_filter( "$pid.wpgID", $p->toArray() )
+ ]
+ );
+
+ $p = $this->termsLookup->lookup( 'inverse', $parameters );
+ $this->conditionBuilder->addQueryInfo( $parameters->get( 'query.info' ) );
+ }
+
+ $condition = $this->conditionBuilder->newCondition( $p );
+ $condition->type( '' );
+
+ return $condition;
+ }
+
+ private function interpretThingDescription( $desc, $property, $pid, $field, &$opType ) {
+
+ $isResourceType = false;
+
+ if ( DataTypeRegistry::getInstance()->getDataItemByType( $property->findPropertyValueType() ) === DataItem::TYPE_WIKIPAGE ) {
+ $field = 'wpgID';
+ $isResourceType = true;
+ }
+
+ // [[Has subobject::!+]] is only supported with the ElasticStore
+ $opType = isset( $desc->isNegation ) ? Condition::TYPE_MUST_NOT : Condition::TYPE_FILTER;
+ $params = $this->fieldMapper->exists( "$pid.$field" );
+
+ // Only allow to match wpg types (aka resources) to be used as
+ // invertible query element, this matches the SQLStore behaviour
+ if ( $property->isInverse() && $isResourceType ) {
+ $parameters = $this->termsLookup->newParameters(
+ [
+ 'query.string' => $desc->getQueryString(),
+ 'property.key' => $property->getKey(),
+ 'field' => "$pid.$field",
+ 'params' => ''
+ ]
+ );
+
+ $params = $this->termsLookup->lookup( 'inverse', $parameters );
+ $this->conditionBuilder->addQueryInfo( $parameters->get( 'query.info' ) );
+ }
+
+ $condition = $this->conditionBuilder->newCondition( $params );
+ $condition->type( '' );
+
+ return $condition;
+ }
+
+ private function interpretValueDescription( $desc, $property, $pid, &$field, &$type ) {
+
+ $options = [
+ 'type' => $type,
+ 'field' => $field,
+ 'pid' => $pid,
+ 'property' => $property
+ ];
+
+ $condition = $this->conditionBuilder->interpretSomeValue( $desc, $options );
+
+ $field = $options['field'];
+ $type = $options['type'];
+
+ return $condition;
+ }
+
+}