MongoDBEntityIdForQueryLookup   C
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 100%

Importance

Changes 12
Bugs 0 Features 2
Metric Value
wmc 36
c 12
b 0
f 2
lcom 1
cbo 17
dl 0
loc 179
ccs 96
cts 96
cp 1
rs 6.9142

15 Methods

Rating   Name   Duplication   Size   Complexity  
A getEntityIdsForQuery() 0 3 1
A buildQueryForSomeProperty() 0 17 4
A buildPropertyValueForSearch() 0 11 4
A buildStringValueForSearch() 0 4 1
A __construct() 0 5 1
A doQuery() 0 14 2
B buildQueryForDescription() 0 15 6
A buildQueryForConjunction() 0 6 2
A buildQueryForDisjunction() 0 6 2
A buildQueryForValueDescription() 0 17 3
A buildEntityIdValueForSearch() 0 9 3
A buildTimeValueForSearch() 0 5 1
A buildQueryModifiers() 0 9 2
A applyOptionsToCursor() 0 9 2
A formatResults() 0 9 2
1
<?php
2
3
namespace Wikibase\EntityStore\MongoDB;
4
5
use Ask\Language\Description\AnyValue;
6
use Ask\Language\Description\Conjunction;
7
use Ask\Language\Description\Description;
8
use Ask\Language\Description\Disjunction;
9
use Ask\Language\Description\SomeProperty;
10
use Ask\Language\Description\ValueDescription;
11
use Ask\Language\Option\QueryOptions;
12
use DataValues\DataValue;
13
use DataValues\StringValue;
14
use DataValues\TimeValue;
15
use Doctrine\MongoDB\Cursor;
16
use Doctrine\MongoDB\Database;
17
use Doctrine\MongoDB\Query\Expr;
18
use Iterator;
19
use MongoRegex;
20
use Wikibase\DataModel\Entity\EntityIdValue;
21
use Wikibase\DataModel\Entity\ItemId;
22
use Wikibase\DataModel\Entity\PropertyId;
23
use Wikibase\EntityStore\FeatureNotSupportedException;
24
use Wikibase\EntityStore\Internal\EntityIdForQueryLookup;
25
26
/**
27
 * Internal class
28
 *
29
 * @licence GPLv2+
30
 * @author Thomas Pellissier Tanon
31
 */
