Add a generic "edge.search" method
Summary:
Ref T12337. Ref T5873. This provides a generic "edge.search" method which feels like other "verison 3" `*.search` methods.
The major issues here are:
  1. Edges use constants internally, which aren't great for an API.
  2. A lot of edges are internal and probably not useful to query.
  3. Edges don't have a real "id", so paginating them properly is challenging.
I've solved these things like this:
  - Edges must opt-in to being available via Conduit by providing a human-readable key (like "mention" instead of "52"). This solvs (1) and (2).
  - I faked a mostly-reasonable behavior for paginating.
Test Plan:
Ran various valid and invalid searches. Paginated a large search. Reviewed UI.
{F3651818}
Reviewers: chad
Reviewed By: chad
Maniphest Tasks: T12337, T5873
Differential Revision: https://secure.phabricator.com/D17462
			
			
This commit is contained in:
		| @@ -1077,6 +1077,7 @@ phutil_register_library_map(array( | |||||||
|     'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', |     'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', | ||||||
|     'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php', |     'DrydockWorker' => 'applications/drydock/worker/DrydockWorker.php', | ||||||
|     'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', |     'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', | ||||||
|  |     'EdgeSearchConduitAPIMethod' => 'infrastructure/edges/conduit/EdgeSearchConduitAPIMethod.php', | ||||||
|     'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php', |     'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php', | ||||||
|     'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php', |     'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php', | ||||||
|     'FeedPublisherHTTPWorker' => 'applications/feed/worker/FeedPublisherHTTPWorker.php', |     'FeedPublisherHTTPWorker' => 'applications/feed/worker/FeedPublisherHTTPWorker.php', | ||||||
| @@ -2589,6 +2590,8 @@ phutil_register_library_map(array( | |||||||
|     'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php', |     'PhabricatorEdgeEditType' => 'applications/transactions/edittype/PhabricatorEdgeEditType.php', | ||||||
|     'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php', |     'PhabricatorEdgeEditor' => 'infrastructure/edges/editor/PhabricatorEdgeEditor.php', | ||||||
|     'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php', |     'PhabricatorEdgeGraph' => 'infrastructure/edges/util/PhabricatorEdgeGraph.php', | ||||||
|  |     'PhabricatorEdgeObject' => 'infrastructure/edges/conduit/PhabricatorEdgeObject.php', | ||||||
|  |     'PhabricatorEdgeObjectQuery' => 'infrastructure/edges/query/PhabricatorEdgeObjectQuery.php', | ||||||
|     'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php', |     'PhabricatorEdgeQuery' => 'infrastructure/edges/query/PhabricatorEdgeQuery.php', | ||||||
|     'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php', |     'PhabricatorEdgeTestCase' => 'infrastructure/edges/__tests__/PhabricatorEdgeTestCase.php', | ||||||
|     'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php', |     'PhabricatorEdgeType' => 'infrastructure/edges/type/PhabricatorEdgeType.php', | ||||||
| @@ -5886,6 +5889,7 @@ phutil_register_library_map(array( | |||||||
|     'DrydockWebrootInterface' => 'DrydockInterface', |     'DrydockWebrootInterface' => 'DrydockInterface', | ||||||
|     'DrydockWorker' => 'PhabricatorWorker', |     'DrydockWorker' => 'PhabricatorWorker', | ||||||
|     'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', |     'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', | ||||||
|  |     'EdgeSearchConduitAPIMethod' => 'ConduitAPIMethod', | ||||||
|     'FeedConduitAPIMethod' => 'ConduitAPIMethod', |     'FeedConduitAPIMethod' => 'ConduitAPIMethod', | ||||||
|     'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod', |     'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod', | ||||||
|     'FeedPublisherHTTPWorker' => 'FeedPushWorker', |     'FeedPublisherHTTPWorker' => 'FeedPushWorker', | ||||||
| @@ -7652,6 +7656,11 @@ phutil_register_library_map(array( | |||||||
|     'PhabricatorEdgeEditType' => 'PhabricatorPHIDListEditType', |     'PhabricatorEdgeEditType' => 'PhabricatorPHIDListEditType', | ||||||
|     'PhabricatorEdgeEditor' => 'Phobject', |     'PhabricatorEdgeEditor' => 'Phobject', | ||||||
|     'PhabricatorEdgeGraph' => 'AbstractDirectedGraph', |     'PhabricatorEdgeGraph' => 'AbstractDirectedGraph', | ||||||
|  |     'PhabricatorEdgeObject' => array( | ||||||
|  |       'Phobject', | ||||||
|  |       'PhabricatorPolicyInterface', | ||||||
|  |     ), | ||||||
|  |     'PhabricatorEdgeObjectQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', | ||||||
|     'PhabricatorEdgeQuery' => 'PhabricatorQuery', |     'PhabricatorEdgeQuery' => 'PhabricatorQuery', | ||||||
|     'PhabricatorEdgeTestCase' => 'PhabricatorTestCase', |     'PhabricatorEdgeTestCase' => 'PhabricatorTestCase', | ||||||
|     'PhabricatorEdgeType' => 'Phobject', |     'PhabricatorEdgeType' => 'Phobject', | ||||||
|   | |||||||
| @@ -24,4 +24,17 @@ final class PhabricatorObjectMentionedByObjectEdgeType | |||||||
|       $add_edges); |       $add_edges); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public function getConduitKey() { | ||||||
|  |     return 'mentioned-in'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getConduitName() { | ||||||
|  |     return pht('Mention In'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getConduitDescription() { | ||||||
|  |     return pht( | ||||||
|  |       'The source object is mentioned in a comment on the destination object.'); | ||||||
|  |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -13,4 +13,17 @@ final class PhabricatorObjectMentionsObjectEdgeType | |||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public function getConduitKey() { | ||||||
|  |     return 'mention'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getConduitName() { | ||||||
|  |     return pht('Mention'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getConduitDescription() { | ||||||
|  |     return pht( | ||||||
|  |       'The source object has a comment which mentions the destination object.'); | ||||||
|  |   } | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										173
									
								
								src/infrastructure/edges/conduit/EdgeSearchConduitAPIMethod.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/infrastructure/edges/conduit/EdgeSearchConduitAPIMethod.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class EdgeSearchConduitAPIMethod | ||||||
|  |   extends ConduitAPIMethod { | ||||||
|  |  | ||||||
|  |   public function getAPIMethodName() { | ||||||
|  |     return 'edge.search'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getMethodDescription() { | ||||||
|  |     return pht('Read edge relationships between objects.'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getMethodDocumentation() { | ||||||
|  |     $viewer = $this->getViewer(); | ||||||
|  |  | ||||||
|  |     $rows = array(); | ||||||
|  |     foreach ($this->getConduitEdgeTypeMap() as $key => $type) { | ||||||
|  |       $inverse_constant = $type->getInverseEdgeConstant(); | ||||||
|  |       if ($inverse_constant) { | ||||||
|  |         $inverse_type = PhabricatorEdgeType::getByConstant($inverse_constant); | ||||||
|  |         $inverse = $inverse_type->getConduitKey(); | ||||||
|  |       } else { | ||||||
|  |         $inverse = null; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $rows[] = array( | ||||||
|  |         $key, | ||||||
|  |         $type->getConduitName(), | ||||||
|  |         $inverse, | ||||||
|  |         new PHUIRemarkupView($viewer, $type->getConduitDescription()), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $types_table = id(new AphrontTableView($rows)) | ||||||
|  |       ->setHeaders( | ||||||
|  |         array( | ||||||
|  |           pht('Constant'), | ||||||
|  |           pht('Name'), | ||||||
|  |           pht('Inverse'), | ||||||
|  |           pht('Description'), | ||||||
|  |         )) | ||||||
|  |       ->setColumnClasses( | ||||||
|  |         array( | ||||||
|  |           'mono', | ||||||
|  |           'pri', | ||||||
|  |           'mono', | ||||||
|  |           'wide', | ||||||
|  |         )); | ||||||
|  |  | ||||||
|  |     return id(new PHUIObjectBoxView()) | ||||||
|  |       ->setHeaderText(pht('Edge Types')) | ||||||
|  |       ->setTable($types_table); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getMethodStatus() { | ||||||
|  |     return self::METHOD_STATUS_UNSTABLE; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getMethodStatusDescription() { | ||||||
|  |     return pht('This method is new and experimental.'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function defineParamTypes() { | ||||||
|  |     return array( | ||||||
|  |       'sourcePHIDs' => 'list<phid>', | ||||||
|  |       'types' => 'list<const>', | ||||||
|  |       'destinationPHIDs' => 'optional list<phid>', | ||||||
|  |     ) + $this->getPagerParamTypes(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function defineReturnType() { | ||||||
|  |     return 'list<dict>'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function defineErrorTypes() { | ||||||
|  |     return array(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function execute(ConduitAPIRequest $request) { | ||||||
|  |     $viewer = $request->getUser(); | ||||||
|  |     $pager = $this->newPager($request); | ||||||
|  |  | ||||||
|  |     $source_phids = $request->getValue('sourcePHIDs', array()); | ||||||
|  |     $edge_types = $request->getValue('types', array()); | ||||||
|  |     $destination_phids = $request->getValue('destinationPHIDs', array()); | ||||||
|  |  | ||||||
|  |     $object_query = id(new PhabricatorObjectQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->withNames($source_phids); | ||||||
|  |  | ||||||
|  |     $object_query->execute(); | ||||||
|  |     $objects = $object_query->getNamedResults(); | ||||||
|  |     foreach ($source_phids as $phid) { | ||||||
|  |       if (empty($objects[$phid])) { | ||||||
|  |         throw new Exception( | ||||||
|  |           pht( | ||||||
|  |             'Source PHID "%s" does not identify a valid object, or you do '. | ||||||
|  |             'not have permission to view it.', | ||||||
|  |             $phid)); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $source_phids = mpull($objects, 'getPHID'); | ||||||
|  |  | ||||||
|  |     if (!$edge_types) { | ||||||
|  |       throw new Exception( | ||||||
|  |         pht( | ||||||
|  |           'Edge search must specify a nonempty list of edge types.')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $edge_map = $this->getConduitEdgeTypeMap(); | ||||||
|  |  | ||||||
|  |     $constant_map = array(); | ||||||
|  |     $edge_constants = array(); | ||||||
|  |     foreach ($edge_types as $edge_type) { | ||||||
|  |       if (!isset($edge_map[$edge_type])) { | ||||||
|  |         throw new Exception( | ||||||
|  |           pht( | ||||||
|  |             'Edge type "%s" is not a recognized edge type.', | ||||||
|  |             $edge_type)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $constant = $edge_map[$edge_type]->getEdgeConstant(); | ||||||
|  |  | ||||||
|  |       $edge_constants[] = $constant; | ||||||
|  |       $constant_map[$constant] = $edge_type; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $edge_query = id(new PhabricatorEdgeObjectQuery()) | ||||||
|  |       ->setViewer($viewer) | ||||||
|  |       ->withSourcePHIDs($source_phids) | ||||||
|  |       ->withEdgeTypes($edge_constants); | ||||||
|  |  | ||||||
|  |     if ($destination_phids) { | ||||||
|  |       $edge_query->withDestinationPHIDs($destination_phids); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $edge_objects = $edge_query->executeWithCursorPager($pager); | ||||||
|  |  | ||||||
|  |     $edges = array(); | ||||||
|  |     foreach ($edge_objects as $edge_object) { | ||||||
|  |       $edges[] = array( | ||||||
|  |         'sourcePHID' => $edge_object->getSourcePHID(), | ||||||
|  |         'edgeType' => $constant_map[$edge_object->getEdgeType()], | ||||||
|  |         'destinationPHID' => $edge_object->getDestinationPHID(), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $results = array( | ||||||
|  |       'data' => $edges, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     return $this->addPagerResults($results, $pager); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private function getConduitEdgeTypeMap() { | ||||||
|  |     $types = PhabricatorEdgeType::getAllTypes(); | ||||||
|  |  | ||||||
|  |     $map = array(); | ||||||
|  |     foreach ($types as $type) { | ||||||
|  |       $key = $type->getConduitKey(); | ||||||
|  |       if ($key === null) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       $map[$key] = $type; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     ksort($map); | ||||||
|  |  | ||||||
|  |     return $map; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										63
									
								
								src/infrastructure/edges/conduit/PhabricatorEdgeObject.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/infrastructure/edges/conduit/PhabricatorEdgeObject.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | final class PhabricatorEdgeObject | ||||||
|  |   extends Phobject | ||||||
|  |   implements PhabricatorPolicyInterface { | ||||||
|  |  | ||||||
|  |   private $id; | ||||||
|  |   private $src; | ||||||
|  |   private $dst; | ||||||
|  |   private $type; | ||||||
|  |  | ||||||
|  |   public static function newFromRow(array $row) { | ||||||
|  |     $edge = new self(); | ||||||
|  |  | ||||||
|  |     $edge->id = $row['id']; | ||||||
|  |     $edge->src = $row['src']; | ||||||
|  |     $edge->dst = $row['dst']; | ||||||
|  |     $edge->type = $row['type']; | ||||||
|  |  | ||||||
|  |     return $edge; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getID() { | ||||||
|  |     return $this->id; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getSourcePHID() { | ||||||
|  |     return $this->src; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getEdgeType() { | ||||||
|  |     return $this->type; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getDestinationPHID() { | ||||||
|  |     return $this->dst; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getPHID() { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | /* -(  PhabricatorPolicyInterface  )----------------------------------------- */ | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function getCapabilities() { | ||||||
|  |     return array( | ||||||
|  |       PhabricatorPolicyCapability::CAN_VIEW, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getPolicy($capability) { | ||||||
|  |     switch ($capability) { | ||||||
|  |       case PhabricatorPolicyCapability::CAN_VIEW: | ||||||
|  |         return PhabricatorPolicies::getMostOpenPolicy(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { | ||||||
|  |     return false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										163
									
								
								src/infrastructure/edges/query/PhabricatorEdgeObjectQuery.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								src/infrastructure/edges/query/PhabricatorEdgeObjectQuery.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This is a more formal version of @{class:PhabricatorEdgeQuery} that is used | ||||||
|  |  * to expose edges to Conduit. | ||||||
|  |  */ | ||||||
|  | final class PhabricatorEdgeObjectQuery | ||||||
|  |   extends PhabricatorCursorPagedPolicyAwareQuery { | ||||||
|  |  | ||||||
|  |   private $sourcePHIDs; | ||||||
|  |   private $sourcePHIDType; | ||||||
|  |   private $edgeTypes; | ||||||
|  |   private $destinationPHIDs; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |   public function withSourcePHIDs(array $source_phids) { | ||||||
|  |     $this->sourcePHIDs = $source_phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function withEdgeTypes(array $types) { | ||||||
|  |     $this->edgeTypes = $types; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function withDestinationPHIDs(array $destination_phids) { | ||||||
|  |     $this->destinationPHIDs = $destination_phids; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function willExecute() { | ||||||
|  |     $source_phids = $this->sourcePHIDs; | ||||||
|  |  | ||||||
|  |     if (!$source_phids) { | ||||||
|  |       throw new Exception( | ||||||
|  |         pht( | ||||||
|  |           'Edge object query must be executed with a nonempty list of '. | ||||||
|  |           'source PHIDs.')); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $phid_item = null; | ||||||
|  |     $phid_type = null; | ||||||
|  |     foreach ($source_phids as $phid) { | ||||||
|  |       $this_type = phid_get_type($phid); | ||||||
|  |       if ($this_type == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) { | ||||||
|  |         throw new Exception( | ||||||
|  |           pht( | ||||||
|  |             'Source PHID "%s" in edge object query has unknown PHID type.', | ||||||
|  |             $phid)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if ($phid_type === null) { | ||||||
|  |         $phid_type = $this_type; | ||||||
|  |         $phid_item = $phid; | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if ($phid_type !== $this_type) { | ||||||
|  |         throw new Exception( | ||||||
|  |           pht( | ||||||
|  |             'Two source PHIDs ("%s" and "%s") have different PHID types '. | ||||||
|  |             '("%s" and "%s"). All PHIDs must be of the same type to execute '. | ||||||
|  |             'an edge object query.', | ||||||
|  |             $phid_item, | ||||||
|  |             $phid, | ||||||
|  |             $phid_type, | ||||||
|  |             $this_type)); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     $this->sourcePHIDType = $phid_type; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function loadPage() { | ||||||
|  |     $type = $this->sourcePHIDType; | ||||||
|  |     $conn = PhabricatorEdgeConfig::establishConnection($type, 'r'); | ||||||
|  |     $table = PhabricatorEdgeConfig::TABLE_NAME_EDGE; | ||||||
|  |     $rows = $this->loadStandardPageRowsWithConnection($conn, $table); | ||||||
|  |  | ||||||
|  |     $result = array(); | ||||||
|  |     foreach ($rows as $row) { | ||||||
|  |       $result[] = PhabricatorEdgeObject::newFromRow($row); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $result; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function buildSelectClauseParts(AphrontDatabaseConnection $conn) { | ||||||
|  |     $parts = parent::buildSelectClauseParts($conn); | ||||||
|  |  | ||||||
|  |     // TODO: This is hacky, because we don't have real IDs on this table. | ||||||
|  |     $parts[] = qsprintf( | ||||||
|  |       $conn, | ||||||
|  |       'CONCAT(dateCreated, %s, seq) AS id', | ||||||
|  |       '_'); | ||||||
|  |  | ||||||
|  |     return $parts; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { | ||||||
|  |     $parts = parent::buildWhereClauseParts($conn); | ||||||
|  |  | ||||||
|  |     $parts[] = qsprintf( | ||||||
|  |       $conn, | ||||||
|  |       'src IN (%Ls)', | ||||||
|  |       $this->sourcePHIDs); | ||||||
|  |  | ||||||
|  |     $parts[] = qsprintf( | ||||||
|  |       $conn, | ||||||
|  |       'type IN (%Ls)', | ||||||
|  |       $this->edgeTypes); | ||||||
|  |  | ||||||
|  |     if ($this->destinationPHIDs !== null) { | ||||||
|  |       $parts[] = qsprintf( | ||||||
|  |         $conn, | ||||||
|  |         'dst IN (%Ls)', | ||||||
|  |         $this->destinationPHIDs); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return $parts; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getQueryApplicationClass() { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function getPrimaryTableAlias() { | ||||||
|  |     return 'edge'; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getOrderableColumns() { | ||||||
|  |     return array( | ||||||
|  |       'dateCreated' => array( | ||||||
|  |         'table' => 'edge', | ||||||
|  |         'column' => 'dateCreated', | ||||||
|  |         'type' => 'int', | ||||||
|  |       ), | ||||||
|  |       'sequence' => array( | ||||||
|  |         'table' => 'edge', | ||||||
|  |         'column' => 'seq', | ||||||
|  |         'type' => 'int', | ||||||
|  |  | ||||||
|  |         // TODO: This is not actually unique, but we're just doing our best | ||||||
|  |         // here. | ||||||
|  |         'unique' => true, | ||||||
|  |       ), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function getDefaultOrderVector() { | ||||||
|  |     return array('dateCreated', 'sequence'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function getPagingValueMap($cursor, array $keys) { | ||||||
|  |     $parts = explode('_', $cursor); | ||||||
|  |  | ||||||
|  |     return array( | ||||||
|  |       'dateCreated' => $parts[0], | ||||||
|  |       'sequence' => $parts[1], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -27,6 +27,18 @@ abstract class PhabricatorEdgeType extends Phobject { | |||||||
|     return $const; |     return $const; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public function getConduitKey() { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getConduitName() { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public function getConduitDescription() { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public function getInverseEdgeConstant() { |   public function getInverseEdgeConstant() { | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -85,12 +85,20 @@ abstract class PhabricatorCursorPagedPolicyAwareQuery | |||||||
|  |  | ||||||
|   protected function loadStandardPageRows(PhabricatorLiskDAO $table) { |   protected function loadStandardPageRows(PhabricatorLiskDAO $table) { | ||||||
|     $conn = $table->establishConnection('r'); |     $conn = $table->establishConnection('r'); | ||||||
|  |     return $this->loadStandardPageRowsWithConnection( | ||||||
|  |       $conn, | ||||||
|  |       $table->getTableName()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected function loadStandardPageRowsWithConnection( | ||||||
|  |     AphrontDatabaseConnection $conn, | ||||||
|  |     $table_name) { | ||||||
|  |  | ||||||
|     $rows = queryfx_all( |     $rows = queryfx_all( | ||||||
|       $conn, |       $conn, | ||||||
|       '%Q FROM %T %Q %Q %Q %Q %Q %Q %Q', |       '%Q FROM %T %Q %Q %Q %Q %Q %Q %Q', | ||||||
|       $this->buildSelectClause($conn), |       $this->buildSelectClause($conn), | ||||||
|       $table->getTableName(), |       $table_name, | ||||||
|       (string)$this->getPrimaryTableAlias(), |       (string)$this->getPrimaryTableAlias(), | ||||||
|       $this->buildJoinClause($conn), |       $this->buildJoinClause($conn), | ||||||
|       $this->buildWhereClause($conn), |       $this->buildWhereClause($conn), | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 epriestley
					epriestley