Completed
Push — master ( 2472c5...a814e8 )
by mw
35:03
created

storage/SQLStore/SMW_SQLStore3_Readers.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
use SMW\DataTypeRegistry;
4
use SMW\DIWikiPage;
5
use SMW\DIProperty;
6
use SMW\SQLStore\TableDefinition;
7
use SMW\SQLStore\EntityStore\Exception\DataItemHandlerException;
8
use SMW\SQLStore\TableBuilder\FieldType;
9
10
/**
11
 * Class to provide all basic read methods for SMWSQLStore3.
12
 *
13
 * @author Markus Krötzsch
14
 * @author Jeroen De Dauw
15
 * @author Nischay Nahata
16
 *
17
 * @since 1.8
18
 * @ingroup SMWStore
19
 */
20
class SMWSQLStore3Readers {
21
22
	/**
23
	 * The store used by this store reader
24
	 *
25
	 * @since 1.8
26
	 * @var SMWSQLStore3
27
	 */
28
	private $store;
29
30
	/**
31
	 *  >0 while getSemanticData runs, used to prevent nested calls from clearing the cache
32
	 * while another call runs and is about to fill it with data
33
	 *
34
	 * @var int
35
	 */
36
	private static $in_getSemanticData = 0;
37
38 5
	public function __construct( SMWSQLStore3 $parentStore ) {
39 5
		$this->store = $parentStore;
40 5
	}
41
42
	/**
43
	 * @see SMWStore::getSemanticData()
44
	 * @since 1.8
45
	 *
46
	 * @param DIWikiPage $subject
47
	 * @param string[]|bool $filter
48
	 */
49 260
	public function getSemanticData( DIWikiPage $subject, $filter = false ) {
50
51
		// *** Find out if this subject exists ***//
52 260
		$sortKey = '';
53
54 260
		$sid = $this->store->smwIds->getSMWPageIDandSort(
55 260
			$subject->getDBkey(),
56 260
			$subject->getNamespace(),
57 260
			$subject->getInterwiki(),
58 260
			$subject->getSubobjectName(),
59
			$sortKey,
60 260
			true,
61 260
			true
62
		);
63
64
		// Ensures that a cached item to contain an expected sortKey when
65
		// for example the ID was just created and the sortKey from the DB
66
		// is empty otherwise the DB wins over the invoked sortKey
67 260
		if ( !$sortKey ) {
68 256
			$sortKey = $subject->getSortKey();
69
		}
70
71 260
		$subject->setSortKey( $sortKey );
72
73 260
		if ( $sid == 0 ) {
74
			// We consider redirects for getting $sid,
75
			// so $sid == 0 also means "no redirects".
76 256
			return new SMWSemanticData( $subject );
77
		}
78
79 251
		$propertyTableHashes = $this->store->smwIds->getPropertyTableHashes( $sid );
80
81 251
		foreach ( $this->store->getPropertyTables() as $tid => $proptable ) {
82 251
			if ( !array_key_exists( $proptable->getName(), $propertyTableHashes ) ) {
83 251
				continue;
84
			}
85
86 242
			if ( $filter !== false ) {
87 3
				$relevant = false;
88 3
				foreach ( $filter as $typeId ) {
89 3
					$diType = DataTypeRegistry::getInstance()->getDataItemId( $typeId );
90 3
					$relevant = $relevant || ( $proptable->getDiType() == $diType );
91 3
					if ( $relevant ) {
92 3
						break;
93
					}
94
				}
95 3
				if ( !$relevant ) {
96 3
					continue;
97
				}
98
			}
99
100 242
			$this->getSemanticDataFromTable( $sid, $subject, $proptable );
101
		}
102
103
		// Note: the sortkey is always set but belongs to no property table,
104
		// hence no entry in $this->store->m_sdstate[$sid] is made.
105 251
		self::$in_getSemanticData++;
106 251
		$this->initSemanticDataCache( $sid, $subject );
107
108
		// Avoid adding a sortkey for an already extended stub
109 251
		if ( !$this->store->m_semdata[$sid]->hasProperty( new DIProperty( '_SKEY' ) ) ) {
110 192
			$this->store->m_semdata[$sid]->addPropertyStubValue( '_SKEY', array( '', $sortKey ) );
111
		}
112
113 251
		self::$in_getSemanticData--;
114
115 251
		return $this->store->m_semdata[$sid];
116
	}
117
118
	/**
119
	 * Helper method to make sure there is a cache entry for the data about
120
	 * the given subject with the given ID.
121
	 *
122
	 * @todo The management of this cache should be revisited.
123
	 *
124
	 * @since 1.8
125
	 *
126
	 * @param int $subjectId
127
	 * @param DIWikiPage $subject
128
	 */
129 254
	private function initSemanticDataCache( $subjectId, DIWikiPage $subject ) {
130
131
		// *** Prepare the cache ***//
132 254
		if ( !array_key_exists( $subjectId, $this->store->m_semdata ) ) { // new cache entry
133 165
			$this->store->m_semdata[$subjectId] = new SMWSql3StubSemanticData( $subject, $this->store, false );
134 165
			$this->store->m_sdstate[$subjectId] = array();
135
		}
136
137
		// Issue #622
138
		// If a redirect was cached preceding this request and points to the same
139
		// subject id ensure that in all cases the requested subject matches with
140
		// the selected DB id
141 254
		if ( $this->store->m_semdata[$subjectId]->getSubject()->getHash() !== $subject->getHash() ) {
142 24
			$this->store->m_semdata[$subjectId] = new SMWSql3StubSemanticData( $subject, $this->store, false );
143 24
			$this->store->m_sdstate[$subjectId] = array();
144
		}
145
146 254
		if ( ( count( $this->store->m_semdata ) > 20 ) && ( self::$in_getSemanticData == 1 ) ) {
147
			// prevent memory leak;
148
			// It is not so easy to find the sweet spot between cache size and performance gains (both memory and time),
149
			// The value of 20 was chosen by profiling runtimes for large inline queries and heavily annotated pages.
150
			// However, things might have changed in the meantime ...
151 25
			$this->store->m_semdata = array( $subjectId => $this->store->m_semdata[$subjectId] );
152 25
			$this->store->m_sdstate = array( $subjectId => $this->store->m_sdstate[$subjectId] );
153
		}
154 254
	}
155
156
	/**
157
	 * Fetch the data storder about one subject in one particular table.
158
	 *
159
	 * @param integer $sid
160
	 * @param DIWikiPage $subject
161
	 * @param TableDefinition $proptable
162
	 *
163
	 * @return SMWSemanticData
164
	 */
165 245
	private function getSemanticDataFromTable( $sid, DIWikiPage $subject, TableDefinition $proptable ) {
166
		// Do not clear the cache when called recursively.
167 245
		self::$in_getSemanticData++;
168
169 245
		$this->initSemanticDataCache( $sid, $subject );
170
171 245
		if ( array_key_exists( $proptable->getName(), $this->store->m_sdstate[$sid] ) ) {
172 242
			self::$in_getSemanticData--;
173 242
			return $this->store->m_semdata[$sid];
174
		}
175
176
		// *** Read the data ***//
177 122
		$data = $this->fetchSemanticData( $sid, $subject, $proptable );
178 122
		foreach ( $data as $d ) {
179 69
			$this->store->m_semdata[$sid]->addPropertyStubValue( reset( $d ), end( $d ) );
180
		}
181 122
		$this->store->m_sdstate[$sid][$proptable->getName()] = true;
182
183 122
		self::$in_getSemanticData--;
184 122
		return $this->store->m_semdata[$sid];
185
	}
186
187
	/**
188
	 * @see SMWStore::getPropertyValues
189
	 *
190
	 * @todo Retrieving all sortkeys (values for _SKEY with $subject null)
191
	 * is not supported. An empty array will be given.
192
	 *
193
	 * @since 1.8
194
	 *
195
	 * @param $subject mixed DIWikiPage or null
196
	 * @param $property SMWDIProperty
197
	 * @param $requestOptions SMWRequestOptions
198
	 *
199
	 * @return SMWDataItem[]
200
	 */
201 219
	public function getPropertyValues( $subject, SMWDIProperty $property, $requestOptions = null ) {
202
203 219
		if ( $property->isInverse() ) { // inverses are working differently
204 4
			$noninverse = new SMW\DIProperty( $property->getKey(), false );
205 4
			$result = $this->getPropertySubjects( $noninverse, $subject, $requestOptions );
206 219
		} elseif ( !is_null( $subject ) ) { // subject given, use semantic data cache
207 219
			$sid = $this->store->smwIds->getSMWPageID( $subject->getDBkey(),
208 219
				$subject->getNamespace(), $subject->getInterwiki(),
209 219
				$subject->getSubobjectName(), true );
210 219
			if ( $sid == 0 ) {
211 179
				$result = array();
212 191
			} elseif ( $property->getKey() == '_SKEY' ) {
213 5
				$this->store->smwIds->getSMWPageIDandSort( $subject->getDBkey(),
214 5
				$subject->getNamespace(), $subject->getInterwiki(),
215 5
				$subject->getSubobjectName(), $sortKey, true );
216 5
				$sortKeyDi = new SMWDIBlob( $sortKey );
217 5
				$result = $this->store->applyRequestOptions( array( $sortKeyDi ), $requestOptions );
218
			} else {
219 191
				$propTableId = $this->store->findPropertyTableID( $property );
220 191
				$proptables =  $this->store->getPropertyTables();
221
222 191
				if ( !isset( $proptables[$propTableId] ) ) {
223
					return array();
224
				}
225
226 191
				$sd = $this->getSemanticDataFromTable( $sid, $subject, $proptables[$propTableId] );
227 219
				$result = $this->store->applyRequestOptions( $sd->getPropertyValues( $property ), $requestOptions );
228
			}
229
		} else { // no subject given, get all values for the given property
230 2
			$pid = $this->store->smwIds->getSMWPropertyID( $property );
231 2
			$tableid =  $this->store->findPropertyTableID( $property );
232
233 2
			if ( ( $pid == 0 ) || ( $tableid === '' ) ) {
234
				return array();
235
			}
236
237 2
			$proptables =  $this->store->getPropertyTables();
238 2
			$data = $this->fetchSemanticData( $pid, $property, $proptables[$tableid], false, $requestOptions );
239 2
			$result = array();
240 2
			$propertyTypeId = $property->findPropertyTypeID();
241 2
			$propertyDiId = DataTypeRegistry::getInstance()->getDataItemId( $propertyTypeId );
242
243 2
			foreach ( $data as $dbkeys ) {
244
				try {
245 2
					$diHandler = $this->store->getDataItemHandlerForDIType( $propertyDiId );
246 2
					$result[] = $diHandler->dataItemFromDBKeys( $dbkeys );
247 2
				} catch ( SMWDataItemException $e ) {
248
					// maybe type assignment changed since data was stored;
249
					// don't worry, but we can only drop the data here
250
				}
251
			}
252
		}
253
254
255 219
		return $result;
256
	}
257
258
	/**
259
	 * Helper function for reading all data for from a given property table
260
	 * (specified by an SMWSQLStore3Table object), based on certain
261
	 * restrictions. The function can filter data based on the subject (1)
262
	 * or on the property it belongs to (2) -- but one of those must be
263
	 * done. The Boolean $issubject is true for (1) and false for (2).
264
	 *
265
	 * In case (1), the first two parameters are taken to refer to a
266
	 * subject; in case (2) they are taken to refer to a property. In any
267
	 * case, the retrieval is limited to the specified $proptable. The
268
	 * parameters are an internal $id (of a subject or property), and an
269
	 * $object (being an DIWikiPage or SMWDIProperty). Moreover, when
270
	 * filtering by property, it is assumed that the given $proptable
271
	 * belongs to the property: if it is a table with fixed property, it
272
	 * will not be checked that this is the same property as the one that
273
	 * was given in $object.
274
	 *
275
	 * In case (1), the result in general is an array of pairs (arrays of
276
	 * size 2) consisting of a property key (string), and DB keys (array if
277
	 * many, string if one) from which a datvalue object for this value can
278
	 * be built. It is possible that some of the DB keys are based on
279
	 * internal objects; these will be represented by similar result arrays
280
	 * of (recursive calls of) fetchSemanticData().
281
	 *
282
	 * In case (2), the result is simply an array of DB keys (array)
283
	 * without the property keys. Container objects will be encoded with
284
	 * nested arrays like in case (1).
285
	 *
286
	 * @todo Maybe share DB handler; asking for it seems to take quite some
287
	 * time and we do not want to change it in one call.
288
	 *
289
	 * @param integer $id
290
	 * @param SMWDataItem $object
291
	 * @param TableDefinition $propTable
292
	 * @param boolean $isSubject
293
	 * @param SMWRequestOptions $requestOptions
294
	 *
295
	 * @return array
296
	 */
297 124
	private function fetchSemanticData( $id, SMWDataItem $object = null, TableDefinition $propTable, $isSubject = true, SMWRequestOptions $requestOptions = null ) {
298
		// stop if there is not enough data:
299
		// properties always need to be given as object,
300
		// subjects at least if !$proptable->idsubject
301 124
		if ( ( $id == 0 ) ||
302 124
			( is_null( $object ) && ( !$isSubject || !$propTable->usesIdSubject() ) ) ) {
303
				return array();
304
		}
305
306 124
		$result = array();
307 124
		$db = $this->store->getConnection();
308
309 124
		$diHandler = $this->store->getDataItemHandlerForDIType( $propTable->getDiType() );
310
311
		// ***  First build $from, $select, and $where for the DB query  ***//
312 124
		$from = $db->tableName( $propTable->getName() ); // always use actual table
313
314 124
		$select = '';
315 124
		$where  = '';
316
317 124
		if ( $isSubject ) { // restrict subject, select property
318 122
			$where .= ( $propTable->usesIdSubject() ) ? 's_id=' . $db->addQuotes( $id ) :
319 32
					  's_title=' . $db->addQuotes( $object->getDBkey() ) .
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getDBkey() does only exist in the following sub-classes of SMWDataItem: SMWDIWikiPage, SMW\DIWikiPage. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
320 122
					  ' AND s_namespace=' . $db->addQuotes( $object->getNamespace() );
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class SMWDataItem as the method getNamespace() does only exist in the following sub-classes of SMWDataItem: SMWDIWikiPage, SMW\DIWikiPage. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
321 122
			if ( !$propTable->isFixedPropertyTable() ) { // select property name
322 68
				$from .= ' INNER JOIN ' . $db->tableName( SMWSql3SmwIds::TABLE_NAME ) . ' AS p ON p_id=p.smw_id';
323 122
				$select .= 'p.smw_title as prop';
324
			} // else: fixed property, no select needed
325 2
		} elseif ( !$propTable->isFixedPropertyTable() ) { // restrict property only
326 2
			$where .= 'p_id=' . $db->addQuotes( $id );
327
		}
328
329 124
		$valuecount = 0;
330
		// Don't use DISTINCT for value of one subject:
331 124
		$usedistinct = !$isSubject;
332
333 124
		$valueField = $diHandler->getIndexField();
334 124
		$labelField = $diHandler->getLabelField();
335 124
		$fields = $diHandler->getFetchFields();
336 124
		foreach ( $fields as $fieldname => $fieldType ) { // select object column(s)
337 124
			if ( $fieldType === FieldType::FIELD_ID ) { // get data from ID table
338 97
				$from .= ' INNER JOIN ' . $db->tableName( SMWSql3SmwIds::TABLE_NAME ) . " AS o$valuecount ON $fieldname=o$valuecount.smw_id";
339 97
				$select .= ( ( $select !== '' ) ? ',' : '' ) .
340 97
					"$fieldname AS id$valuecount" .
341 97
					",o$valuecount.smw_title AS v$valuecount" .
342 97
					",o$valuecount.smw_namespace AS v" . ( $valuecount + 1 ) .
343 97
					",o$valuecount.smw_iw AS v" . ( $valuecount + 2 ) .
344 97
					",o$valuecount.smw_sortkey AS v" . ( $valuecount + 3 ) .
345 97
					",o$valuecount.smw_subobject AS v" . ( $valuecount + 4 );
346
347 97
				if ( $valueField == $fieldname ) {
348 97
					$valueField = "o$valuecount.smw_sortkey";
349
				}
350 97
				if ( $labelField == $fieldname ) {
351 97
					$labelField = "o$valuecount.smw_sortkey";
352
				}
353
354 97
				$valuecount += 4;
355
			} else {
356 108
				$select .= ( ( $select !== '' ) ? ',' : '' ) .
357 108
					"$fieldname AS v$valuecount";
358
			}
359
360 124
			$valuecount += 1;
361
		}
362
363
		// Postgres
364
		// Function: SMWSQLStore3Readers::fetchSemanticData
365
		// Error: 42P10 ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
366 124
		if ( strpos( $select, $valueField ) === false ) {
367 64
			$select .= ", $valueField AS v" . ( $valuecount + 1 );
368
		}
369
370 124
		if ( !$isSubject ) { // Apply sorting/string matching; only with given property
371 2
			$where .= $this->store->getSQLConditions( $requestOptions, $valueField, $labelField, $where !== '' );
372
		} else {
373 122
			$valueField = '';
374
		}
375
376
		// ***  Now execute the query and read the results  ***//
377 124
		$res = $db->select( $from, $select, $where, __METHOD__,
378 124
				( $usedistinct ?
379 2
					$this->store->getSQLOptions( $requestOptions, $valueField ) + array( 'DISTINCT' ) :
380 124
					$this->store->getSQLOptions( $requestOptions, $valueField )
381
				) );
382
383 124
		foreach ( $res as $row ) {
384
385 71
			$valueHash = '';
386
387 71
			if ( $isSubject ) { // use joined or predefined property name
388 69
				$propertykey = $propTable->isFixedPropertyTable() ? $propTable->getFixedProperty() : $row->prop;
389 69
				$valueHash = $propertykey;
390
			}
391
392
			// Use enclosing array only for results with many values:
393 71
			if ( $valuecount > 1 ) {
394 67
				$valuekeys = array();
395 67
				for ( $i = 0; $i < $valuecount; $i += 1 ) { // read the value fields from the current row
396 67
					$fieldname = "v$i";
397 67
					$valuekeys[] = $row->$fieldname;
398
				}
399
			} else {
400 63
				$valuekeys = $row->v0;
401
			}
402
403
			// #Issue 615
404
			// If the iw field contains a redirect marker then remove it
405 71
			if ( isset( $valuekeys[2] ) && ( $valuekeys[2] === SMW_SQL3_SMWREDIIW || $valuekeys[2] === SMW_SQL3_SMWDELETEIW ) ) {
406 7
				$valuekeys[2] = '';
407
			}
408
409
			// The valueHash prevents from inserting duplicate entries of the same content
410 71
			$valueHash = $valuecount > 1 ? md5( $valueHash . implode( '#', $valuekeys ) ) : md5( $valueHash . $valuekeys );
411
412
			// Filter out any accidentally retrieved internal things (interwiki starts with ":"):
413 71
			if ( $valuecount < 3 ||
414 55
				implode( '', $fields ) !== FieldType::FIELD_ID ||
415 55
				$valuekeys[2] === '' ||
416 71
				$valuekeys[2]{0} != ':' ) {
417
418 71
				if ( isset( $result[$valueHash] ) ) {
419
					wfDebugLog( 'smw', __METHOD__ . " Duplicate entry for {$propertykey} with " . ( is_array( $valuekeys ) ? implode( ',', $valuekeys ) : $valuekeys ) . "\n" );
420
				}
421
422 71
				$result[$valueHash] = $isSubject ? array( $propertykey, $valuekeys ) : $valuekeys;
423
			}
424
		}
425
426 124
		$db->freeResult( $res );
427
428 124
		return $result;
429
	}
430
431
	/**
432
	 * @see SMWStore::getPropertySubjects
433
	 *
434
	 * @todo This method cannot retrieve subjects for sortkeys, i.e., for
435
	 * property _SKEY. Only empty arrays will be returned there.
436
	 *
437
	 * @param SMWDIProperty $property
438
	 * @param SMWDataItem|null $value
439
	 * @param SMWRequestOptions|null $requestOptions
440
	 *
441
	 * @return array of DIWikiPage
442
	 */
443 165
	public function getPropertySubjects( SMWDIProperty $property, SMWDataItem $value = null, SMWRequestOptions $requestOptions = null ) {
444
		/// TODO: should we share code with #ask query computation here? Just use queries?
445
446 165
		if ( $property->isInverse() ) { // inverses are working differently
447
			$noninverse = new SMW\DIProperty( $property->getKey(), false );
448
			$result = $this->getPropertyValues( $value, $noninverse, $requestOptions );
449
			return $result;
450
		}
451
452
		// #1222, Filter those where types don't match (e.g property = _txt
453
		// and value = _wpg)
454 165
		if ( $value !== null && DataTypeRegistry::getInstance()->getDataItemId( $property->findPropertyTypeID() ) !== $value->getDIType() ) {
455 1
			return array();
456
		}
457
458
		// First build $select, $from, and $where for the DB query
459 165
		$where = $from = '';
0 ignored issues
show
$from is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
460 165
		$pid = $this->store->smwIds->getSMWPropertyID( $property );
461 165
		$tableid =  $this->store->findPropertyTableID( $property );
462
463 165
		if ( ( $pid == 0 ) || ( $tableid === '' ) ) {
464 152
			return array();
465
		}
466
467 165
		$proptables =  $this->store->getPropertyTables();
468 165
		$proptable = $proptables[$tableid];
469 165
		$db = $this->store->getConnection();
470
471 165
		if ( $proptable->usesIdSubject() ) { // join with ID table to get title data
472 165
			$from = $db->tableName( SMWSql3SmwIds::TABLE_NAME ) . " INNER JOIN " . $db->tableName( $proptable->getName() ) . " AS t1 ON t1.s_id=smw_id";
473 165
			$select = 'smw_title, smw_namespace, smw_iw, smw_sortkey, smw_subobject';
474
		} else { // no join needed, title+namespace as given in proptable
475 4
			$from = $db->tableName( $proptable->getName() ) . " AS t1";
476 4
			$select = 's_title AS smw_title, s_namespace AS smw_namespace, \'\' AS smw_iw, s_title AS smw_sortkey, \'\' AS smw_subobject';
477
		}
478
479 165
		if ( !$proptable->isFixedPropertyTable() ) {
480 158
			$where .= ( $where ? ' AND ' : '' ) . "t1.p_id=" . $db->addQuotes( $pid );
481
		}
482
483 165
		$this->prepareValueQuery( $from, $where, $proptable, $value, 1 );
0 ignored issues
show
It seems like $value defined by parameter $value on line 443 can be null; however, SMWSQLStore3Readers::prepareValueQuery() 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...
484
485
		// ***  Now execute the query and read the results  ***//
486 165
		$result = array();
487
488 165
		if ( !$proptable->isFixedPropertyTable() ) {
489 158
			if ( $where !== '' && strpos( SMW_SQL3_SMWIW_OUTDATED, $where ) === false ) {
490 158
				$where .= " AND smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWIW_OUTDATED ) . " AND smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWDELETEIW );
491
			} else {
492
				$where .= " smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWIW_OUTDATED ) . " AND smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWDELETEIW );
493
			}
494
		}
