protected function insertRelated(array $rows) { if (!is_array(reset($rows))) { $rows = array($rows); } $trees = array(); foreach ($rows as $key => $row) { $trees[$key] = $this->splitUpdate($row, 'tree'); } $dbw = $this->dbFactory->getDB(DB_MASTER); $res = $dbw->insert($this->joinTable(), $this->preprocessNestedSqlArray($trees), __METHOD__); // If this is a brand new root revision it needs to be added to the tree // If it has a rev_parent_id then its already a part of the tree if ($res) { foreach ($rows as $row) { if ($row['rev_parent_id'] === null) { $res = $res && $this->treeRepo->insert(UUID::create($row['tree_rev_descendant_id']), UUID::create($row['tree_parent_id'])); } } } if (!$res) { return array(); } return $rows; }
/** * @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; } }
/** * All queries are for roots (guaranteed in findMulti), so anything that falls * through and has to be queried from storage will actually need to be doing a * special condition either joining against flow_tree_node or first collecting the * subtree node lists and then doing a big IN condition * * This isn't a hot path (should be pre-populated into index) but we still don't want * horrible performance * * @param array $queries * @return array * @throws \Flow\Exception\InvalidInputException */ protected function findDescendantQuery(array $query) { $roots = array(UUID::create($query['topic_root_id'])); $nodeList = $this->treeRepository->fetchSubtreeNodeList($roots); if ($nodeList === false) { // We can't return the existing $retval, that false data would be cached. return array(); } /** @var UUID $topicRootId */ $topicRootId = UUID::create($query['topic_root_id']); $nodes = $nodeList[$topicRootId->getAlphadecimal()]; return array('rev_type_id' => UUID::convertUUIDs($nodes)); }
/** * @param UUID[] $postIds * @return UUID[] Map from alphadecimal id to UUID object */ protected function fetchRelatedPostIds(array $postIds) { // list of all posts descendant from the provided $postIds $nodeList = $this->treeRepo->fetchSubtreeNodeList($postIds); // merge all the children from the various posts into one array if (!$nodeList) { // It should have returned at least $postIds // TODO: log errors? $res = $postIds; } elseif (count($nodeList) === 1) { $res = reset($nodeList); } else { $res = call_user_func_array('array_merge', $nodeList); } $retval = array(); foreach ($res as $id) { $retval[$id->getAlphadecimal()] = $id; } return $retval; }
/** * Entry point for batch loading metadata for a variety of revisions * into the internal cache. * * @param AbstractRevision[]|ResultWrapper $results */ protected function loadMetadataBatch($results) { // Batch load data related to a list of revisions $postIds = array(); $workflowIds = array(); $revisions = array(); $previousRevisionIds = array(); $collectionIds = array(); foreach ($results as $result) { if ($result instanceof PostRevision) { // If top-level, then just get the workflow. // Otherwise we need to find the root post. $id = $result->getPostId(); $alpha = $id->getAlphadecimal(); if ($result->isTopicTitle()) { $workflowIds[] = $id; } else { $postIds[$alpha] = $id; } $this->postCache[$alpha] = $result; } elseif ($result instanceof Header) { $workflowIds[] = $result->getWorkflowId(); } elseif ($result instanceof PostSummary) { // This would be the post id for the summary $id = $result->getSummaryTargetId(); $postIds[$id->getAlphadecimal()] = $id; } $revisions[$result->getRevisionId()->getAlphadecimal()] = $result; if ($this->needsPreviousRevision($result)) { $previousRevisionIds[get_class($result)][] = $result->getPrevRevisionId(); } $collection = $result->getCollection(); $collectionIds[get_class($result)][] = $collection->getId(); } // map from post Id to the related root post id $rootPostIds = array_filter($this->treeRepository->findRoots($postIds)); $rootPostRequests = array(); foreach ($rootPostIds as $postId) { $rootPostRequests[] = array('rev_type_id' => $postId); } // these tree identity maps are required for determining where a reply goes when // // replying to a specific post. $identityMap = $this->treeRepository->fetchSubtreeIdentityMap(array_unique($rootPostIds, SORT_REGULAR)); $rootPostResult = $this->storage->findMulti('PostRevision', $rootPostRequests, array('SORT' => 'rev_id', 'ORDER' => 'DESC', 'LIMIT' => 1)); $rootPosts = array(); if (count($rootPostResult) > 0) { foreach ($rootPostResult as $found) { $root = reset($found); $rootPosts[$root->getPostId()->getAlphadecimal()] = $root; $revisions[$root->getRevisionId()->getAlphadecimal()] = $root; } } // Workflow IDs are the same as root post IDs // So any post IDs that *are* root posts + found root post IDs + header workflow IDs // should cover the lot. $workflows = $this->storage->getMulti('Workflow', array_merge($rootPostIds, $workflowIds)); $workflows = $workflows ?: array(); // preload all requested previous revisions foreach ($previousRevisionIds as $revisionType => $ids) { // get rid of null-values (for original revisions, without previous revision) $ids = array_filter($ids); /** @var AbstractRevision[] $found */ $found = $this->storage->getMulti($revisionType, $ids); foreach ($found as $rev) { $revisions[$rev->getRevisionId()->getAlphadecimal()] = $rev; } } // preload all current versions foreach ($collectionIds as $revisionType => $ids) { $queries = array(); foreach ($ids as $uuid) { $queries[] = array('rev_type_id' => $uuid); } $found = $this->storage->findMulti($revisionType, $queries, array('sort' => 'rev_id', 'order' => 'DESC', 'limit' => 1)); /** @var AbstractRevision[] $result */ foreach ($found as $result) { $rev = reset($result); $this->currentRevisionsCache[$rev->getCollectionId()->getAlphadecimal()] = $rev->getRevisionId(); $revisions[$rev->getRevisionId()->getAlphadecimal()] = $rev; } } $this->revisionCache = array_merge($this->revisionCache, $revisions); $this->postCache = array_merge($this->postCache, $rootPosts); $this->rootPostIdCache = array_merge($this->rootPostIdCache, $rootPostIds); $this->workflowCache = array_merge($this->workflowCache, $workflows); $this->identityMap = array_merge($this->identityMap, $identityMap); }