/** * @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)); } } }