495
496 165
		$res = $db->select( $from, 'DISTINCT ' . $select,
497 165
		                    $where . $this->store->getSQLConditions( $requestOptions, 'smw_sortkey', 'smw_sortkey', $where !== '' ),
498 165
		                    __METHOD__, $this->store->getSQLOptions( $requestOptions, 'smw_sortkey' ) );
499
500 165
		$diHandler = $this->store->getDataItemHandlerForDIType( SMWDataItem::TYPE_WIKIPAGE );
501
502 165
		foreach ( $res as $row ) {
503
			try {
504 15
				if ( $row->smw_iw === '' || $row->smw_iw{0} != ':' ) { // filter special objects
505 15
					$result[] = $diHandler->dataItemFromDBKeys( array_values( (array)$row ) );
506
				}
507 15
			} catch ( DataItemHandlerException $e ) {
508
				// silently drop data, should be extremely rare and will usually fix itself at next edit
509
			}
510
		}
511
512 165
		$db->freeResult( $res );
513
514 165
		return $result;
515
	}
516
517
518
	/**
519
	 * Helper function to compute from and where strings for a DB query so that
520
	 * only rows of the given value object match. The parameter $tableindex
521
	 * counts that tables used in the query to avoid duplicate table names. The
522
	 * parameter $proptable provides the SMWSQLStore3Table object that is
523
	 * queried.
524
	 *
525
	 * @todo Maybe do something about redirects. The old code was
526
	 * $oid = $this->store->smwIds->getSMWPageID($value->getDBkey(),$value->getNamespace(),$value->getInterwiki(),false);
527
	 *
528
	 * @note This method cannot handle DIContainer objects with sortkey
529
	 * properties correctly. This should never occur, but it would be good
530
	 * to fail in a more controlled way if it ever does.
531
	 *
532
	 * @param string $from
533
	 * @param string $where
534
	 * @param TableDefinition $propTable
535
	 * @param SMWDataItem $value
536
	 * @param integer $tableIndex
537
	 */
