Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/infrastructure/edges/query/PhabricatorEdgeQuery.php
13410 views
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
*/
22
final 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 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 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 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 const 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 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 phid Source PHID.
125
* @param const Edge type.
126
* @return list<phid> List of destination PHIDs.
127
*/
128
public static function loadDestinationPHIDs($src_phid, $edge_type) {
129
$edges = id(new PhabricatorEdgeQuery())
130
->withSourcePHIDs(array($src_phid))
131
->withEdgeTypes(array($edge_type))
132
->execute();
133
return array_keys($edges[$src_phid][$edge_type]);
134
}
135
136
/**
137
* Convenience method for loading a single edge's metadata for
138
* a given source, destination, and edge type. Returns null
139
* if the edge does not exist or does not have metadata. Builds
140
* and immediately executes a full query.
141
*
142
* @param phid Source PHID.
143
* @param const Edge type.
144
* @param phid Destination PHID.
145
* @return wild Edge annotation (or null).
146
*/
147
public static function loadSingleEdgeData($src_phid, $edge_type, $dest_phid) {
148
$edges = id(new PhabricatorEdgeQuery())
149
->withSourcePHIDs(array($src_phid))
150
->withEdgeTypes(array($edge_type))
151
->withDestinationPHIDs(array($dest_phid))
152
->needEdgeData(true)
153
->execute();
154
155
if (isset($edges[$src_phid][$edge_type][$dest_phid]['data'])) {
156
return $edges[$src_phid][$edge_type][$dest_phid]['data'];
157
}
158
return null;
159
}
160
161
162
/**
163
* Load specified edges.
164
*
165
* @task exec
166
*/
167
public function execute() {
168
if ($this->sourcePHIDs === null) {
169
throw new Exception(
170
pht(
171
'You must use "withSourcePHIDs()" to query edges.'));
172
}
173
174
$sources = phid_group_by_type($this->sourcePHIDs);
175
176
$result = array();
177
178
// When a query specifies types, make sure we return data for all queried
179
// types.
180
if ($this->edgeTypes) {
181
foreach ($this->sourcePHIDs as $phid) {
182
foreach ($this->edgeTypes as $type) {
183
$result[$phid][$type] = array();
184
}
185
}
186
}
187
188
foreach ($sources as $type => $phids) {
189
$conn_r = PhabricatorEdgeConfig::establishConnection($type, 'r');
190
191
$where = $this->buildWhereClause($conn_r);
192
$order = $this->buildOrderClause($conn_r);
193
194
$edges = queryfx_all(
195
$conn_r,
196
'SELECT edge.* FROM %T edge %Q %Q',
197
PhabricatorEdgeConfig::TABLE_NAME_EDGE,
198
$where,
199
$order);
200
201
if ($this->needEdgeData) {
202
$data_ids = array_filter(ipull($edges, 'dataID'));
203
$data_map = array();
204
if ($data_ids) {
205
$data_rows = queryfx_all(
206
$conn_r,
207
'SELECT edgedata.* FROM %T edgedata WHERE id IN (%Ld)',
208
PhabricatorEdgeConfig::TABLE_NAME_EDGEDATA,
209
$data_ids);
210
foreach ($data_rows as $row) {
211
$data_map[$row['id']] = idx(
212
phutil_json_decode($row['data']),
213
'data');
214
}
215
}
216
foreach ($edges as $key => $edge) {
217
$edges[$key]['data'] = idx($data_map, $edge['dataID'], array());
218
}
219
}
220
221
foreach ($edges as $edge) {
222
$result[$edge['src']][$edge['type']][$edge['dst']] = $edge;
223
}
224
}
225
226
$this->resultSet = $result;
227
return $result;
228
}
229
230
231
/**
232
* Convenience function for selecting edge destination PHIDs after calling
233
* execute().
234
*
235
* Returns a flat list of PHIDs matching the provided source PHID and type
236
* filters. By default, the filters are empty so all PHIDs will be returned.
237
* For example, if you're doing a batch query from several sources, you might
238
* write code like this:
239
*
240
* $query = new PhabricatorEdgeQuery();
241
* $query->setViewer($viewer);
242
* $query->withSourcePHIDs(mpull($objects, 'getPHID'));
243
* $query->withEdgeTypes(array($some_type));
244
* $query->execute();
245
*
246
* // Gets all of the destinations.
247
* $all_phids = $query->getDestinationPHIDs();
248
* $handles = id(new PhabricatorHandleQuery())
249
* ->setViewer($viewer)
250
* ->withPHIDs($all_phids)
251
* ->execute();
252
*
253
* foreach ($objects as $object) {
254
* // Get all of the destinations for the given object.
255
* $dst_phids = $query->getDestinationPHIDs(array($object->getPHID()));
256
* $object->attachHandles(array_select_keys($handles, $dst_phids));
257
* }
258
*
259
* @param list? List of PHIDs to select, or empty to select all.
260
* @param list? List of edge types to select, or empty to select all.
261
* @return list<phid> List of matching destination PHIDs.
262
*/
263
public function getDestinationPHIDs(
264
array $src_phids = array(),
265
array $types = array()) {
266
if ($this->resultSet === null) {
267
throw new PhutilInvalidStateException('execute');
268
}
269
270
$result_phids = array();
271
272
$set = $this->resultSet;
273
if ($src_phids) {
274
$set = array_select_keys($set, $src_phids);
275
}
276
277
foreach ($set as $src => $edges_by_type) {
278
if ($types) {
279
$edges_by_type = array_select_keys($edges_by_type, $types);
280
}
281
282
foreach ($edges_by_type as $edges) {
283
foreach ($edges as $edge_phid => $edge) {
284
$result_phids[$edge_phid] = true;
285
}
286
}
287
}
288
289
return array_keys($result_phids);
290
}
291
292
293
/* -( Internals )---------------------------------------------------------- */
294
295
296
/**
297
* @task internal
298
*/
299
protected function buildWhereClause(AphrontDatabaseConnection $conn) {
300
$where = array();
301
302
if ($this->sourcePHIDs) {
303
$where[] = qsprintf(
304
$conn,
305
'edge.src IN (%Ls)',
306
$this->sourcePHIDs);
307
}
308
309
if ($this->edgeTypes) {
310
$where[] = qsprintf(
311
$conn,
312
'edge.type IN (%Ls)',
313
$this->edgeTypes);
314
}
315
316
if ($this->destPHIDs) {
317
// potentially complain if $this->edgeType was not set
318
$where[] = qsprintf(
319
$conn,
320
'edge.dst IN (%Ls)',
321
$this->destPHIDs);
322
}
323
324
return $this->formatWhereClause($conn, $where);
325
}
326
327
328
/**
329
* @task internal
330
*/
331
private function buildOrderClause(AphrontDatabaseConnection $conn) {
332
if ($this->order == self::ORDER_NEWEST_FIRST) {
333
return qsprintf($conn, 'ORDER BY edge.dateCreated DESC, edge.seq DESC');
334
} else {
335
return qsprintf($conn, 'ORDER BY edge.dateCreated ASC, edge.seq ASC');
336
}
337
}
338
339
}
340
341