@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 342 lines 9.1 kB view raw
1<?php 2 3/** 4 * Load object edges created by @{class:PhabricatorEdgeEditor}. 5 * 6 * name=Querying Edges 7 * $src = $earth_phid; 8 * $type = PhabricatorEdgeConfig::TYPE_BODY_HAS_SATELLITE; 9 * 10 * // Load the earth's satellites. 11 * $satellite_edges = id(new PhabricatorEdgeQuery()) 12 * ->withSourcePHIDs(array($src)) 13 * ->withEdgeTypes(array($type)) 14 * ->execute(); 15 * 16 * For more information on edges, see @{article:Using Edges}. 17 * 18 * @task config Configuring the Query 19 * @task exec Executing the Query 20 * @task internal Internal 21 */ 22final class PhabricatorEdgeQuery extends PhabricatorQuery { 23 24 private $sourcePHIDs; 25 private $destPHIDs; 26 private $edgeTypes; 27 private $resultSet; 28 29 const ORDER_OLDEST_FIRST = 'order:oldest'; 30 const ORDER_NEWEST_FIRST = 'order:newest'; 31 private $order = self::ORDER_NEWEST_FIRST; 32 33 private $needEdgeData; 34 35 36/* -( Configuring the Query )---------------------------------------------- */ 37 38 39 /** 40 * Find edges originating at one or more source PHIDs. You MUST provide this 41 * to execute an edge query. 42 * 43 * @param list $source_phids List of source PHIDs. 44 * @return $this 45 * 46 * @task config 47 */ 48 public function withSourcePHIDs(array $source_phids) { 49 if (!$source_phids) { 50 throw new Exception( 51 pht( 52 'Edge list passed to "withSourcePHIDs(...)" is empty, but it must '. 53 'be nonempty.')); 54 } 55 56 $this->sourcePHIDs = $source_phids; 57 return $this; 58 } 59 60 61 /** 62 * Find edges terminating at one or more destination PHIDs. 63 * 64 * @param list $dest_phids List of destination PHIDs. 65 * @return $this 66 * 67 */ 68 public function withDestinationPHIDs(array $dest_phids) { 69 $this->destPHIDs = $dest_phids; 70 return $this; 71 } 72 73 74 /** 75 * Find edges of specific types. 76 * 77 * @param list $types List of PhabricatorEdgeConfig type constants. 78 * @return $this 79 * 80 * @task config 81 */ 82 public function withEdgeTypes(array $types) { 83 $this->edgeTypes = $types; 84 return $this; 85 } 86 87 88 /** 89 * Configure the order edge results are returned in. 90 * 91 * @param string $order Order constant. 92 * @return $this 93 * 94 * @task config 95 */ 96 public function setOrder($order) { 97 $this->order = $order; 98 return $this; 99 } 100 101 102 /** 103 * When loading edges, also load edge data. 104 * 105 * @param bool $need True to load edge data. 106 * @return $this 107 * 108 * @task config 109 */ 110 public function needEdgeData($need) { 111 $this->needEdgeData = $need; 112 return $this; 113 } 114 115 116/* -( Executing the Query )------------------------------------------------ */ 117 118 119 /** 120 * Convenience method for loading destination PHIDs with one source and one 121 * edge type. Equivalent to building a full query, but simplifies a common 122 * use case. 123 * 124 * @param string $src_phid Source PHID. 125 * @param int $edge_type Edge type constant 126 * (SomeClassEdgeType::EDGECONST). 127 * @return list<string> List of destination PHIDs. 128 */ 129 public static function loadDestinationPHIDs($src_phid, $edge_type) { 130 $edges = id(new PhabricatorEdgeQuery()) 131 ->withSourcePHIDs(array($src_phid)) 132 ->withEdgeTypes(array($edge_type)) 133 ->execute(); 134 return array_keys($edges[$src_phid][$edge_type]); 135 } 136 137 /** 138 * Convenience method for loading a single edge's metadata for 139 * a given source, destination, and edge type. Returns null 140 * if the edge does not exist or does not have metadata. Builds 141 * and immediately executes a full query. 142 * 143 * @param string $src_phid Source PHID. 144 * @param string $edge_type Edge type constant. 145 * @param string $dest_phid Destination PHID. 146 * @return mixed Edge annotation, or null. 147 */ 148 public static function loadSingleEdgeData($src_phid, $edge_type, $dest_phid) { 149 $edges = id(new PhabricatorEdgeQuery()) 150 ->withSourcePHIDs(array($src_phid)) 151 ->withEdgeTypes(array($edge_type)) 152 ->withDestinationPHIDs(array($dest_phid)) 153 ->needEdgeData(true) 154 ->execute(); 155 156 if (isset($edges[$src_phid][$edge_type][$dest_phid]['data'])) { 157 return $edges[$src_phid][$edge_type][$dest_phid]['data']; 158 } 159 return null; 160 } 161 162 163 /** 164 * Load specified edges. 165 * 166 * @task exec 167 */ 168 public function execute() { 169 if ($this->sourcePHIDs === null) { 170 throw new Exception( 171 pht( 172 'You must use "withSourcePHIDs()" to query edges.')); 173 } 174 175 $sources = phid_group_by_type($this->sourcePHIDs); 176 177 $result = array(); 178 179 // When a query specifies types, make sure we return data for all queried 180 // types. 181 if ($this->edgeTypes) { 182 foreach ($this->sourcePHIDs as $phid) { 183 foreach ($this->edgeTypes as $type) { 184 $result[$phid][$type] = array(); 185 } 186 } 187 } 188 189 foreach ($sources as $type => $phids) { 190 $conn_r = PhabricatorEdgeConfig::establishConnection($type, 'r'); 191 192 $where = $this->buildWhereClause($conn_r); 193 $order = $this->buildOrderClause($conn_r); 194 195 $edges = queryfx_all( 196 $conn_r, 197 'SELECT edge.* FROM %T edge %Q %Q', 198 PhabricatorEdgeConfig::TABLE_NAME_EDGE, 199 $where, 200 $order); 201 202 if ($this->needEdgeData) { 203 $data_ids = array_filter(ipull($edges, 'dataID')); 204 $data_map = array(); 205 if ($data_ids) { 206 $data_rows = queryfx_all( 207 $conn_r, 208 'SELECT edgedata.* FROM %T edgedata WHERE id IN (%Ld)', 209 PhabricatorEdgeConfig::TABLE_NAME_EDGEDATA, 210 $data_ids); 211 foreach ($data_rows as $row) { 212 $data_map[$row['id']] = idx( 213 phutil_json_decode($row['data']), 214 'data'); 215 } 216 } 217 foreach ($edges as $key => $edge) { 218 $edges[$key]['data'] = idx($data_map, $edge['dataID'], array()); 219 } 220 } 221 222 foreach ($edges as $edge) { 223 $result[$edge['src']][$edge['type']][$edge['dst']] = $edge; 224 } 225 } 226 227 $this->resultSet = $result; 228 return $result; 229 } 230 231 232 /** 233 * Convenience function for selecting edge destination PHIDs after calling 234 * execute(). 235 * 236 * Returns a flat list of PHIDs matching the provided source PHID and type 237 * filters. By default, the filters are empty so all PHIDs will be returned. 238 * For example, if you're doing a batch query from several sources, you might 239 * write code like this: 240 * 241 * $query = new PhabricatorEdgeQuery(); 242 * $query->setViewer($viewer); 243 * $query->withSourcePHIDs(mpull($objects, 'getPHID')); 244 * $query->withEdgeTypes(array($some_type)); 245 * $query->execute(); 246 * 247 * // Gets all of the destinations. 248 * $all_phids = $query->getDestinationPHIDs(); 249 * $handles = id(new PhabricatorHandleQuery()) 250 * ->setViewer($viewer) 251 * ->withPHIDs($all_phids) 252 * ->execute(); 253 * 254 * foreach ($objects as $object) { 255 * // Get all of the destinations for the given object. 256 * $dst_phids = $query->getDestinationPHIDs(array($object->getPHID())); 257 * $object->attachHandles(array_select_keys($handles, $dst_phids)); 258 * } 259 * 260 * @param list $src_phids (optional) List of PHIDs to select, or empty to 261 * select all. 262 * @param list $types (optional) List of edge types to select, or empty to 263 * select all. 264 * @return list<string> List of matching destination PHIDs. 265 */ 266 public function getDestinationPHIDs( 267 array $src_phids = array(), 268 array $types = array()) { 269 if ($this->resultSet === null) { 270 throw new PhutilInvalidStateException('execute'); 271 } 272 273 $result_phids = array(); 274 275 $set = $this->resultSet; 276 if ($src_phids) { 277 $set = array_select_keys($set, $src_phids); 278 } 279 280 foreach ($set as $src => $edges_by_type) { 281 if ($types) { 282 $edges_by_type = array_select_keys($edges_by_type, $types); 283 } 284 285 foreach ($edges_by_type as $edges) { 286 foreach ($edges as $edge_phid => $edge) { 287 $result_phids[$edge_phid] = true; 288 } 289 } 290 } 291 292 return array_keys($result_phids); 293 } 294 295 296/* -( Internals )---------------------------------------------------------- */ 297 298 299 /** 300 * @task internal 301 */ 302 protected function buildWhereClause(AphrontDatabaseConnection $conn) { 303 $where = array(); 304 305 if ($this->sourcePHIDs) { 306 $where[] = qsprintf( 307 $conn, 308 'edge.src IN (%Ls)', 309 $this->sourcePHIDs); 310 } 311 312 if ($this->edgeTypes) { 313 $where[] = qsprintf( 314 $conn, 315 'edge.type IN (%Ls)', 316 $this->edgeTypes); 317 } 318 319 if ($this->destPHIDs) { 320 // potentially complain if $this->edgeType was not set 321 $where[] = qsprintf( 322 $conn, 323 'edge.dst IN (%Ls)', 324 $this->destPHIDs); 325 } 326 327 return $this->formatWhereClause($conn, $where); 328 } 329 330 331 /** 332 * @task internal 333 */ 334 private function buildOrderClause(AphrontDatabaseConnection $conn) { 335 if ($this->order == self::ORDER_NEWEST_FIRST) { 336 return qsprintf($conn, 'ORDER BY edge.dateCreated DESC, edge.seq DESC'); 337 } else { 338 return qsprintf($conn, 'ORDER BY edge.dateCreated ASC, edge.seq ASC'); 339 } 340 } 341 342}