jenkins-bot has submitted this change and it was merged. ( 
https://gerrit.wikimedia.org/r/345302 )

Change subject: Check Constraints with SPARQL
......................................................................


Check Constraints with SPARQL

Two new checkers are added, which check Type and Value type constraints
using SPARQL, and UniqueValueChecker is implemented, also using SPARQL.
They use a new helper class, SparqlHelper, which translates requests
into SPARQL queries and executes them.

ConstraintReportFactory associates the new checkers with the constraint
types “Type (SPARQL)” and “Value type (SPARQL)”.

A new configuration variable for the SPARQL endpoint to be used is
added, defaulting to the Wikidata Query Service.

Change-Id: I8af2e92215c026805df64babc5baf2a4ac8bc25e
---
M api/CheckConstraints.php
M extension.json
M i18n/en.json
M i18n/qqq.json
A includes/ConstraintCheck/Checker/TypeSparqlChecker.php
M includes/ConstraintCheck/Checker/UniqueValueChecker.php
A includes/ConstraintCheck/Checker/ValueTypeSparqlChecker.php
A includes/ConstraintCheck/Helper/SparqlHelper.php
A includes/ConstraintCheck/Helper/SparqlHelperException.php
M includes/ConstraintReportFactory.php
A tests/phpunit/Checker/TypeChecker/Q42.json
A tests/phpunit/Checker/TypeChecker/TypeSparqlCheckerTest.php
A tests/phpunit/Checker/TypeChecker/ValueTypeSparqlCheckerTest.php
M tests/phpunit/Checker/ValueCountChecker/UniqueValueCheckerTest.php
M tests/phpunit/DefaultConfig.php
M tests/phpunit/DelegatingConstraintCheckerTest.php
A tests/phpunit/Helper/SparqlHelperTest.php
A tests/phpunit/SparqlHelperMock.php
18 files changed, 1,110 insertions(+), 27 deletions(-)

Approvals:
  Jonas Kress (WMDE): Looks good to me, approved
  jenkins-bot: Verified



diff --git a/api/CheckConstraints.php b/api/CheckConstraints.php
index e18fbb8..c5c9d8e 100644
--- a/api/CheckConstraints.php
+++ b/api/CheckConstraints.php
@@ -116,7 +116,9 @@
                        $repo->getEntityLookup(),
                        $statementGuidParser,
                        MediaWikiServices::getInstance()->getMainConfig(),
-                       $constraintParameterRenderer
+                       $constraintParameterRenderer,
+                       $repo->getRdfVocabulary(),
+                       $repo->getEntityIdParser()
                );
 
                return new CheckConstraints( $main, $name, $prefix, 
$repo->getEntityIdParser(),
diff --git a/extension.json b/extension.json
index 176a2f7..ce0160a 100644
--- a/extension.json
+++ b/extension.json
@@ -66,6 +66,11 @@
                        "description": "Whether to import property constraint 
statements into the constraint database or not.",
                        "public": true
                },
+               "WBQualityConstraintsSparqlEndpoint": {
+                       "value": 
"https://query.wikidata.org/bigdata/namespace/wdq/sparql";,
+                       "description": "The URL of the SPARQL endpoint. Should 
accept the URL parameters 'query' and 'format'.",
+                       "public": true
+               },
                "WBQualityConstraintsInstanceOfId": {
                        "value": "P31",
                        "description": "The property ID of the 'instance of' 
property, which specifies the class(es) of an item.",
diff --git a/i18n/en.json b/i18n/en.json
index fed8913..0b4ff22 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -58,6 +58,7 @@
        "wbqc-violation-message-parameter-entity": "The value for the parameter 
\"$1\" must be an entity, not \"$2\".",
        "wbqc-violation-message-parameter-single": "The parameter \"$1\" must 
only have a single value.",
        "wbqc-violation-message-parameter-oneof": "The parameter \"$1\" must be 
{{PLURAL:$2|1=$4.|2=either $4 or $5.|one of the following:$3}}",
+       "wbqc-violation-message-sparql-error": "The SPARQL query resulted in an 
error.",
 
        "wbqc-violation-message-commons-link-no-existent": "Commons link should 
exist.",
        "wbqc-violation-message-commons-link-not-well-formed": "Commons link 
should be well-formed.",
@@ -86,5 +87,8 @@
        "wbqc-violation-message-type-subclass": "Entities using the $1 property 
should be subclasses of {{PLURAL:$3|1=$5|2=$5 or $6|one of the following 
classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a subclass of them|one of 
their subclasses}}), but $2 currently {{PLURAL:$3|1=isn't.|2=isn't.|isn't: 
$4}}",
        "wbqc-violation-message-valueType-instance": "Values of $1 statements 
should be instances of {{PLURAL:$3|1=$5|2=$5 or $6|one of the following 
classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a subclass of them|one of 
their subclasses}}), but $2 currently {{PLURAL:$3|1=isn't.|2=isn't.|isn't: 
$4}}",
        "wbqc-violation-message-valueType-subclass": "Values of $1 statements 
should be subclasses of {{PLURAL:$3|1=$5|2=$5 or $6|one of the following 
classes}} (or of {{PLURAL:$3|1=a subclass of it|2=a subclass of them|one of 
their subclasses}}), but $2 currently {{PLURAL:$3|1=isn't.|2=isn't.|isn't: 
$4}}",
-       "wbqc-violation-message-target-required-claim": "$1 should have 
{{PLURAL:$3|0=a statement $2.|1=a statement $2 $5.|a statement for $2 with one 
of the following values:$4}}"
+       "wbqc-violation-message-target-required-claim": "$1 should have 
{{PLURAL:$3|0=a statement $2.|1=a statement $2 $5.|a statement for $2 with one 
of the following values:$4}}",
+       "wbqc-violation-message-unique-value": "This property's value must not 
be present on any other item.",
+       "wbqc-violation-message-sparql-type": "This property must only be used 
on items that are in the relation to the item (or a subclass of the item) 
defined in the parameters.",
+       "wbqc-violation-message-sparql-value-type": "This property's value 
entity must be in the relation to the item (or a subclass of the item) defined 
in the parameters."
 }
diff --git a/i18n/qqq.json b/i18n/qqq.json
index 84a07f8..2ed9efd 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -54,6 +54,7 @@
        "wbqc-violation-message-parameter-entity": "Message for when the value 
of a constraint parameter must be an entity, but is some other kind of data 
value. $1 contains the parameter, $2 the data value type.",
        "wbqc-violation-message-parameter-single": "Message for when a 
constraint parameter has multiple values but only supports one. $1 contains the 
parameter.",
        "wbqc-violation-message-parameter-oneof": "Message for when a 
constraint parameter must be one of several values, but is something different. 
$1 contains the parameter, $2 the number of allowed values (possibly 1), $3 an 
HTML list of all allowed values, and $4, $5 etc. are the individual allowed 
values.{{Related|wbqc-violation-message-one-of}}",
+       "wbqc-violation-message-sparql-error": "Message for when a constraint 
checker runs a SPARQL query but the SPARQL endpoint returns an error instead of 
query results.",
        "wbqc-violation-message-commons-link-no-existent": "Message for 
violation of Commons link constraint. When linked commons page does not exist.",
        "wbqc-violation-message-commons-link-not-well-formed": "Message for 
violation of Commons link constraint. When link contains invalid characters.",
        
"wbqc-violation-message-commons-link-check-for-namespace-not-yet-implemented": 
"Message for when the check for the Commons link constraint has not yet been 
implemented for a specific namespace.",
@@ -81,5 +82,8 @@
        "wbqc-violation-message-type-subclass": "Message for a violation of the 
