diff options
Diffstat (limited to 'www/wiki/extensions/SemanticMediaWiki/src/SQLStore/EntityValueUniquenessConstraintChecker.php')
-rw-r--r-- | www/wiki/extensions/SemanticMediaWiki/src/SQLStore/EntityValueUniquenessConstraintChecker.php | 218 |
1 files changed, 218 insertions, 0 deletions
diff --git a/www/wiki/extensions/SemanticMediaWiki/src/SQLStore/EntityValueUniquenessConstraintChecker.php b/www/wiki/extensions/SemanticMediaWiki/src/SQLStore/EntityValueUniquenessConstraintChecker.php new file mode 100644 index 00000000..01a634e0 --- /dev/null +++ b/www/wiki/extensions/SemanticMediaWiki/src/SQLStore/EntityValueUniquenessConstraintChecker.php @@ -0,0 +1,218 @@ +<?php + +namespace SMW\SQLStore; + +use SMW\DIProperty; +use SMW\DIWikiPage; +use SMW\SQLStore\SQLStore; +use SMW\Store; +use SMWDataItem as DataItem; +use SMW\RequestOptions; +use SMW\IteratorFactory; +use InvalidArgumentException; +use RuntimeException; +use SMWDIContainer as DIContainer; + +/** + * @license GNU GPL v2+ + * @since 3.0 + * + * @author mwjames + */ +class EntityValueUniquenessConstraintChecker { + + /** + * @var SQLStore + */ + private $store; + + /** + * @var IteratorFactory + */ + private $iteratorFactory; + + /** + * @since 3.0 + * + * @param SQLStore $store + * @param IteratorFactory $iteratorFactory + */ + public function __construct( Store $store, IteratorFactory $iteratorFactory ) { + $this->store = $store; + $this->iteratorFactory = $iteratorFactory; + } + + /** + * Find references (all or limited by RequestOptions) for the combination of + * a property and a value. This can be used to identify uniqueness violations + * amongst entities where the same value (+property) is assigned to different + * subjects or it be used to count the cardinality for a specific value + * representation. + * + * @since 3.0 + * + * @param DIProperty $property + * @param DataItem $dataItem + * @param RequestOptions $requestOptions + * + * @return Iterator|[] + */ + public function checkConstraint( DIProperty $property, DataItem $dataItem, RequestOptions $requestOptions ) { + + $propTableId = $this->store->getPropertyTableInfoFetcher()->findTableIdForProperty( + $property + ); + + $proptables = $this->store->getPropertyTables(); + $propertyTable = $proptables[$propTableId]; + + if ( !isset( $proptables[$propTableId] ) || !$propertyTable->usesIdSubject() ) { + return []; + } + + $connection = $this->store->getConnection( 'mw.db' ); + $query = $connection->newQuery(); + + $query->index = 1; + $query->alias = 't'; + $i = $query->index; + + $query->table( $propertyTable->getName(), "{$query->alias}{$i}" ); + + // Only find entities + $query->field( "{$query->alias}{$i}.s_id" ); + + $this->resolve_value_condition( $propertyTable, $property, $dataItem, $query ); + + foreach ( $requestOptions->getExtraConditions() as $extraCondition ) { + if ( is_callable( $extraCondition ) ) { + $query->condition( $extraCondition( $this->store, $query, "{$query->alias}{$i}" ) ); + } else { + throw new RuntimeException( "Expected a callable at this point!" ); + } + } + + $query->type( 'SELECT' ); + $query->options( [ 'LIMIT' => $requestOptions->getLimit() ] ); + + $res = $connection->query( + $query, + __METHOD__ + ); + + $result = $this->iteratorFactory->newMappingIterator( + $this->iteratorFactory->newResultIterator( $res ), + function( $row ) { + return $this->store->getObjectIds()->getDataItemById( $row->s_id ); + } + ); + + return $result; + } + + private function resolve_value_condition( $propertyTable, $property, $dataItem, $query ) { + + // Collect conditions to appear as + // `... (t1.p_id='121913' AND t1.o_sortkey='3520062') ...` + $conditions = []; + + // Keep the index in case of a recursive iteration + $i = $query->index; + + if ( !$propertyTable->isFixedPropertyTable() ) { + + $pid = $this->store->getObjectIds()->getSMWPropertyID( + $property + ); + + $conditions[] = $query->eq( "{$query->alias}{$i}.p_id", $pid ); + } + + $diHandler = $this->store->getDataItemHandlerForDIType( + $propertyTable->getDiType() + ); + + if ( !$dataItem instanceof DIContainer ) { + foreach ( $diHandler->getWhereConds( $dataItem ) as $fieldName => $value ) { + $conditions[] = $query->eq( "{$query->alias}{$i}.$fieldName", $value ); + } + } else { + + /** + * For a container based property/value pair we expected something similar + * to: + * + * SELECT t1.s_id FROM `smw_di_wikipage` AS t1 + * INNER JOIN `smw_di_wikipage` AS t2 ON t2.s_id=t1.o_id + * INNER JOIN `smw_di_wikipage` AS t3 ON t3.s_id=t1.o_id + * INNER JOIN `smw_di_number` AS t4 ON t4.s_id=t1.o_id + * WHERE + * (t2.p_id='333615' AND t2.o_id='302096') AND + * (t3.p_id='333611' AND t3.o_id='193213') AND + * (t4.p_id='121913' AND t4.o_sortkey='3520062') AND + * (t1.p_id='310161') AND (t1.s_id!='333608') + * LIMIT 2 + */ + + // Handle containers recursively + $this->resolve_container_conditions( $propertyTable, $dataItem, $query ); + } + + $query->condition( $query->asAnd( $conditions ) ); + } + + private function resolve_container_conditions( $propertyTable, $dataItem, $query ) { + + $proptables = $this->store->getPropertyTables(); + $semanticData = $dataItem->getSemanticData(); + + $alias = $query->alias; + $i = $query->index; + + // ought to be a type 'p' object + $keys = array_keys( $propertyTable->getFields( $this->store ) ); + $joinfield = "{$alias}{$i}." . reset( $keys ); + + foreach ( $semanticData->getProperties() as $property ) { + + $tableid = $this->store->findPropertyTableID( $property ); + $subproptable = $proptables[$tableid]; + + foreach ( $semanticData->getPropertyValues( $property ) as $subvalue ) { + // Increase the index for each iteration to ensure that each + // condition has its own alias + $i++; + + if ( $subproptable->usesIdSubject() ) { + // simply add property table to check values + $query->join( + 'INNER JOIN', + [ + // e.g. `... INNER JOIN `smw_di_wikipage` AS t2 ON t2.s_id=t1.o_id ...` + $subproptable->getName() => "{$alias}{$i} ON {$alias}{$i}.s_id=$joinfield" + ] + ); + } else { + // Rare case with a table that uses subject title+namespace + // in a container object (should never happen in SMW core!!) + $query->join( + 'INNER JOIN', + [ + SQLStore::ID_TABLE => "ids{$i} ON ids{$i}.smw_id=$joinfield" + ] + ); + $query->join( + 'INNER JOIN', + [ + $subproptable->getName() => "{$alias}{$i} ON {$alias}{$i}.s_title=ids{$alias}{$i}.smw_title AND {$alias}{$i}.s_namespace=ids{$alias}{$i}.smw_namespace" + ] + ); + } + + $query->index = $i; + $this->resolve_value_condition( $subproptable, $property, $subvalue, $query ); + } + } + } + +} |