/**
  * @task load
  */
 public static function getAllTypes()
 {
     static $type_map;
     if ($type_map === null) {
         $types = id(new PhutilSymbolLoader())->setAncestorClass(__CLASS__)->loadObjects();
         $map = array();
         // TODO: Remove this once everything is migrated.
         $exclude = mpull($types, 'getEdgeConstant');
         $map = PhabricatorEdgeConfig::getLegacyTypes($exclude);
         unset($types['PhabricatorLegacyEdgeType']);
         foreach ($types as $class => $type) {
             $const = $type->getEdgeConstant();
             if (isset($map[$const])) {
                 throw new Exception(pht('Two edge types ("%s", "%s") share the same edge constant ' . '(%d). Each edge type must have a unique constant.', $class, get_class($map[$const]), $const));
             }
             $map[$const] = $type;
         }
         // Check that all the inverse edge definitions actually make sense. If
         // edge type A says B is its inverse, B must exist and say that A is its
         // inverse.
         foreach ($map as $const => $type) {
             $inverse = $type->getInverseEdgeConstant();
             if ($inverse === null) {
                 continue;
             }
             if (empty($map[$inverse])) {
                 throw new Exception(pht('Edge type "%s" ("%d") defines an inverse type ("%d") which ' . 'does not exist.', get_class($type), $const, $inverse));
             }
             $inverse_inverse = $map[$inverse]->getInverseEdgeConstant();
             if ($inverse_inverse !== $const) {
                 throw new Exception(pht('Edge type "%s" ("%d") defines an inverse type ("%d"), but that ' . 'inverse type defines a different type ("%d") as its ' . 'inverse.', get_class($type), $const, $inverse, $inverse_inverse));
             }
         }
         $type_map = $map;
     }
     return $type_map;
 }
 /**
  * Load specified edges.
  *
  * @task exec
  */
 public function execute()
 {
     if (!$this->sourcePHIDs) {
         throw new Exception("You must use withSourcePHIDs() to query edges.");
     }
     $sources = phid_group_by_type($this->sourcePHIDs);
     $result = array();
     // When a query specifies types, make sure we return data for all queried
     // types. This is mostly to make sure PhabricatorLiskDAO->attachEdges()
     // gets some data, so that getEdges() doesn't throw later.
     if ($this->edgeTypes) {
         foreach ($this->sourcePHIDs as $phid) {
             foreach ($this->edgeTypes as $type) {
                 $result[$phid][$type] = array();
             }
         }
     }
     foreach ($sources as $type => $phids) {
         $conn_r = PhabricatorEdgeConfig::establishConnection($type, 'r');
         $where = $this->buildWhereClause($conn_r);
         $order = $this->buildOrderClause($conn_r);
         $edges = queryfx_all($conn_r, 'SELECT edge.* FROM %T edge %Q %Q', PhabricatorEdgeConfig::TABLE_NAME_EDGE, $where, $order);
         if ($this->needEdgeData) {
             $data_ids = array_filter(ipull($edges, 'dataID'));
             $data_map = array();
             if ($data_ids) {
                 $data_rows = queryfx_all($conn_r, 'SELECT edgedata.* FROM %T edgedata WHERE id IN (%Ld)', PhabricatorEdgeConfig::TABLE_NAME_EDGEDATA, $data_ids);
                 foreach ($data_rows as $row) {
                     $data_map[$row['id']] = idx(json_decode($row['data'], true), 'data');
                 }
             }
             foreach ($edges as $key => $edge) {
                 $edges[$key]['data'] = idx($data_map, $edge['dataID']);
             }
         }
         foreach ($edges as $edge) {
             $result[$edge['src']][$edge['type']][$edge['dst']] = $edge;
         }
     }
     return $result;
 }
 /**
  * Get a list of all edge types which are being added, and which we should
  * prevent cycles on.
  *
  * @return list<const> List of edge types which should have cycles prevented.
  * @task cycle
  */
 private function getPreventCyclesEdgeTypes()
 {
     $edge_types = array();
     foreach ($this->addEdges as $edge) {
         $edge_types[$edge['type']] = true;
     }
     foreach ($edge_types as $type => $ignored) {
         if (!PhabricatorEdgeConfig::shouldPreventCycles($type)) {
             unset($edge_types[$type]);
         }
     }
     return array_keys($edge_types);
 }
 /**
  * Remove queued edges.
  *
  * @task internal
  */
 private function executeRemoves()
 {
     $rems = $this->remEdges;
     $rems = igroup($rems, 'src_type');
     $deletes = array();
     foreach ($rems as $src_type => $edges) {
         $conn_w = PhabricatorEdgeConfig::establishConnection($src_type, 'w');
         $sql = array();
         foreach ($edges as $edge) {
             $sql[] = qsprintf($conn_w, '(src = %s AND type = %d AND dst = %s)', $edge['src'], $edge['type'], $edge['dst']);
         }
         $deletes[] = array($conn_w, $sql);
     }
     foreach ($deletes as $delete) {
         list($conn_w, $sql) = $delete;
         $conn_w->openTransaction();
         $this->openTransactions[] = $conn_w;
         foreach (array_chunk($sql, 256) as $chunk) {
             queryfx($conn_w, 'DELETE FROM %T WHERE (%Q)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, implode(' OR ', $chunk));
         }
     }
 }