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);
 }