“Type” constraint, when the subject of a statement should have be a subclass of 
a certain type but isn't. $1 is the property of the statement, $2 is the 
subject of the statement, $3 is the number of classes, $4 is an HTML list of 
all classes, and $5, $6 etc. are the individual 
classes.{{Related|wbqc-violation-message-type-instance}}",
        "wbqc-violation-message-valueType-instance": "Message for a violation 
of the “Value type” constraint, when the value of a statement should have be an 
instance of a certain type but isn't. $1 is the property of the statement, $2 
is the value of the statement, $3 is the number of classes, $4 is an HTML list 
of all classes, and $5, $6 etc. are the individual 
classes.{{Related|wbqc-violation-message-valueType-subclass}}",
        "wbqc-violation-message-valueType-subclass": "Message for a violation 
of the “Value type” constraint, when the value of a statement should have be a 
subclass of a certain type but isn't. $1 is the property of the statement, $2 
is the value of the statement, $3 is the number of classes, $4 is an HTML list 
of all classes, and $5, $6 etc. are the individual 
classes.{{Related|wbqc-violation-message-valueType-instance}}",
-       "wbqc-violation-message-target-required-claim": "Message for a 
violation of the “Target required claim” constraint, when the target entity of 
a statement is missing an expected statement. Parameters:\n* $1 is the subject 
entity of the missing statement, i. e. the target entity of the statement that 
has the constraint.\n* $2 is the property of the missing statement.\n* $3 is 
the number of values permitted for the missing statement (or 0, in which case 
the constraint only specifies that there should be a statement but not the 
values it should have).\n* $4 is an HTML list of all values permitted for the 
missing statement.\n* $5, $6 etc. are the individual values permitted for the 
missing statement.\n{{Related|wbqc-violation-message-item}}"
+       "wbqc-violation-message-target-required-claim": "Message for a 
violation of the “Target required claim” constraint, when the target entity of 
a statement is missing an expected statement. Parameters:\n* $1 is the subject 
entity of the missing statement, i. e. the target entity of the statement that 
has the constraint.\n* $2 is the property of the missing statement.\n* $3 is 
the number of values permitted for the missing statement (or 0, in which case 
the constraint only specifies that there should be a statement but not the 
values it should have).\n* $4 is an HTML list of all values permitted for the 
missing statement.\n* $5, $6 etc. are the individual values permitted for the 
missing statement.\n{{Related|wbqc-violation-message-item}}",
+       "wbqc-violation-message-unique-value": "Message for violation of the 
Unique Value constraint, when other items are found.",
+       "wbqc-violation-message-sparql-type": "{{notranslate}}",
+       "wbqc-violation-message-sparql-value-type": "{{notranslate}}"
 }
diff --git a/includes/ConstraintCheck/Checker/TypeSparqlChecker.php 
b/includes/ConstraintCheck/Checker/TypeSparqlChecker.php
new file mode 100644
index 0000000..129d1ec
--- /dev/null
+++ b/includes/ConstraintCheck/Checker/TypeSparqlChecker.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker;
+
+use Wikibase\DataModel\Entity\EntityDocument;
+use Wikibase\DataModel\Services\Lookup\EntityLookup;
+use Wikibase\DataModel\Statement\StatementListProvider;
+use WikibaseQuality\ConstraintReport\Constraint;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelperException;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
+use Wikibase\DataModel\Statement\Statement;
+
+/**
+ * @package WikibaseQuality\ConstraintReport\ConstraintCheck\Checker
+ * @author BP2014N1
+ * @license GNU GPL v2+
+ */
+class TypeSparqlChecker implements ConstraintChecker {
+
+       /**
+        * @var ConstraintParameterParser
+        */
+       private $helper;
+
+       /**
+        * @var EntityLookup
+        */
+       private $entityLookup;
+
+       /**
+        * @var SparqlHelper
+        */
+       private $sparqlHelper;
+
+       /**
+        * @param EntityLookup $lookup
+        * @param ConstraintParameterParser $helper
+        * @param SparqlHelper $sparqlHelper
+        */
+       public function __construct( EntityLookup $lookup, 
ConstraintParameterParser $helper, SparqlHelper $sparqlHelper ) {
+               $this->entityLookup = $lookup;
+               $this->helper = $helper;
+               $this->sparqlHelper = $sparqlHelper;
+       }
+
+       /**
+        * Checks 'Type' constraint.
+        *
+        * @param Statement $statement
+        * @param Constraint $constraint
+        * @param EntityDocument|StatementListProvider $entity
+        *
+        * @return CheckResult
+        */
+       public function checkConstraint( Statement $statement, Constraint 
$constraint, EntityDocument $entity = null ) {
+               $parameters = [];
+               $constraintParameters = $constraint->getConstraintParameters();
+
+               $classes = false;
+               if ( array_key_exists( 'class', $constraintParameters ) ) {
+                       $classes = explode( ',', $constraintParameters['class'] 
);
+                       $parameters['class'] = 
$this->helper->parseParameterArray( $classes );
+               }
+
+               $relation = false;
+               if ( array_key_exists( 'relation', $constraintParameters ) ) {
+                       $relation = $constraintParameters['relation'];
+                       $parameters['relation'] = 
$this->helper->parseSingleParameter( $relation, true );
+               }
+
+               /*
+                * error handling:
+                *   parameter $constraintParameters['class'] must not be null
+                */
+               if ( !$classes ) {
+                       $message = wfMessage( 
"wbqc-violation-message-parameter-needed" )->params( 
$constraint->getConstraintTypeName(), 'class' )->escaped();
+                       return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, CheckResult::STATUS_VIOLATION, $message );
+               }
+
+               /*
+                * error handling:
+                *   parameter $constraintParameters['relation'] must be either 
'instance' or 'subclass'
+                */
+               if ( $relation === 'instance' ) {
+                       $withInstance = true;
+               } elseif ( $relation === 'subclass' ) {
+                       $withInstance = false;
+               } else {
+                       $message = wfMessage( 
"wbqc-violation-message-type-relation-instance-or-subclass" )->escaped();
+                       return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, CheckResult::STATUS_VIOLATION, $message );
+               }
+
+               try {
+                       if ( $this->sparqlHelper->hasType( 
$entity->getId()->getSerialization(), $classes, $withInstance ) ) {
+                               $message = '';
+                               $status = CheckResult::STATUS_COMPLIANCE;
+                       } else {
+                               $message = wfMessage( 
"wbqc-violation-message-sparql-type" )->escaped();
+                               $status = CheckResult::STATUS_VIOLATION;
+                       }
+               } catch ( SparqlHelperException $e ) {
+                       $status = CheckResult::STATUS_VIOLATION;
+                       $message = wfMessage( 
'wbqc-violation-message-sparql-error' )->escaped();
+               }
+
+               return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, $status, $message );
+       }
+
+}
diff --git a/includes/ConstraintCheck/Checker/UniqueValueChecker.php 
b/includes/ConstraintCheck/Checker/UniqueValueChecker.php
index 907b68e..789f4fd 100644
--- a/includes/ConstraintCheck/Checker/UniqueValueChecker.php
+++ b/includes/ConstraintCheck/Checker/UniqueValueChecker.php
@@ -6,7 +6,8 @@
 use Wikibase\DataModel\Statement\StatementListProvider;
 use WikibaseQuality\ConstraintReport\Constraint;
 use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
-use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ValueCountCheckerHelper;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelperException;
 use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
 use Wikibase\DataModel\Statement\Statement;
 