538 168
	private function prepareValueQuery( &$from, &$where, TableDefinition $propTable, $value, $tableIndex = 1 ) {
539 168
		$db = $this->store->getConnection();
540
541 168
		if ( $value instanceof SMWDIContainer ) { // recursive handling of containers
542
			$keys = array_keys( $propTable->getFields( $this->store ) );
543
			$joinfield = "t$tableIndex." . reset( $keys ); // this must be a type 'p' object
544
			$proptables =  $this->store->getPropertyTables();
545
			$semanticData = $value->getSemanticData();
546
547
			foreach ( $semanticData->getProperties() as $subproperty ) {
548
				$tableid =  $this->store->findPropertyTableID( $subproperty );
549
				$subproptable = $proptables[$tableid];
550
551
				foreach ( $semanticData->getPropertyValues( $subproperty ) as $subvalue ) {
0 ignored issues
show
The expression $semanticData->getPropertyValues($subproperty) of type array|object<SMWDataItem> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
552
					$tableIndex++;
553
554
					if ( $subproptable->usesIdSubject() ) { // simply add property table to check values
555
						$from .= " INNER JOIN " . $db->tableName( $subproptable->getName() ) . " AS t$tableIndex ON t$tableIndex.s_id=$joinfield";
556
					} else { // exotic case with table that uses subject title+namespace in container object (should never happen in SMW core)
557
						$from .= " INNER JOIN " . $db->tableName( SMWSql3SmwIds::TABLE_NAME ) . " AS ids$tableIndex ON ids$tableIndex.smw_id=$joinfield" .
558
						         " INNER JOIN " . $db->tableName( $subproptable->getName() ) . " AS t$tableIndex ON " .
559
						         "t$tableIndex.s_title=ids$tableIndex.smw_title AND t$tableIndex.s_namespace=ids$tableIndex.smw_namespace";
560
					}
561
562
					if ( !$subproptable->isFixedPropertyTable() ) { // the ID we get should be !=0, so no point in filtering the converse
563
						$where .= ( $where ? ' AND ' : '' ) . "t$tableIndex.p_id=" . $db->addQuotes( $this->store->smwIds->getSMWPropertyID( $subproperty ) );
564
					}
565
566
					$this->prepareValueQuery( $from, $where, $subproptable, $subvalue, $tableIndex );
567
				}
568
			}
569 168
		} elseif ( !is_null( $value ) ) { // add conditions for given value
570 167
			$diHandler = $this->store->getDataItemHandlerForDIType( $value->getDIType() );
571 167
			foreach ( $diHandler->getWhereConds( $value ) as $fieldname => $value ) {
572 167
				$where .= ( $where ? ' AND ' : '' ) . "t$tableIndex.$fieldname=" . $db->addQuotes( $value );
573
			}
574
		}
575 168
	}