32
class MongoDBEntityIdForQueryLookup implements EntityIdForQueryLookup {
33
34
	/**
35
	 * @var Database
36
	 */
37
	private $database;
38
39
	/**
40
	 * @var MongoDBDocumentBuilder
41
	 */
42
	private $documentBuilder;
43
44
	/**
45
	 * @var int|null
46
	 */
47
	private $timeLimit;
48
49
	/**
50
	 * @param Database $database
51
	 * @param MongoDBDocumentBuilder $documentBuilder
52
	 * @param int|null $timeLimit
53
	 */
54 16
	public function __construct( Database $database, MongoDBDocumentBuilder $documentBuilder, $timeLimit = null ) {
55 16
		$this->database = $database;
56 16
		$this->documentBuilder = $documentBuilder;
57 16
		$this->timeLimit = $timeLimit;
58 16
	}
59
60
	/**
61
	 * @see EntityForQueryLookup::getEntityIdsForQuery
62
	 */
63 16
	public function getEntityIdsForQuery( Description $queryDescription, QueryOptions $queryOptions = null, $entityType ) {
64 16
		return $this->formatResults( $this->doQuery( $queryDescription, $queryOptions, $entityType ) );
65
	}
66
67 16
	private function doQuery( Description $queryDescription, QueryOptions $queryOptions = null, $entityType ) {
68 16
		$cursor = $this->database
69 16
			->selectCollection( $entityType )
70 16
			->find(
71 16
				$this->buildQueryForDescription( $queryDescription, new Expr() )->getQuery(),
72 9
				$this->buildQueryModifiers()
73 9
			);
74
75 9
		if( $queryOptions === null ) {
76 1
			return $cursor;
77
		}
78
79 8
		return $this->applyOptionsToCursor( $cursor, $queryOptions );
80
	}
81
82 16
	private function buildQueryForDescription( Description $description, Expr $expr, PropertyId $currentProperty = null ) {
83 16
		if( $description instanceof AnyValue ) {
84 4
			return $expr;
85 14
		} elseif( $description instanceof Conjunction ) {
86 2
			return $this->buildQueryForConjunction( $description, $expr, $currentProperty );
87 14
		} elseif( $description instanceof Disjunction ) {
88 2
			return $this->buildQueryForDisjunction( $description, $expr, $currentProperty );
89 14
		} elseif( $description instanceof SomeProperty ) {
90 13
			return $this->buildQueryForSomeProperty( $description, $expr );
91 11
		} elseif( $description instanceof ValueDescription ) {
92 10
			return $this->buildQueryForValueDescription( $description, $expr, $currentProperty );
93
		} else {
94 1
			throw new FeatureNotSupportedException( 'Unknown description type: ' . $description->getType() );
95
		}
96
	}
97
98 2
	private function buildQueryForConjunction( Conjunction $conjunction, Expr $expr, PropertyId $currentProperty = null ) {
99 2
		foreach( $conjunction->getDescriptions() as $description ) {
100 2
			$expr->addAnd( $this->buildQueryForDescription( $description, new Expr(), $currentProperty ) );
101 2
		}
102 2
		return $expr;
103
	}
104
105 2
	private function buildQueryForDisjunction( Disjunction $disjunction, Expr $expr, PropertyId $currentProperty = null ) {
106 2
		foreach( $disjunction->getDescriptions() as $description ) {
107 2
			$expr->addOr( $this->buildQueryForDescription( $description, new Expr(), $currentProperty ) );
108 2
		}
109 2
		return $expr;
110
	}
111
112 13
	private function buildQueryForSomeProperty( SomeProperty $someProperty, Expr $expr ) {
113 13
		if( $someProperty->isSubProperty() ) {
114 1
			throw new FeatureNotSupportedException( 'Sub-properties are not supported yet' );
115
		}
116
117 13
		$propertyIdValue = $someProperty->getPropertyId();
118 13
		if( !( $propertyIdValue instanceof EntityIdValue ) ) {
119 1
			throw new FeatureNotSupportedException( 'PropertyId should be an EntityIdValue' );
120
		}
121
122 12
		$propertyId = $propertyIdValue->getEntityId();
123 12
		if( !( $propertyId instanceof PropertyId ) ) {
124 1
			throw new FeatureNotSupportedException( 'PropertyId should be a PropertyId' );
125
		}
126
127 11
		return $this->buildQueryForDescription( $someProperty->getSubDescription(), $expr, $propertyId );
128
	}
129
130 10
	private function buildQueryForValueDescription(
131
		ValueDescription $valueDescription,
132
		Expr $expr,
133
		PropertyId $currentProperty = null
134
	) {
135 10
		$value = $valueDescription->getValue();
136
137 10
		switch( $valueDescription->getComparator() ) {
138 10
			case ValueDescription::COMP_EQUAL:
139 10
			case ValueDescription::COMP_LIKE:
140 9
				$expr->field( 'sclaims.' . $value->getType() )->equals( $this->buildPropertyValueForSearch( $currentProperty, $value ) );
0 ignored issues
show
Bug introduced by
It seems like $currentProperty defined by parameter $currentProperty on line 133 can be null; however, Wikibase\EntityStore\Mon...ropertyValueForSearch() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
141 7
				return $expr;
142
143 1
			default:
144 1
				throw new FeatureNotSupportedException( 'Unsupported ValueDescription comparator' );
145 1
		}
146
	}
147
148 9
	private function buildPropertyValueForSearch( PropertyId $propertyId, DataValue $dataValue ) {
149 9
		if( $dataValue instanceof EntityIdValue ) {
150 5
			return $this->buildEntityIdValueForSearch( $propertyId, $dataValue );
151 7
		} elseif( $dataValue instanceof StringValue ) {
152 4
			return $this->buildStringValueForSearch( $propertyId, $dataValue );
153 3
		} elseif( $dataValue instanceof TimeValue ) {
154 2
			return $this->buildTimeValueForSearch( $propertyId, $dataValue );
155
		} else {
156 1
			throw new FeatureNotSupportedException( 'Not supported DataValue type: ' . $dataValue->getType() );
157
		}
158
	}
159
160 5
	private function buildEntityIdValueForSearch( PropertyId $propertyId, EntityIdValue $entityIdValue ) {
161 5
		$entityId = $entityIdValue->getEntityId();
162
163 5
		if( !( $entityId instanceof ItemId || $entityId instanceof PropertyId ) ) {
164 1
			throw new FeatureNotSupportedException( 'Not supported entity type: ' . $entityId->getEntityType() );
165
		}
166
167 4
		return $propertyId->getSerialization() . '-' . $entityIdValue->getEntityId()->getSerialization();
168
	}
169
170 4
	private function buildStringValueForSearch( PropertyId $propertyId, StringValue $stringValue ) {
171 4
		return $propertyId->getSerialization() . '-' .
172 4
			$this->documentBuilder->buildSearchedStringValue( $stringValue->getValue() );
173
	}
174
175 2
	private function buildTimeValueForSearch( PropertyId $propertyId, TimeValue $timeValue ) {
176 2
		$significantTimePart = preg_replace( '/(-00)*T00:00:00Z$/', '', $timeValue->getTime() );
177
178 2
		return new MongoRegex( '/^' . preg_quote( $propertyId->getSerialization() . '-' . $significantTimePart, '/' ) . '/' );
179
	}
180
181 9
	private function buildQueryModifiers() {
182 9
		$modifiers = [ '_id' => 1 ];
183
184 9
		if( $this->timeLimit !== null ) {
185 1
			$modifiers['$maxTimeMS'] = $this->timeLimit;
186 1
		}
187
188 9
		return $modifiers;
189
	}
190
191 8
	private function applyOptionsToCursor( Cursor $cursor, QueryOptions $options ) {
192 8
		if( $this->timeLimit !== null ) {
193 1
			$cursor->timeout( $this->timeLimit );
194 1
		}
195
196
		return $cursor
197 8
			->skip( $options->getOffset() )
198 8
			->limit( $options->getLimit() );
199
	}
200
201 9
	private function formatResults( Iterator $cursor ) {
202 9
		$entityIds = [];
203
204 9
		foreach( $cursor as $document ) {
205 9
			$entityIds[] = $this->documentBuilder->buildEntityIdForDocument( $document );
206 9
		}
207
208 9
		return $entityIds;
209
	}
210
}
211