/** * Checks if additional select columns are readable as volatile data even if we hit the cache while fetching entity. * * https://github.com/Elgg/Elgg/issues/5544 */ public function testSqlAdditionalSelectsAsVolatileDataWithCache() { // remove ignore access as it disables entity cache $access = elgg_set_ignore_access(false); // may not have groups in DB - let's create one $group = new ElggGroup(); $group->name = 'test_group'; $group->access_id = ACCESS_PUBLIC; $this->assertTrue($group->save() !== false); foreach (array('site', 'user', 'group', 'object') as $type) { $entities = elgg_get_entities(array('type' => $type, 'selects' => array('42 as added_col3'), 'limit' => 1)); $entity = array_shift($entities); $this->assertTrue($entity instanceof ElggEntity); $this->assertEqual($entity->added_col3, null, "Additional select columns are leaking to attributes for " . get_class($entity)); $this->assertEqual($entity->getVolatileData('select:added_col3'), 42); // make sure we have cached the entity $this->assertNotEqual(false, _elgg_retrieve_cached_entity($entity->guid)); } // run these again but with different value to make sure cache does not interfere foreach (array('site', 'user', 'group', 'object') as $type) { $entities = elgg_get_entities(array('type' => $type, 'selects' => array('64 as added_col3'), 'limit' => 1)); $entity = array_shift($entities); $this->assertTrue($entity instanceof ElggEntity); $this->assertEqual($entity->added_col3, null, "Additional select columns are leaking to attributes for " . get_class($entity)); $this->assertEqual($entity->getVolatileData('select:added_col3'), 64, "Failed to overwrite volatile data in cached entity"); } elgg_set_ignore_access($access); $group->delete(); }
/** * Retrieve a entity from the cache. * * @param int $guid The guid * * @return ElggEntity|bool false if entity not cached, or not fully loaded * @access private * @deprecated 1.8 */ function retrieve_cached_entity($guid) { elgg_deprecated_notice('retrieve_cached_entity() is a private function and should not be used.', 1.8); return _elgg_retrieve_cached_entity($guid); }
/** * Get entities in any order checking cache first * * @param int[] $guids * @return \ElggEntity[] */ protected function getEntities(array $guids) { // most objects are already preloaded $entities = array(); $fetch_guids = array(); foreach ($guids as $guid) { $entity = _elgg_retrieve_cached_entity($guid); if ($entity) { $entities[] = $entity; } else { $fetch_guids[] = $guid; } } if ($fetch_guids) { $fetched = elgg_get_entities(array('guids' => $fetch_guids)); array_splice($entities, count($entities), 0, $fetched); } return $entities; }
/** * Prefetch entities that will be displayed in the river. * * @param \ElggRiverItem[] $river_items * @access private */ function _elgg_prefetch_river_entities(array $river_items) { // prefetch objects, subjects and targets $guids = array(); foreach ($river_items as $item) { if ($item->subject_guid && !_elgg_retrieve_cached_entity($item->subject_guid)) { $guids[$item->subject_guid] = true; } if ($item->object_guid && !_elgg_retrieve_cached_entity($item->object_guid)) { $guids[$item->object_guid] = true; } if ($item->target_guid && !_elgg_retrieve_cached_entity($item->target_guid)) { $guids[$item->target_guid] = true; } } if ($guids) { // The entity cache only holds 256. We don't want to bump out any plugins. $guids = array_slice($guids, 0, 200, true); // return value unneeded, just priming cache elgg_get_entities(array('guids' => array_keys($guids), 'limit' => 0, 'distinct' => false)); } // prefetch object containers, in case they were not in the targets $guids = array(); foreach ($river_items as $item) { $object = $item->getObjectEntity(); if ($object->container_guid && !_elgg_retrieve_cached_entity($object->container_guid)) { $guids[$object->container_guid] = true; } } if ($guids) { $guids = array_slice($guids, 0, 200, true); elgg_get_entities(array('guids' => array_keys($guids), 'limit' => 0, 'distinct' => false, 'type' => 'group')); } // Note: We've tried combining the above ege() calls into one (pulling containers at the same time). // Although it seems like it would reduce queries, it added some. o_O }
/** * Return entities from an SQL query generated by elgg_get_entities. * * @param string $sql * @param \ElggBatch $batch * @return \ElggEntity[] * * @access private * @throws \LogicException */ function fetchFromSql($sql, \ElggBatch $batch = null) { static $plugin_subtype; if (null === $plugin_subtype) { $plugin_subtype = get_subtype_id('object', 'plugin'); } // Keys are types, values are columns that, if present, suggest that the secondary // table is already JOINed. Note it's OK if guess incorrectly because entity load() // will fetch any missing attributes. $types_to_optimize = array('object' => 'title', 'user' => 'password', 'group' => 'name', 'site' => 'url'); $rows = _elgg_services()->db->getData($sql); // guids to look up in each type $lookup_types = array(); // maps GUIDs to the $rows key $guid_to_key = array(); if (isset($rows[0]->type, $rows[0]->subtype) && $rows[0]->type === 'object' && $rows[0]->subtype == $plugin_subtype) { // Likely the entire resultset is plugins, which have already been optimized // to JOIN the secondary table. In this case we allow retrieving from cache, // but abandon the extra queries. $types_to_optimize = array(); } // First pass: use cache where possible, gather GUIDs that we're optimizing foreach ($rows as $i => $row) { if (empty($row->guid) || empty($row->type)) { throw new \LogicException('Entity row missing guid or type'); } $entity = _elgg_retrieve_cached_entity($row->guid); if ($entity) { $entity->refresh($row); $rows[$i] = $entity; continue; } if (isset($types_to_optimize[$row->type])) { // check if row already looks JOINed. if (isset($row->{$types_to_optimize[$row->type]})) { // Row probably already contains JOINed secondary table. Don't make another query just // to pull data that's already there continue; } $lookup_types[$row->type][] = $row->guid; $guid_to_key[$row->guid] = $i; } } // Do secondary queries and merge rows if ($lookup_types) { $dbprefix = _elgg_services()->config->get('dbprefix'); foreach ($lookup_types as $type => $guids) { $set = "(" . implode(',', $guids) . ")"; $sql = "SELECT * FROM {$dbprefix}{$type}s_entity WHERE guid IN {$set}"; $secondary_rows = _elgg_services()->db->getData($sql); if ($secondary_rows) { foreach ($secondary_rows as $secondary_row) { $key = $guid_to_key[$secondary_row->guid]; // cast to arrays to merge then cast back $rows[$key] = (object) array_merge((array) $rows[$key], (array) $secondary_row); } } } } // Second pass to finish conversion foreach ($rows as $i => $row) { if ($row instanceof \ElggEntity) { continue; } else { try { $rows[$i] = entity_row_to_elggstar($row); } catch (IncompleteEntityException $e) { // don't let incomplete entities throw fatal errors unset($rows[$i]); // report incompletes to the batch process that spawned this query if ($batch) { $batch->reportIncompleteEntity($row); } } } } return $rows; }
/** * Prefetch entities that will be displayed in the river. * * @param ElggRiverItem[] $river_items * @access private */ function _elgg_prefetch_river_entities(array $river_items) { // prefetch objects and subjects $guids = array(); foreach ($river_items as $item) { if ($item->subject_guid && !_elgg_retrieve_cached_entity($item->subject_guid)) { $guids[$item->subject_guid] = true; } if ($item->object_guid && !_elgg_retrieve_cached_entity($item->object_guid)) { $guids[$item->object_guid] = true; } } if ($guids) { // avoid creating oversized query // @todo how to better handle this? $guids = array_slice($guids, 0, 300, true); // return value unneeded, just priming cache elgg_get_entities(array('guids' => array_keys($guids), 'limit' => 0)); } // prefetch object containers $guids = array(); foreach ($river_items as $item) { $object = $item->getObjectEntity(); if ($object->container_guid && !_elgg_retrieve_cached_entity($object->container_guid)) { $guids[$object->container_guid] = true; } } if ($guids) { $guids = array_slice($guids, 0, 300, true); elgg_get_entities(array('guids' => array_keys($guids), 'limit' => 0)); } }
/** * Get user by username * * @param string $username The user's username * * @return \ElggUser|false Depending on success */ function getByUsername($username) { global $USERNAME_TO_GUID_MAP_CACHE; // Fixes #6052. Username is frequently sniffed from the path info, which, // unlike $_GET, is not URL decoded. If the username was not URL encoded, // this is harmless. $username = rawurldecode($username); $username = sanitise_string($username); $access = _elgg_get_access_where_sql(); // Caching if (isset($USERNAME_TO_GUID_MAP_CACHE[$username]) && _elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username])) { return _elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); } $query = "SELECT e.* FROM {$this->CONFIG->dbprefix}users_entity u\n\t\t\tJOIN {$this->CONFIG->dbprefix}entities e ON e.guid = u.guid\n\t\t\tWHERE u.username = '******' AND {$access}"; $entity = _elgg_services()->db->getDataRow($query, 'entity_row_to_elggstar'); if ($entity) { $USERNAME_TO_GUID_MAP_CACHE[$username] = $entity->guid; } else { $entity = false; } return $entity; }
/** * Get user by session code * * @param string $code The session code * * @return ElggUser|false Depending on success */ function get_user_by_code($code) { global $CONFIG, $CODE_TO_GUID_MAP_CACHE; $code = sanitise_string($code); $access = get_access_sql_suffix('e'); // Caching if (isset($CODE_TO_GUID_MAP_CACHE[$code]) && _elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code])) { return _elgg_retrieve_cached_entity($CODE_TO_GUID_MAP_CACHE[$code]); } $query = "SELECT e.* from {$CONFIG->dbprefix}users_entity u\n\t\tjoin {$CONFIG->dbprefix}entities e on e.guid=u.guid\n\t\twhere u.code='{$code}' and {$access}"; $entity = get_data_row($query, 'entity_row_to_elggstar'); if ($entity) { $CODE_TO_GUID_MAP_CACHE[$code] = $entity->guid; } return $entity; }
/** * Get user by username * * @param string $username The user's username * * @return ElggUser|false Depending on success */ function get_user_by_username($username) { global $CONFIG, $USERNAME_TO_GUID_MAP_CACHE; $username = sanitise_string($username); $access = _elgg_get_access_where_sql(); // Caching if (isset($USERNAME_TO_GUID_MAP_CACHE[$username]) && _elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username])) { return _elgg_retrieve_cached_entity($USERNAME_TO_GUID_MAP_CACHE[$username]); } $query = "SELECT e.* FROM {$CONFIG->dbprefix}users_entity u\n\t\tJOIN {$CONFIG->dbprefix}entities e ON e.guid = u.guid\n\t\tWHERE u.username = '******' AND {$access}"; $entity = get_data_row($query, 'entity_row_to_elggstar'); if ($entity) { $USERNAME_TO_GUID_MAP_CACHE[$username] = $entity->guid; } else { $entity = false; } return $entity; }