/** * @param array $cacheKeys * @param callable $loadCallback * @return array */ public function getByKey(array $cacheKeys, $loadCallback) { if (!$cacheKeys) { return array(); } $result = array(); $multiRes = $this->cache->getMulti(array_keys($cacheKeys)); if ($multiRes === false) { // Falls through to query only backend wfDebugLog('Flow', __METHOD__ . ': Failure querying memcache'); } else { // Memcached BagOStuff only returns found keys, but the redis bag // returns false for not found keys. $multiRes = array_filter($multiRes, function ($val) { return $val !== false; }); foreach ($multiRes as $key => $value) { $idx = $cacheKeys[$key]; if ($idx instanceof UUID) { $idx = $idx->getAlphadecimal(); } $result[$idx] = $value; unset($cacheKeys[$key]); } } if (count($cacheKeys) === 0) { return $result; } $res = call_user_func($loadCallback, array_values($cacheKeys)); if (!$res) { // storage failure of some sort return $result; } $invCacheKeys = array(); foreach ($cacheKeys as $cacheKey => $id) { if ($id instanceof UUID) { $id = $id->getAlphadecimal(); } $invCacheKeys[$id] = $cacheKey; } foreach ($res as $id => $row) { // If we failed contacting memcache a moment ago don't bother trying to // push values either. if ($multiRes !== false) { $this->cache->set($invCacheKeys[$id], $row); } $result[$id] = $row; } return $result; }
/** * @expectedException \Flow\Exception\DataModelException */ public function testFailingInsert() { global $wgFlowCacheTime; // Catch the exception and test the cache result then re-throw the exception, // otherwise the exception would skip the cache result test $cache = new BufferedCache(new BufferedBagOStuff(new \HashBagOStuff()), $wgFlowCacheTime); try { $treeRepository = new TreeRepository($this->mockDbFactory(false), $cache); $this->assertNull($treeRepository->insert($this->descendant, $this->ancestor)); } catch (\Exception $e) { $reflection = new ReflectionClass('\\Flow\\Repository\\TreeRepository'); $method = $reflection->getMethod('cacheKey'); $method->setAccessible(true); $this->assertSame($cache->get($method->invoke($treeRepository, 'rootpath', $this->descendant)), false); $this->assertSame($cache->get($method->invoke($treeRepository, 'parent', $this->descendant)), false); throw $e; } }
/** * Given a list of nodes, find the path from each node to the root of its tree. * the root must be the first element of the array, $node must be the last element. * @param UUID[] $descendants Array of UUID objects to find the root paths for. * @return UUID[][] Associative array, key is the post ID in hex, value is the path as an array. */ public function findRootPaths(array $descendants) { // alphadecimal => cachekey $cacheKeys = array(); // alphadecimal => cache result ( distance => parent uuid obj ) $cacheValues = array(); // list of binary values for db query $missingValues = array(); // alphadecimal => distance => parent uuid obj $paths = array(); foreach ($descendants as $descendant) { $cacheKeys[$descendant->getAlphadecimal()] = $this->cacheKey('rootpath', $descendant); } $cacheResult = $this->cache->getMulti(array_values($cacheKeys)); foreach ($descendants as $descendant) { $alpha = $descendant->getAlphadecimal(); if (isset($cacheResult[$cacheKeys[$alpha]])) { $cacheValues[$alpha] = $cacheResult[$cacheKeys[$alpha]]; } else { $missingValues[] = $descendant->getBinary(); $paths[$alpha] = array(); } } if (!count($missingValues)) { return $cacheValues; } $dbr = $this->dbFactory->getDB(DB_SLAVE); $res = $dbr->select($this->tableName, array('tree_descendant_id', 'tree_ancestor_id', 'tree_depth'), array('tree_descendant_id' => $missingValues), __METHOD__); if (!$res || $res->numRows() === 0) { return $cacheValues; } foreach ($res as $row) { $alpha = UUID::create($row->tree_descendant_id)->getAlphadecimal(); $paths[$alpha][$row->tree_depth] = UUID::create($row->tree_ancestor_id); } foreach ($paths as $descendantId => &$path) { if (!$path) { $path = null; continue; } // sort by reverse distance, so furthest away // parent (root) is at position 0. ksort($path); $path = array_reverse($path); $this->cache->set($cacheKeys[$descendantId], $path); } return $paths + $cacheValues; }
/** * Returns a boolean true/false if the findMulti()-operation for the given * attributes has already been resolves and doesn't need to query any * outside cache/database. * Determining if a find() has not yet been resolved may be useful so that * additional data may be loaded at once. * * @param array $queries Queries to findMulti() * @param array[optional] $options Options to findMulti() * @return bool */ public function foundMulti(array $queries, array $options = array()) { if (!$queries) { return true; } // get cache keys for all queries $cacheKeys = $this->getCacheKeys($queries); // check if cache has a way of identifying what's stored locally if (!method_exists($this->cache, 'has')) { return false; } // check if keys matching given queries are already known in local cache foreach ($cacheKeys as $key) { if (!$this->cache->has($key)) { return false; } } $keyToQuery = array(); foreach ($cacheKeys as $i => $key) { // These results will be merged into the query results, and as such need binary // uuid's as would be received from storage if (!isset($keyToQuery[$key])) { $keyToQuery[$key] = $queries[$i]; } } // retrieve from cache - this is cheap, it's is local storage $cached = $this->cache->getMulti($cacheKeys); foreach ($cached as $i => $result) { $limit = isset($options['limit']) ? $options['limit'] : $this->getLimit(); $cached[$i] = array_splice($result, 0, $limit); } // if we have a shallow compactor, the returned data are PKs of objects // that need to be fetched too if ($this->rowCompactor instanceof ShallowCompactor) { // test of the keys to be expanded are already in local cache $duplicator = $this->rowCompactor->getResultDuplicator($cached, $keyToQuery); $queries = $duplicator->getUniqueQueries(); if (!$this->rowCompactor->getShallow()->foundMulti($queries)) { return false; } } return true; }