576
577
	/**
578
	 * @see SMWStore::getAllPropertySubjects
579
	 *
580
	 * @param SMWDIProperty $property
581
	 * @param SMWRequestOptions $requestOptions
582
	 *
583
	 * @return array of DIWikiPage
584
	 */
585 158
	public function getAllPropertySubjects( SMWDIProperty $property, SMWRequestOptions $requestOptions = null ) {
586 158
		$result = $this->getPropertySubjects( $property, null, $requestOptions );
587
588 158
		return $result;
589
	}
590
591
	/**
592
	 * @see Store::getProperties
593
	 *
594
	 * @param DIWikiPage $subject
595
	 * @param SMWRequestOptions|null $requestOptions
596
	 *
597
	 * @return SMWDataItem[]
598
	 */
599 2
	public function getProperties( DIWikiPage $subject, SMWRequestOptions $requestOptions = null ) {
600 2
		$sid = $this->store->smwIds->getSMWPageID(
601 2
			$subject->getDBkey(),
602 2
			$subject->getNamespace(),
603 2
			$subject->getInterwiki(),
604 2
			$subject->getSubobjectName()
605
		);
606
607 2
		if ( $sid == 0 ) { // no id, no page, no properties
608 2
			return array();
609
		}
610
611
		$db = $this->store->getConnection();
612
		$result = array();
613
614
		// potentially need to get more results, since options apply to union
615
		if ( $requestOptions !== null ) {
616
			$suboptions = clone $requestOptions;
617
			$suboptions->limit = $requestOptions->limit + $requestOptions->offset;
618
			$suboptions->offset = 0;
619
		} else {
620
			$suboptions = null;
621
		}
622
623
		foreach ( $this->store->getPropertyTables() as $propertyTable ) {
624
			if ( $propertyTable->usesIdSubject() ) {
625
				$where = 's_id=' . $db->addQuotes( $sid );
626
			} elseif ( $subject->getInterwiki() === '' ) {
627
				$where = 's_title=' . $db->addQuotes( $subject->getDBkey() ) . ' AND s_namespace=' . $db->addQuotes( $subject->getNamespace() );
628
			} else { // subjects with non-emtpy interwiki cannot have properties
629
				continue;
630
			}
631
632
			if ( $propertyTable->isFixedPropertyTable() ) {
633
				// just check if subject occurs in table
634
				$res = $db->select(
635
					$propertyTable->getName(),
636
					'*',
637
					$where,
638
					__METHOD__,
639
					array( 'LIMIT' => 1 )
640
				);
641
642
				if ( $db->numRows( $res ) > 0 ) {
643
					$result[] = new SMW\DIProperty( $propertyTable->getFixedProperty() );
644
				}
645
646
647
			} else {
648
				// select all properties
649
				$from = $db->tableName( $propertyTable->getName() );
650
651
				$from .= " INNER JOIN " . $db->tableName( SMWSql3SmwIds::TABLE_NAME ) . " ON smw_id=p_id";
652
				$res = $db->select( $from, 'DISTINCT smw_title,smw_sortkey',
653
					// (select sortkey since it might be used in ordering (needed by Postgres))
654
					$where . $this->store->getSQLConditions( $suboptions, 'smw_sortkey', 'smw_sortkey' ),
655
					__METHOD__, $this->store->getSQLOptions( $suboptions, 'smw_sortkey' ) );
656
				foreach ( $res as $row ) {
657
					$result[] = new SMW\DIProperty( $row->smw_title );
658
				}
659
			}
660
661
			$db->freeResult( $res );
662
		}
663
664
		// apply options to overall result
665
		$result = $this->store->applyRequestOptions( $result, $requestOptions );
666
667
668
		return $result;
669
	}