@@ -18,18 +19,16 @@
 class UniqueValueChecker implements ConstraintChecker {
 
        /**
-        * @var ValueCountCheckerHelper
+        * @var SparqlHelper
         */
-       private $valueCountCheckerHelper;
+       private $sparqlHelper;
 
-       public function __construct() {
-               $this->valueCountCheckerHelper = new ValueCountCheckerHelper();
+       public function __construct( SparqlHelper $sparqlHelper ) {
+               $this->sparqlHelper = $sparqlHelper;
        }
 
        /**
         * Checks 'Unique value' constraint.
-        *
-        * @todo Implement when index exists that makes it possible in 
reasonable time.
         *
         * @param Statement $statement
         * @param Constraint $constraint
@@ -40,8 +39,23 @@
        public function checkConstraint( Statement $statement, Constraint 
$constraint, EntityDocument $entity ) {
                $parameters = [];
 
-               $message = wfMessage( 
"wbqc-violation-message-not-yet-implemented" )->params( 
$constraint->getConstraintTypeName() )->escaped();
-               return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, CheckResult::STATUS_TODO, $message );
+               try {
+                       $otherEntities = 
$this->sparqlHelper->findEntitiesWithSameStatement( $statement );
+
+                       if ( $otherEntities === [] ) {
+                               $status = CheckResult::STATUS_COMPLIANCE;
+                               $message = '';
+                       } else {
+                               $status = CheckResult::STATUS_VIOLATION;
+                               // TODO include the other entities in the 
message
+                               $message = wfMessage( 
'wbqc-violation-message-unique-value' )->escaped();
+                       }
+               } catch ( SparqlHelperException $e ) {
+                       $status = CheckResult::STATUS_VIOLATION;
+                       $message = wfMessage( 
'wbqc-violation-message-sparql-error' )->escaped();
+               }
+
+               return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, $status, $message );
        }
 
 }
diff --git a/includes/ConstraintCheck/Checker/ValueTypeSparqlChecker.php 
b/includes/ConstraintCheck/Checker/ValueTypeSparqlChecker.php
new file mode 100644
index 0000000..0a2c8b3
--- /dev/null
+++ b/includes/ConstraintCheck/Checker/ValueTypeSparqlChecker.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Checker;
+
+use Wikibase\DataModel\Entity\EntityDocument;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Services\Lookup\EntityLookup;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\DataModel\Statement\StatementListProvider;
+use WikibaseQuality\ConstraintReport\Constraint;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelperException;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult;
+use Wikibase\DataModel\Statement\Statement;
+
+/**
+ * @package WikibaseQuality\ConstraintReport\ConstraintCheck\Checker
+ * @author BP2014N1
+ * @license GNU GPL v2+
+ */
+class ValueTypeSparqlChecker implements ConstraintChecker {
+
+       /**
+        * @var ConstraintParameterParser
+        */
+       private $helper;
+
+       /**
+        * @var EntityLookup
+        */
+       private $entityLookup;
+
+       /**
+        * @var SparqlHelper
+        */
+       private $sparqlHelper;
+
+       /**
+        * @param EntityLookup $lookup
+        * @param ConstraintParameterParser $helper
+        * @param SparqlHelper $sparqlHelper
+        */
+       public function __construct( EntityLookup $lookup, 
ConstraintParameterParser $helper, SparqlHelper $sparqlHelper ) {
+               $this->entityLookup = $lookup;
+               $this->helper = $helper;
+               $this->sparqlHelper = $sparqlHelper;
+       }
+
+       /**
+        * Checks 'Value type' constraint.
+        *
+        * @param Statement $statement
+        * @param Constraint $constraint
+        * @param EntityDocument|StatementListProvider $entity
+        *
+        * @return CheckResult
+        */
+       public function checkConstraint( Statement $statement, Constraint 
$constraint, EntityDocument $entity = null ) {
+               $parameters = [];
+               $constraintParameters = $constraint->getConstraintParameters();
+
+               $classes = false;
+               if ( array_key_exists( 'class', $constraintParameters ) ) {
+                       $classes = explode( ',', $constraintParameters['class'] 
);
+                       $parameters['class'] = 
$this->helper->parseParameterArray( $classes );
+               }
+
+               $relation = false;
+               if ( array_key_exists( 'relation', $constraintParameters ) ) {
+                       $relation = $constraintParameters['relation'];
+                       $parameters['relation'] = 
$this->helper->parseSingleParameter( $relation, true );
+               }
+
+               if ( array_key_exists( 'constraint_status', 
$constraintParameters ) ) {
+                       $parameters['constraint_status'] = 
$this->helper->parseSingleParameter( 
$constraintParameters['constraint_status'], true );
+               }
+
+               $mainSnak = $statement->getMainSnak();
+
+               /*
+                * error handling:
+                *   $mainSnak must be PropertyValueSnak, neither 
PropertySomeValueSnak nor PropertyNoValueSnak is allowed
+                */
+               if ( !$mainSnak instanceof PropertyValueSnak ) {
+                       $message = wfMessage( 
"wbqc-violation-message-value-needed" )->params( 
$constraint->getConstraintTypeQid() )->escaped();
+                       return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, CheckResult::STATUS_VIOLATION, $message );
+               }
+
+               $dataValue = $mainSnak->getDataValue();
+
+               /*
+                * error handling:
+                *   type of $dataValue for properties with 'Value type' 
constraint has to be 'wikibase-entityid'
+                *   parameter $constraintParameters['class']  must not be null
+                */
+               if ( $dataValue->getType() !== 'wikibase-entityid' ) {
+                       $message = wfMessage( 
"wbqc-violation-message-value-needed-of-type" )->params( 
$constraint->getConstraintTypeQid(), 'wikibase-entityid' )->escaped();
+                       return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, CheckResult::STATUS_VIOLATION, $message );
+               }
+               /** @var EntityIdValue $dataValue */
+
+               if ( !$classes ) {
+                       $message = wfMessage( 
"wbqc-violation-message-parameter-needed" )->params( 
$constraint->getConstraintTypeQid(), 'class' )->escaped();
+                       return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, CheckResult::STATUS_VIOLATION, $message );
+               }
+
+               /*
+                * error handling:
+                *   parameter $constraintParameters['relation'] must be either 
'instance' or 'subclass'
+                */
+               if ( $relation === 'instance' ) {
+                       $withInstance = true;
+               } elseif ( $relation === 'subclass' ) {
+                       $withInstance = false;
+               } else {
+                       $message = wfMessage( 
"wbqc-violation-message-type-relation-instance-or-subclass" )->escaped();
+                       return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, CheckResult::STATUS_VIOLATION, $message );
+               }
+
+               $item = $this->entityLookup->getEntity( 
$dataValue->getEntityId() );
+
+               if ( !( $item instanceof StatementListProvider ) ) {
+                       $message = wfMessage( 
"wbqc-violation-message-value-entity-must-exist" )->escaped();
+                       return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, CheckResult::STATUS_VIOLATION, $message );
+               }
+
+               try {
+                       if ( $this->sparqlHelper->hasType( 
$dataValue->getEntityId()->getSerialization(), $classes, $withInstance ) ) {
+                               $message = '';
+                               $status = CheckResult::STATUS_COMPLIANCE;
+                       } else {
+                               $message = wfMessage( 
"wbqc-violation-message-sparql-value-type" )->escaped();
+                               $status = CheckResult::STATUS_VIOLATION;
+                       }
+               } catch ( SparqlHelperException $e ) {
+                       $status = CheckResult::STATUS_VIOLATION;
+                       $message = wfMessage( 
'wbqc-violation-message-sparql-error' )->escaped();
+               }
+
+               return new CheckResult( $entity->getId(), $statement, 
$constraint->getConstraintTypeQid(), $constraint->getConstraintId(), 
$parameters, $status, $message );
+       }
+
+}
diff --git a/includes/ConstraintCheck/Helper/SparqlHelper.php 
b/includes/ConstraintCheck/Helper/SparqlHelper.php
new file mode 100644
index 0000000..8060342
--- /dev/null
+++ b/includes/ConstraintCheck/Helper/SparqlHelper.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Helper;
+
+use Config;
+use Http;
+use MediaWiki\MediaWikiServices;
+use Wikibase\DataModel\Entity\EntityIdParser;
+use Wikibase\DataModel\Statement\Statement;
+use Wikibase\Rdf\RdfVocabulary;
+
+/**
+ * Class for running a SPARQL query on some endpoint and getting the results.
+ *
+ * @package WikibaseQuality\ConstraintReport\ConstraintCheck\Helper
+ * @author Lucas Werkmeister
+ * @license GNU GPL v2+
+ */
+class SparqlHelper {
+
+       const MAX_QUERY_SECONDS = 10;
+
+       /**
+        * @var Config
+        */
+       private $config;
+
+       /**
+        * @var string
+        */
+       private $entityPrefix;
+
+       /**
+        * @var string
+        */
+       private $prefixes;
+
+       /**
+        * @var EntityIdParser
+        */
+       private $entityIdParser;
+
+       public function __construct(
+               Config $config,
+               RdfVocabulary $rdfVocabulary,
+               EntityIdParser $entityIdParser
+       ) {
+               $this->config = $config;
+               $this->entityIdParser = $entityIdParser;
+
+               $this->entityPrefix = $rdfVocabulary->getNamespaceUri( 
RdfVocabulary::NS_ENTITY );
+               $this->prefixes = <<<EOT
+PREFIX wd: <{$rdfVocabulary->getNamespaceUri( RdfVocabulary::NS_ENTITY )}>
+PREFIX wds: <{$rdfVocabulary->getNamespaceUri( RdfVocabulary::NS_STATEMENT )}>
+PREFIX wdt: <{$rdfVocabulary->getNamespaceUri( RdfVocabulary::NSP_DIRECT_CLAIM 
)}>
+PREFIX p: <{$rdfVocabulary->getNamespaceUri( RdfVocabulary::NSP_CLAIM )}>
+PREFIX ps: <{$rdfVocabulary->getNamespaceUri( 
RdfVocabulary::NSP_CLAIM_STATEMENT )}>
+EOT;
+       }
+
+       /**
+        * @param string $id entity ID serialization of the entity to check
+        * @param string[] $classes entity ID serializations of the expected 
types
+        * @param boolean $withInstance true for “instance” relation, false for 
“subclass” relation
+        * @return boolean
+        */
+       public function hasType( $id, array $classes, $withInstance ) {
+               $instanceOfId = $this->config->get( 
'WBQualityConstraintsInstanceOfId' );
+               $subclassOfId = $this->config->get( 
'WBQualityConstraintsSubclassOfId' );
+
+               $path = ( $withInstance ? "wdt:$instanceOfId/" : "" ) . 
"wdt:$subclassOfId*";
+
+               foreach ( array_chunk( $classes, 20 ) as $classesChunk ) {
+                       $classesValues = implode( ' ', array_map(
+                               function( $class ) {
+                                       return 'wd:' . $class;
+                               },
+                               $classesChunk
+                       ) );
+
+                       $query = <<<EOF
+ASK {
+  BIND(wd:$id AS ?item)
+  VALUES ?class { $classesValues }
+  ?item $path ?class.
+}
+EOF;
+
+                       $result = $this->runQuery( $query );
+                       if ( $result['boolean'] ) {
+                               return true;
+                       }
+               }
+
+               return false;
+       }
+
+       /**
+        * @param Statement $statement
+        * @return EntityId?[]
+        */
+       public function findEntitiesWithSameStatement( Statement $statement ) {
+               $pid = $statement->getPropertyId()->serialize();
+               $guid = str_replace( '$', '-', $statement->getGuid() );
+
+               $query = <<<EOF
+SELECT ?otherEntity WHERE {
+  BIND(wds:$guid AS ?statement)
+  BIND(p:$pid AS ?p)
+  BIND(ps:$pid AS ?ps)
+  ?entity ?p ?statement.
+  ?statement ?ps ?value.
+  ?otherStatement ?ps ?value.
+  ?otherEntity ?p ?otherStatement.
+  FILTER(?otherEntity != ?entity)
+}
+LIMIT 10
+EOF;
+
+               $result = $this->runQuery( $query );
+
+               return array_map(
+                       function( $resultBindings ) {
+                               $entityIRI = 
$resultBindings['otherEntity']['value'];
+                               $entityPrefixLength = strlen( 
$this->entityPrefix );
+                               if ( substr( $entityIRI, 0, $entityPrefixLength 
) === $this->entityPrefix ) {
+                                       try {
+                                               return 
$this->entityIdParser->parse( substr( $entityIRI, $entityPrefixLength ) );
+                                       } catch ( EntityIdParsingException $e ) 
{
+                                               // fall through
+                                       }
+                               }
+                               return null;
+                       },
+                       $result['results']['bindings']
+               );
+       }
+
+       /**
+        * Runs a query against the configured endpoint and returns the results.
+        *
+        * @param string $query The query, unencoded (plain string).
+        *
+        * @return array The returned JSON data (you typically iterate over 
["results"]["bindings"]).
+        */
+       public function runQuery( $query ) {
+
+               $endpoint = $this->config->get( 
'WBQualityConstraintsSparqlEndpoint' );
+               $url = $endpoint . '?' . http_build_query(
+                       [
+                               'query' => "#wbqc\n" . $this->prefixes . $query,
+                               'format' => 'json',
+                               'maxQueryTimeMillis' => self::MAX_QUERY_SECONDS 
* 1000,
+                       ],
+                       null, ini_get( 'arg_separator.output' ),
+                       // encode spaces with %20, not +
+                       PHP_QUERY_RFC3986
+               );
+
+               $json = Http::get(
+                       $url,
+                       [
+                               'timeout' => self::MAX_QUERY_SECONDS + 1,
+                       ]
+               );
+
+               if ( $json === false ) {
+                       throw new SparqlHelperException();
+               }
+               $arr = json_decode( $json, true );
+               return $arr;
+       }
+
+}
diff --git a/includes/ConstraintCheck/Helper/SparqlHelperException.php 
b/includes/ConstraintCheck/Helper/SparqlHelperException.php
new file mode 100644
index 0000000..fe36a7f
--- /dev/null
+++ b/includes/ConstraintCheck/Helper/SparqlHelperException.php
@@ -0,0 +1,16 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\ConstraintCheck\Helper;
+
+use RuntimeException;
+
+/**
+ * @license GPL-2.0+
+ */
+class SparqlHelperException extends RuntimeException {
+
+       public function __construct() {
+               parent::__construct( 'The SPARQL query endpoint returned an 
error.' );
+       }
+
+}
diff --git a/includes/ConstraintReportFactory.php 
b/includes/ConstraintReportFactory.php
index 871c9ca..a1823c0 100644
--- a/includes/ConstraintReportFactory.php
+++ b/includes/ConstraintReportFactory.php
@@ -5,8 +5,10 @@
 use Config;
 use MediaWiki\MediaWikiServices;
 use ValueFormatters\FormatterOptions;
+use Wikibase\DataModel\Entity\EntityIdParser;
 use Wikibase\DataModel\Services\Lookup\EntityLookup;
 use Wikibase\Lib\SnakFormatter;
+use Wikibase\Rdf\RdfVocabulary;
 use Wikibase\Repo\WikibaseRepo;
 use 
WikibaseQuality\ConstraintReport\ConstraintCheck\DelegatingConstraintChecker;
 use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
@@ -28,10 +30,14 @@
 use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\SingleValueChecker;
 use WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\MultiValueChecker;
 use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\UniqueValueChecker;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\TypeSparqlChecker;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\ValueTypeSparqlChecker;
 use WikibaseQuality\ConstraintReport\ConstraintCheck\ConstraintChecker;
 use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConnectionCheckerHelper;
 use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\RangeCheckerHelper;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
 use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\TypeCheckerHelper;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\TypeCheckerSparqlHelper;
 use Wikibase\DataModel\Services\Statement\StatementGuidParser;
 
 class ConstraintReportFactory {
@@ -77,6 +83,16 @@
        private $constraintParameterRenderer;
 
        /**
+        * @var RdfVocabulary
+        */
+       private $rdfVocabulary;
+
+       /**
+        * @var EntityIdParser
+        */
+       private $entityIdParser;
+
+       /**
         * Returns the default instance.
         * IMPORTANT: Use only when it is not feasible to inject an instance 
properly.
         *
@@ -102,7 +118,9 @@
                                                SnakFormatter::FORMAT_HTML,
                                                new FormatterOptions()
                                        )
-                               )
+                               ),
+                               $wikibaseRepo->getRdfVocabulary(),
+                               $wikibaseRepo->getEntityIdParser()
                        );
                }
 
@@ -113,12 +131,16 @@
                EntityLookup $lookup,
                StatementGuidParser $statementGuidParser,
                Config $config,
-               ConstraintParameterRenderer $constraintParameterRenderer
+               ConstraintParameterRenderer $constraintParameterRenderer,
+               RdfVocabulary $rdfVocabulary,
+               EntityIdParser $entityIdParser
        ) {
                $this->lookup = $lookup;
                $this->statementGuidParser = $statementGuidParser;
                $this->config = $config;
                $this->constraintParameterRenderer = 
$constraintParameterRenderer;
+               $this->rdfVocabulary = $rdfVocabulary;
+               $this->entityIdParser = $entityIdParser;
        }
 
        /**
@@ -145,6 +167,11 @@
                        $connectionCheckerHelper = new 
ConnectionCheckerHelper();
                        $rangeCheckerHelper = new RangeCheckerHelper();
                        $typeCheckerHelper = new TypeCheckerHelper( 
$this->lookup, $this->config, $this->constraintParameterRenderer );
+                       $sparqlHelper = new SparqlHelper(
+                               $this->config,
+                               $this->rdfVocabulary,
+                               $this->entityIdParser
+                       );
 
                        $this->constraintCheckerMap = [
                                'Conflicts with' => new ConflictsWithChecker( 
$this->lookup, $constraintParameterParser, $connectionCheckerHelper, 
$this->constraintParameterRenderer ),
@@ -161,10 +188,12 @@
                                'Value type' => new ValueTypeChecker( 
$this->lookup, $constraintParameterParser, $typeCheckerHelper, $this->config ),
                                'Single value' => new SingleValueChecker(),
                                'Multi value' => new MultiValueChecker(),
-                               'Unique value' => new UniqueValueChecker(),
+                               'Unique value' => new UniqueValueChecker( 
$sparqlHelper ),
                                'Format' => new FormatChecker( 
$constraintParameterParser ),
                                'Commons link' => new CommonsLinkChecker( 
$constraintParameterParser ),
                                'One of' => new OneOfChecker( 
$constraintParameterParser, $this->constraintParameterRenderer ),
+                               'Type (SPARQL)' => new TypeSparqlChecker( 
$this->lookup, $constraintParameterParser, $sparqlHelper ),
+                               'Value type (SPARQL)' => new 
ValueTypeSparqlChecker( $this->lookup, $constraintParameterParser, 
$sparqlHelper ),
                        ];
                        $this->constraintCheckerMap += [
                                $this->config->get( 
'WBQualityConstraintsDistinctValuesConstraintId' ) => 
$this->constraintCheckerMap['Unique value'],
diff --git a/tests/phpunit/Checker/TypeChecker/Q42.json 
b/tests/phpunit/Checker/TypeChecker/Q42.json
new file mode 100644
index 0000000..5dd13d5
--- /dev/null
+++ b/tests/phpunit/Checker/TypeChecker/Q42.json
@@ -0,0 +1,61 @@
+{
+  "pageid": 2076,
+  "ns": 0,
+  "title": "Q42",
+  "lastrevid": 11029,
+  "modified": "2015-04-01T13:51:03Z",
+  "id": "Q42",
+  "type": "item",
+  "labels": {
+    "en": {
+      "language": "en",
+      "value": "TestItemType"
+    }
+  },
+  "descriptions": {
+    "en": {
+      "language": "en",
+      "value": "Used for tests"
+    }
+  },
+  "claims": {
+    "P31": [
+      {
+        "id": "Q1$56e6a474-4431-fb24-cc15-1d580e467559",
+        "mainsnak": {
+          "snaktype": "value",
+          "property": "P31",
+          "datatype": "wikibase-item",
+          "datavalue": {
+            "value": {
+              "entity-type": "item",
+              "numeric-id": 100
+            },
+            "type": "wikibase-entityid"
+          }
+        },
+        "type": "statement",
+        "rank": "normal"
+      }
+    ],
+    "P279": [
+      {
+        "id": "Q1$e35707be-4a84-61fe-9b52-623784a316a7",
+        "mainsnak": {
+          "snaktype": "value",
+          "property": "P279",
+          "datatype": "wikibase-item",
+          "datavalue": {
+            "value": {
+              "entity-type": "item",
+              "numeric-id": 24
+            },
+            "type": "wikibase-entityid"
+          }
+        },
+        "type": "statement",
+        "rank": "normal"
+      }
+    ]
+  }
+}
\ No newline at end of file
diff --git a/tests/phpunit/Checker/TypeChecker/TypeSparqlCheckerTest.php 
b/tests/phpunit/Checker/TypeChecker/TypeSparqlCheckerTest.php
new file mode 100644
index 0000000..7a72315
--- /dev/null
+++ b/tests/phpunit/Checker/TypeChecker/TypeSparqlCheckerTest.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\Test\TypeChecker;
+
+use PHPUnit_Framework_TestCase;
+use Wikibase\DataModel\Statement\Statement;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\PropertyId;
+use WikibaseQuality\ConstraintReport\Constraint;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\TypeSparqlChecker;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
+use WikibaseQuality\ConstraintReport\Tests\DefaultConfig;
+use WikibaseQuality\ConstraintReport\Tests\ResultAssertions;
+use WikibaseQuality\ConstraintReport\Tests\SparqlHelperMock;
+use WikibaseQuality\Tests\Helper\JsonFileEntityLookup;
+
+/**
+ * @covers WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\TypeChecker
+ *
+ * @group WikibaseQualityConstraints
+ *
+ * @uses   WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult
+ * @uses   
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser
+ *
+ * @author Olga Bode
+ * @license GNU GPL v2+
+ */
+class TypeSparqlCheckerTest extends \PHPUnit_Framework_TestCase  {
+
+       use DefaultConfig, ResultAssertions, SparqlHelperMock;
+
+       /**
+        * @var JsonFileEntityLookup
+        */
+       private $lookup;
+
+       /**
+        * @var TypeSparqlChecker
+        */
+       private $checker;
+
+       /**
+        * @var Statement
+        */
+       private $typeStatement;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->lookup = new JsonFileEntityLookup( __DIR__ );
+
+               $this->typeStatement = new Statement( new PropertyValueSnak( 
new PropertyId( 'P1' ), new EntityIdValue( new ItemId( 'Q42' ) ) ) );
+       }
+
+       protected function tearDown() {
+               unset( $this->lookup );
+               unset( $this->typeStatement );
+               parent::tearDown();
+       }
+
+       // relation 'subclass'
+
+       public function testTypeConstraintSubclassValid() {
+               $mock = $this->getSparqlHelperMockHasType( 'Q1', [ 'Q100', 
'Q101' ], false, true );
+
+               $this->checker = new TypeSparqlChecker(
+                       $this->lookup,
+                       new ConstraintParameterParser(),
+                       $mock
+               );
+
+               $entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
+
+               $constraintParameters = [
+                       'class' => 'Q100,Q101',
+                       'relation' => 'subclass'
+               ];
+
+               $checkResult = $this->checker->checkConstraint( 
$this->typeStatement, $this->getConstraintMock( $constraintParameters ), 
$entity );
+               $this->assertCompliance( $checkResult );
+       }
+
+       // relation 'subclass', violations
+
+       public function testTypeConstraintSubclassIsInvalid() {
+               $mock = $this->getSparqlHelperMockHasType( 'Q1', [ 'Q200', 
'Q201' ], false, false );
+
+               $this->checker = new TypeSparqlChecker(
+                       $this->lookup,
+                       new ConstraintParameterParser(),
+                       $mock
+               );
+
+               $entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
+
+               $constraintParameters = [
+                       'class' => 'Q200,Q201',
+                       'relation' => 'subclass'
+               ];
+
+               $checkResult = $this->checker->checkConstraint( 
$this->typeStatement, $this->getConstraintMock( $constraintParameters ), 
$entity );
+               $this->assertViolation( $checkResult, 
'wbqc-violation-message-sparql-type' );
+       }
+
+       /**
+        * @param string[] $parameters
+        *
+        * @return Constraint
+        */
+       private function getConstraintMock( array $parameters ) {
+               $mock = $this
+                         ->getMockBuilder( Constraint::class )
+                         ->disableOriginalConstructor()
+                         ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'getConstraintParameters' )
+                       ->will( $this->returnValue( $parameters ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getConstraintTypeQid' )
+                       ->will( $this->returnValue( 'Type' ) );
+
+               return $mock;
+       }
+
+}
diff --git a/tests/phpunit/Checker/TypeChecker/ValueTypeSparqlCheckerTest.php 
b/tests/phpunit/Checker/TypeChecker/ValueTypeSparqlCheckerTest.php
new file mode 100644
index 0000000..9db5b74
--- /dev/null
+++ b/tests/phpunit/Checker/TypeChecker/ValueTypeSparqlCheckerTest.php
@@ -0,0 +1,178 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\Test\Checker;
+
+use PHPUnit_Framework_TestCase;
+use Wikibase\DataModel\Statement\Statement;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\PropertyId;
+use WikibaseQuality\ConstraintReport\Constraint;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\ValueTypeSparqlChecker;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
+use WikibaseQuality\ConstraintReport\Tests\DefaultConfig;
+use WikibaseQuality\ConstraintReport\Tests\ResultAssertions;
+use WikibaseQuality\ConstraintReport\Tests\SparqlHelperMock;
+use WikibaseQuality\Tests\Helper\JsonFileEntityLookup;
+
+/**
+ * @covers WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\TypeChecker
+ *
+ * @group WikibaseQualityConstraints
+ *
+ * @uses   WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult
+ * @uses   
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser
+ *
+ * @author Olga Bode
+ * @license GNU GPL v2+
+ */
+class ValueTypeCheckerSparqlTest extends \PHPUnit_Framework_TestCase  {
+
+       use DefaultConfig, ResultAssertions, SparqlHelperMock;
+
+       /**
+        * @var JsonFileEntityLookup
+        */
+       private $lookup;
+
+       /**
+        * @var ValueTypeSparqlChecker
+        */
+       private $checker;
+
+       /**
+        * @var PropertyId
+        */
+       private $valueTypePropertyId;
+
+       /**
+        * @var Statement
+        */
+       private $typeStatement;
+
+       protected function setUp() {
+               parent::setUp();
+
+               $this->lookup = new JsonFileEntityLookup( __DIR__ );
+               $this->valueTypePropertyId = new PropertyId( 'P1234' );
+               $this->typeStatement = new Statement( new PropertyValueSnak( 
new PropertyId( 'P1' ), new EntityIdValue( new ItemId( 'Q42' ) ) ) );
+       }
+
+       protected function tearDown() {
+               unset( $this->lookup );
+               unset( $this->typeStatement );
+               unset( $this->valueTypePropertyId );
+               parent::tearDown();
+       }
+
+       // relation 'subclass'
+
+       public function testValueTypeConstraintSubclassValid() {
+               $mock = $this->getSparqlHelperMockHasType( 'Q42', [ 'Q100', 
'Q101' ], false, true );
+
+               $this->checker = new ValueTypeSparqlChecker(
+                       $this->lookup,
+                       new ConstraintParameterParser(),
+                       $mock
+               );
+
+               $entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
+
+               $constraintParameters = [
+                       'class' => 'Q100,Q101',
+                       'relation' => 'subclass'
+               ];
+
+               $checkResult = $this->checker->checkConstraint( 
$this->typeStatement, $this->getConstraintMock( $constraintParameters ), 
$entity );
+               $this->assertCompliance( $checkResult );
+       }
+
+       // relation 'subclass', violations
+
+       public function testValueTypeConstraintSubclassInvalid() {
+               $mock = $this->getSparqlHelperMockHasType( 'Q42', [ 'Q200', 
'Q201' ], false, false );
+
+               $this->checker = new ValueTypeSparqlChecker(
+                       $this->lookup,
+                       new ConstraintParameterParser(),
+                       $mock
+               );
+
+               $entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
+
+               $constraintParameters = [
+                       'class' => 'Q200,Q201',
+                       'relation' => 'subclass'
+               ];
+
+               $checkResult = $this->checker->checkConstraint( 
$this->typeStatement, $this->getConstraintMock( $constraintParameters ), 
$entity );
+               $this->assertViolation( $checkResult, 
'wbqc-violation-message-sparql-value-type' );
+       }
+
+       // relation 'instance'
+
+       public function testValueTypeConstraintInstanceValid() {
+               $mock = $this->getSparqlHelperMockHasType( 'Q42', [ 'Q100', 
'Q101' ], true, true );
+
+               $this->checker = new ValueTypeSparqlChecker(
+                       $this->lookup,
+                       new ConstraintParameterParser(),
+                       $mock
+               );
+
+               $entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
+
+               $constraintParameters = [
+                       'relation' => 'instance',
+                       'class' => 'Q100,Q101'
+               ];
+
+               $checkResult = $this->checker->checkConstraint( 
$this->typeStatement, $this->getConstraintMock( $constraintParameters ), 
$entity );
+               $this->assertCompliance( $checkResult );
+       }
+
+       // relation 'instance', violations
+
+       public function testValueTypeConstraintInstanceInvalid() {
+               $mock = $this->getSparqlHelperMockHasType( 'Q42', [ 'Q200', 
'Q201' ], true, false );
+
+               $this->checker = new ValueTypeSparqlChecker(
+                       $this->lookup,
+                       new ConstraintParameterParser(),
+                       $mock
+               );
+
+               $entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
+
+               $constraintParameters = [
+                       'relation' => 'instance',
+                       'class' => 'Q200,Q201'
+               ];
+
+               $checkResult = $this->checker->checkConstraint( 
$this->typeStatement, $this->getConstraintMock( $constraintParameters ), 
$entity );
+               $this->assertViolation( $checkResult, 
'wbqc-violation-message-sparql-value-type' );
+       }
+
+       /**
+        * @param string[] $parameters
+        *
+        * @return Constraint
+        */
+       private function getConstraintMock( array $parameters ) {
+               $mock = $this
+                         ->getMockBuilder( Constraint::class )
+                         ->disableOriginalConstructor()
+                         ->getMock();
+               $mock->expects( $this->any() )
+                       ->method( 'getConstraintParameters' )
+                       ->will( $this->returnValue( $parameters ) );
+               $mock->expects( $this->any() )
+                       ->method( 'getConstraintTypeQid' )
+                       ->will( $this->returnValue( 'Type' ) );
+
+               return $mock;
+       }
+
+}
diff --git a/tests/phpunit/Checker/ValueCountChecker/UniqueValueCheckerTest.php 
b/tests/phpunit/Checker/ValueCountChecker/UniqueValueCheckerTest.php
index c7f98e7..50b085d 100644
--- a/tests/phpunit/Checker/ValueCountChecker/UniqueValueCheckerTest.php
+++ b/tests/phpunit/Checker/ValueCountChecker/UniqueValueCheckerTest.php
@@ -7,10 +7,15 @@
 use Wikibase\DataModel\Entity\Item;
 use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\DataModel\Entity\EntityDocument;
 use Wikibase\DataModel\Entity\EntityIdValue;
 use WikibaseQuality\ConstraintReport\Constraint;
 use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\UniqueValueChecker;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
+use 
WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\ConstraintParameterParser;
 use WikibaseQuality\ConstraintReport\Tests\ResultAssertions;
+use WikibaseQuality\ConstraintReport\Tests\SparqlHelperMock;
+use WikibaseQuality\Tests\Helper\JsonFileEntityLookup;
 
 /**
  * @covers 
\WikibaseQuality\ConstraintReport\ConstraintCheck\Checker\UniqueValueChecker
@@ -19,10 +24,17 @@
  *
  * @uses   \WikibaseQuality\ConstraintReport\ConstraintCheck\Result\CheckResult
  *
- * @author BP2014N1
+ * @author Olga Bode
  * @license GNU GPL v2+
  */
-class UniqueValueCheckerTest extends \MediaWikiTestCase {
+class UniqueValueCheckerTest extends \PHPUnit_Framework_TestCase  {
+
+       use SparqlHelperMock;
+
+       /**
+        * @var JsonFileEntityLookup
+        */
+       private $lookup;
 
        use ResultAssertions;
 
@@ -38,23 +50,43 @@
 
        protected function setUp() {
                parent::setUp();
+               $this->lookup = new JsonFileEntityLookup( __DIR__ );
+               $this->uniquePropertyId = new PropertyId( 'P31' );
 
-               $this->uniquePropertyId = new PropertyId( 'P227' );
-               $this->checker = new UniqueValueChecker();
        }
 
        protected function tearDown() {
                unset( $this->uniquePropertyId );
+               unset( $this->lookup );
                parent::tearDown();
        }
 
-       public function testCheckUniqueValueConstraint() {
-               $itemId = new ItemId( 'Q404' );
-               $entity = new Item( $itemId );
-               $statement = new Statement( new PropertyValueSnak( 
$this->uniquePropertyId, new EntityIdValue( $itemId ) ) );
-               $checkResult = $this->checker->checkConstraint( $statement, 
$this->getConstraintMock( [] ), $entity );
+       public function testCheckUniqueValueConstraintInvalid() {
+               $statement = new Statement( new PropertyValueSnak( 
$this->uniquePropertyId, new EntityIdValue( new ItemId( 'Q6' ) ) ) );
+               $statement->setGuid( 'Q6$e35707be-4a84-61fe-9b52-623784a316a7' 
);
 
-               $this->assertTodo( $checkResult );
+               $mock = $this->getSparqlHelperMockFindEntities( $statement, [ 
new ItemId( 'Q42' ) ] );
+
+               $this->checker = new UniqueValueChecker( $mock );
+
+               $entity = $this->lookup->getEntity( new ItemId( 'Q6' ) );
+
+               $checkResult = $this->checker->checkConstraint( $statement, 
$this->getConstraintMock( [] ), $entity );
+               $this->assertViolation( $checkResult, 
'wbqc-violation-message-unique-value' );
+       }
+
+       public function testCheckUniqueValueConstraintValid() {
+               $statement = new Statement( new PropertyValueSnak( 
$this->uniquePropertyId, new EntityIdValue( new ItemId( 'Q1' ) ) ) );
+               $statement->setGuid( "Q1$56e6a474-4431-fb24-cc15-1d580e467559" 
);
+
+               $mock = $this->getSparqlHelperMockFindEntities( $statement, [] 
);
+
+               $this->checker = new UniqueValueChecker( $mock );
+
+               $entity = $this->lookup->getEntity( new ItemId( 'Q1' ) );
+
+               $checkResult = $this->checker->checkConstraint( $statement, 
$this->getConstraintMock( [] ), $entity );
+               $this->assertCompliance( $checkResult );
        }
 
        /**
diff --git a/tests/phpunit/DefaultConfig.php b/tests/phpunit/DefaultConfig.php
index cc49b92..b3812ad 100644
--- a/tests/phpunit/DefaultConfig.php
+++ b/tests/phpunit/DefaultConfig.php
@@ -26,6 +26,8 @@
                        }
                        // reduce some limits to make tests run faster
                        $this->defaultConfig->set( 
'WBQualityConstraintsTypeCheckMaxEntities', 10 );
+                       // never query remote servers
+                       $this->defaultConfig->set( 
'WBQualityConstraintsSparqlEndpoint', 'http://localhost:65536/' );
                }
 
                return $this->defaultConfig;
diff --git a/tests/phpunit/DelegatingConstraintCheckerTest.php 
b/tests/phpunit/DelegatingConstraintCheckerTest.php
index ee1e980..dc373de 100644
--- a/tests/phpunit/DelegatingConstraintCheckerTest.php
+++ b/tests/phpunit/DelegatingConstraintCheckerTest.php
@@ -5,6 +5,7 @@
 use Wikibase\DataModel\Entity\ItemId;
 use Wikibase\DataModel\Entity\ItemIdParser;
 use Wikibase\DataModel\Services\Statement\StatementGuidParser;
+use Wikibase\Rdf\RdfVocabulary;
 use 
WikibaseQuality\ConstraintReport\ConstraintCheck\DelegatingConstraintChecker;
 use WikibaseQuality\ConstraintReport\ConstraintReportFactory;
 use WikibaseQuality\ConstraintReport\Tests\ConstraintParameters;
@@ -63,13 +64,20 @@
        protected function setUp() {
                parent::setUp();
                $this->lookup = $this->createEntityLookup();
-               $this->statementGuidParser = new StatementGuidParser( new 
ItemIdParser() );
+               $itemIdParser = new ItemIdParser();
+               $this->statementGuidParser = new StatementGuidParser( 
$itemIdParser );
                $config = $this->getDefaultConfig();
+               $rdfVocabulary = new RdfVocabulary(
+                       'http://www.wikidata.org/entity/',
+                       'http://www.wikidata.org/wiki/Special:EntityData/'
+               );
                $factory = new ConstraintReportFactory(
                        $this->lookup,
                        $this->statementGuidParser,
                        $config,
-                       $this->getConstraintParameterRenderer()
+                       $this->getConstraintParameterRenderer(),
+                       $rdfVocabulary,
+                       $itemIdParser
                );
                $this->constraintChecker = $factory->getConstraintChecker();
 
@@ -80,6 +88,7 @@
        protected function tearDown() {
                unset( $this->lookup );
                unset( $this->constraintChecker );
+               unset( $this->statementGuidParser );
                parent::tearDown();
        }
 
diff --git a/tests/phpunit/Helper/SparqlHelperTest.php 
b/tests/phpunit/Helper/SparqlHelperTest.php
new file mode 100644
index 0000000..e52e7f2
--- /dev/null
+++ b/tests/phpunit/Helper/SparqlHelperTest.php
@@ -0,0 +1,105 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\Test\Helper;
+
+use Wikibase\DataModel\Entity\EntityIdValue;
+use Wikibase\DataModel\Entity\ItemId;
+use Wikibase\DataModel\Entity\ItemIdParser;
+use Wikibase\DataModel\Entity\PropertyId;
+use Wikibase\DataModel\Snak\PropertyValueSnak;
+use Wikibase\DataModel\Statement\Statement;
+use Wikibase\Rdf\RdfVocabulary;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
+use WikibaseQuality\ConstraintReport\Tests\DefaultConfig;
+
+/**
+ * @covers 
\WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper
+ *
+ * @group WikibaseQualityConstraints
+ *
+ * @author Lucas Werkmeister
+ * @license GNU GPL v2+
+ */
+class SparqlHelperTest extends \PHPUnit_Framework_TestCase {
+
+       use DefaultConfig;
+
+       public function testHasType() {
+               $sparqlHelper = $this->getMockBuilder( SparqlHelper::class )
+                                         ->setConstructorArgs( [
+                                                 $this->getDefaultConfig(),
+                                                 new RdfVocabulary(
+                                                         
'http://www.wikidata.org/entity/',
+                                                         
'http://www.wikidata.org/wiki/Special:EntityData/'
+                                                 ),
+                                                 new ItemIdParser()
+                                         ] )
+                                         ->setMethods( [ 'runQuery' ] )
+                                         ->getMock();
+
+               $query = <<<EOF
+ASK {
+  BIND(wd:Q1 AS ?item)
+  VALUES ?class { wd:Q100 wd:Q101 }
+  ?item wdt:P31/wdt:P279* ?class.
+}
+EOF;
+
+               $sparqlHelper->expects( $this->exactly( 1 ) )
+                       ->method( 'runQuery' )
+                       ->willReturn( [ 'boolean' => true ] )
+                       ->withConsecutive( [ $this->equalTo( $query ) ] );
+
+               $this->assertTrue( $sparqlHelper->hasType( 'Q1', [ 'Q100', 
'Q101' ], true ) );
+       }
+
+       public function testFindEntitiesWithSameStatement() {
+               $guid = 'Q1$8542690f-dfab-4846-944f-8382df730d2c';
+               $statement = new Statement(
+                       new PropertyValueSnak( new PropertyId( 'P1' ), new 
EntityIdValue( new ItemId( 'Q1' ) ) ),
+                       null,
+                       null,
+                       $guid
+               );
+
+               $sparqlHelper = $this->getMockBuilder( SparqlHelper::class )
+                                         ->setConstructorArgs( [
+                                                 $this->getDefaultConfig(),
+                                                 new RdfVocabulary(
+                                                         
'http://www.wikidata.org/entity/',
+                                                         
'http://www.wikidata.org/wiki/Special:EntityData/'
+                                                 ),
+                                                 new ItemIdParser()
+                                         ] )
+                                         ->setMethods( [ 'runQuery' ] )
+                                         ->getMock();
+
+               $query = <<<EOF
+SELECT ?otherEntity WHERE {
+  BIND(wds:Q1-8542690f-dfab-4846-944f-8382df730d2c AS ?statement)
+  BIND(p:P1 AS ?p)
+  BIND(ps:P1 AS ?ps)
+  ?entity ?p ?statement.
+  ?statement ?ps ?value.
+  ?otherStatement ?ps ?value.
+  ?otherEntity ?p ?otherStatement.
+  FILTER(?otherEntity != ?entity)
+}
+LIMIT 10
+EOF;
+
+               $sparqlHelper->expects( $this->exactly( 1 ) )
+                       ->method( 'runQuery' )
+                       ->willReturn( [ 'head' => [ 'vars' => [ 'otherEntity' ] 
], 'results' => [ 'bindings' => [
+                               [ 'otherEntity' => [ 'type' => 'uri', 'value' 
=> 'http://www.wikidata.org/entity/Q100' ] ],
+                               [ 'otherEntity' => [ 'type' => 'uri', 'value' 
=> 'http://www.wikidata.org/entity/Q101' ] ],
+                       ] ] ] )
+                       ->withConsecutive( [ $this->equalTo( $query ) ] );
+
+               $this->assertEquals(
+                       $sparqlHelper->findEntitiesWithSameStatement( 
$statement ),
+                       [ new ItemId( 'Q100' ), new ItemId( 'Q101' ) ]
+               );
+       }
+
+}
diff --git a/tests/phpunit/SparqlHelperMock.php 
b/tests/phpunit/SparqlHelperMock.php
new file mode 100644
index 0000000..51dc540
--- /dev/null
+++ b/tests/phpunit/SparqlHelperMock.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace WikibaseQuality\ConstraintReport\Tests;
+
+use HashConfig;
+use Wikibase\DataModel\Statement\Statement;
+use WikibaseQuality\ConstraintReport\ConstraintCheck\Helper\SparqlHelper;
+
+/**
+ * @author Lucas Werkmeister
+ * @license GNU GPL v2+
+ */
+trait SparqlHelperMock {
+
+       /**
+        * @param string $expectedId
+        * @param string[] $expectedClasses
+        * @param boolean $expectedWithInstance
+        * @param boolean $result
+        * @return SparqlHelper
+        */
+       private function getSparqlHelperMockHasType(
+               $expectedId, array $expectedClasses, $expectedWithInstance,
+               $result ) {
+
+               $mock = $this->getMockBuilder( SparqlHelper::class )
+                         ->disableOriginalConstructor()
+                         ->getMock();
+
+               $mock->expects( $this->exactly( 1 ) )
+                       ->method( 'hasType' )
+                       ->willReturn( $result )
+                       ->withConsecutive( [
+                               $this->equalTo( $expectedId ),
+                               $this->equalTo( $expectedClasses ),
+                               $this->equalTo( $expectedWithInstance )
+                       ] );
+
+               return $mock;
+       }
+
+       /**
+        * @param Statement $expectedStatement
+        * @param EntityId?[] $result
+        * @return SparqlHelper
+        */
+       private function getSparqlHelperMockFindEntities(
+               Statement $expectedStatement,
+               $result ) {
+
+               $mock = $this->getMockBuilder( SparqlHelper::class )
+                         ->disableOriginalConstructor()
+                         ->getMock();
+
+               $mock->expects( $this->exactly( 1 ) )
+                       ->method( 'findEntitiesWithSameStatement' )
+                       ->willReturn( $result )
+                       ->withConsecutive( [ $this->equalTo( $expectedStatement 
) ] );
+
+               return $mock;
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/345302
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I8af2e92215c026805df64babc5baf2a4ac8bc25e
Gerrit-PatchSet: 33
Gerrit-Project: mediawiki/extensions/WikibaseQualityConstraints
Gerrit-Branch: master
Gerrit-Owner: Olga Bode <[email protected]>
Gerrit-Reviewer: Jonas Kress (WMDE) <[email protected]>
Gerrit-Reviewer: Lucas Werkmeister (WMDE) <[email protected]>
Gerrit-Reviewer: Olga Bode <[email protected]>
Gerrit-Reviewer: Smalyshev <[email protected]>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to