670
671
	/**
672
	 * Implementation of SMWStore::getInProperties(). This function is meant to
673
	 * be used for finding properties that link to wiki pages.
674
	 *
675
	 * @since 1.8
676
	 * @see SMWStore::getInProperties
677
	 *
678
	 * @param SMWDataItem $value
679
	 * @param SMWRequestOptions|null $requestOptions
680
	 *
681
	 * @return array of SMWWikiPageValue
682
	 */
683 18
	public function getInProperties( SMWDataItem $value, SMWRequestOptions $requestOptions = null ) {
684
685 18
		$db = $this->store->getConnection();
686 18
		$result = array();
687
688
		// Potentially need to get more results, since options apply to union.
689 18
		if ( $requestOptions !== null ) {
690 4
			$subOptions = clone $requestOptions;
691 4
			$subOptions->limit = $requestOptions->limit + $requestOptions->offset;
692 4
			$subOptions->offset = 0;
693
		} else {
694 14
			$subOptions = null;
695
		}
696
697 18
		$diType = $value->getDIType();
698
699 18
		foreach ( $this->store->getPropertyTables() as $proptable ) {
700 18
			if ( $diType != $proptable->getDiType() ) {
701 18
				continue;
702
			}
703
704 18
			$where = $from = '';
0 ignored issues
show
$from is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
705 18
			if ( !$proptable->isFixedPropertyTable() ) { // join ID table to get property titles
706 18
				$from = $db->tableName( SMWSql3SmwIds::TABLE_NAME ) . " INNER JOIN " . $db->tableName( $proptable->getName() ) . " AS t1 ON t1.p_id=smw_id";
707 18
				$this->prepareValueQuery( $from, $where, $proptable, $value, 1 );
708
709 18
				$where .= " AND smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWIW_OUTDATED ) . " AND smw_iw!=" . $db->addQuotes( SMW_SQL3_SMWDELETEIW );
710
711 18
				$res = $db->select( $from, 'DISTINCT smw_title,smw_sortkey,smw_iw',
712
						// select sortkey since it might be used in ordering (needed by Postgres)
713 18
						$where . $this->store->getSQLConditions( $subOptions, 'smw_sortkey', 'smw_sortkey', $where !== '' ),
714 18
						__METHOD__, $this->store->getSQLOptions( $subOptions, 'smw_sortkey' ) );
715
716 18
				foreach ( $res as $row ) {
717
					try {
718 3
						$result[] = new SMW\DIProperty( $row->smw_title );
719 18
					} catch (SMWDataItemException $e) {
720
						// has been observed to happen (empty property title); cause unclear; ignore this data
721
					}
722
				}
723
			} else {
724 18
				$from = $db->tableName( $proptable->getName() ) . " AS t1";
725 18
				$this->prepareValueQuery( $from, $where, $proptable, $value, 1 );
726 18
				$res = $db->select( $from, '*', $where, __METHOD__, array( 'LIMIT' => 1 ) );
727
728 18
				if ( $db->numRows( $res ) > 0 ) {
729 10
					$result[] = new SMW\DIProperty( $proptable->getFixedProperty() );
730
				}
731
			}
732 18
			$db->freeResult( $res );
733
		}
734
735 18
		$result = $this->store->applyRequestOptions( $result, $requestOptions ); // apply options to overall result
736
737 18
		return $result;
738
	}
739
740
}
741