Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/diviner/query/DivinerAtomQuery.php
12256 views
1
<?php
2
3
final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {
4
5
private $ids;
6
private $phids;
7
private $bookPHIDs;
8
private $names;
9
private $types;
10
private $contexts;
11
private $indexes;
12
private $isDocumentable;
13
private $isGhost;
14
private $nodeHashes;
15
private $titles;
16
private $nameContains;
17
private $repositoryPHIDs;
18
19
private $needAtoms;
20
private $needExtends;
21
private $needChildren;
22
private $needRepositories;
23
24
public function withIDs(array $ids) {
25
$this->ids = $ids;
26
return $this;
27
}
28
29
public function withPHIDs(array $phids) {
30
$this->phids = $phids;
31
return $this;
32
}
33
34
public function withBookPHIDs(array $phids) {
35
$this->bookPHIDs = $phids;
36
return $this;
37
}
38
39
public function withTypes(array $types) {
40
$this->types = $types;
41
return $this;
42
}
43
44
public function withNames(array $names) {
45
$this->names = $names;
46
return $this;
47
}
48
49
public function withContexts(array $contexts) {
50
$this->contexts = $contexts;
51
return $this;
52
}
53
54
public function withIndexes(array $indexes) {
55
$this->indexes = $indexes;
56
return $this;
57
}
58
59
public function withNodeHashes(array $hashes) {
60
$this->nodeHashes = $hashes;
61
return $this;
62
}
63
64
public function withTitles($titles) {
65
$this->titles = $titles;
66
return $this;
67
}
68
69
public function withNameContains($text) {
70
$this->nameContains = $text;
71
return $this;
72
}
73
74
public function needAtoms($need) {
75
$this->needAtoms = $need;
76
return $this;
77
}
78
79
public function needChildren($need) {
80
$this->needChildren = $need;
81
return $this;
82
}
83
84
/**
85
* Include or exclude "ghosts", which are symbols which used to exist but do
86
* not exist currently (for example, a function which existed in an older
87
* version of the codebase but was deleted).
88
*
89
* These symbols had PHIDs assigned to them, and may have other sorts of
90
* metadata that we don't want to lose (like comments or flags), so we don't
91
* delete them outright. They might also come back in the future: the change
92
* which deleted the symbol might be reverted, or the documentation might
93
* have been generated incorrectly by accident. In these cases, we can
94
* restore the original data.
95
*
96
* @param bool
97
* @return this
98
*/
99
public function withGhosts($ghosts) {
100
$this->isGhost = $ghosts;
101
return $this;
102
}
103
104
public function needExtends($need) {
105
$this->needExtends = $need;
106
return $this;
107
}
108
109
public function withIsDocumentable($documentable) {
110
$this->isDocumentable = $documentable;
111
return $this;
112
}
113
114
public function withRepositoryPHIDs(array $repository_phids) {
115
$this->repositoryPHIDs = $repository_phids;
116
return $this;
117
}
118
119
public function needRepositories($need_repositories) {
120
$this->needRepositories = $need_repositories;
121
return $this;
122
}
123
124
protected function loadPage() {
125
$table = new DivinerLiveSymbol();
126
$conn_r = $table->establishConnection('r');
127
128
$data = queryfx_all(
129
$conn_r,
130
'SELECT * FROM %T %Q %Q %Q',
131
$table->getTableName(),
132
$this->buildWhereClause($conn_r),
133
$this->buildOrderClause($conn_r),
134
$this->buildLimitClause($conn_r));
135
136
return $table->loadAllFromArray($data);
137
}
138
139
protected function willFilterPage(array $atoms) {
140
assert_instances_of($atoms, 'DivinerLiveSymbol');
141
142
$books = array_unique(mpull($atoms, 'getBookPHID'));
143
144
$books = id(new DivinerBookQuery())
145
->setViewer($this->getViewer())
146
->withPHIDs($books)
147
->execute();
148
$books = mpull($books, null, 'getPHID');
149
150
foreach ($atoms as $key => $atom) {
151
$book = idx($books, $atom->getBookPHID());
152
if (!$book) {
153
$this->didRejectResult($atom);
154
unset($atoms[$key]);
155
continue;
156
}
157
$atom->attachBook($book);
158
}
159
160
if ($this->needAtoms) {
161
$atom_data = id(new DivinerLiveAtom())->loadAllWhere(
162
'symbolPHID IN (%Ls)',
163
mpull($atoms, 'getPHID'));
164
$atom_data = mpull($atom_data, null, 'getSymbolPHID');
165
166
foreach ($atoms as $key => $atom) {
167
$data = idx($atom_data, $atom->getPHID());
168
$atom->attachAtom($data);
169
}
170
}
171
172
// Load all of the symbols this symbol extends, recursively. Commonly,
173
// this means all the ancestor classes and interfaces it extends and
174
// implements.
175
if ($this->needExtends) {
176
// First, load all the matching symbols by name. This does 99% of the
177
// work in most cases, assuming things are named at all reasonably.
178
$names = array();
179
foreach ($atoms as $atom) {
180
if (!$atom->getAtom()) {
181
continue;
182
}
183
184
foreach ($atom->getAtom()->getExtends() as $xref) {
185
$names[] = $xref->getName();
186
}
187
}
188
189
if ($names) {
190
$xatoms = id(new DivinerAtomQuery())
191
->setViewer($this->getViewer())
192
->withNames($names)
193
->withGhosts(false)
194
->needExtends(true)
195
->needAtoms(true)
196
->needChildren($this->needChildren)
197
->execute();
198
$xatoms = mgroup($xatoms, 'getName', 'getType', 'getBookPHID');
199
} else {
200
$xatoms = array();
201
}
202
203
foreach ($atoms as $atom) {
204
$atom_lang = null;
205
$atom_extends = array();
206
207
if ($atom->getAtom()) {
208
$atom_lang = $atom->getAtom()->getLanguage();
209
$atom_extends = $atom->getAtom()->getExtends();
210
}
211
212
$extends = array();
213
214
foreach ($atom_extends as $xref) {
215
// If there are no symbols of the matching name and type, we can't
216
// resolve this.
217
if (empty($xatoms[$xref->getName()][$xref->getType()])) {
218
continue;
219
}
220
221
// If we found matches in the same documentation book, prefer them
222
// over other matches. Otherwise, look at all the matches.
223
$matches = $xatoms[$xref->getName()][$xref->getType()];
224
if (isset($matches[$atom->getBookPHID()])) {
225
$maybe = $matches[$atom->getBookPHID()];
226
} else {
227
$maybe = array_mergev($matches);
228
}
229
230
if (!$maybe) {
231
continue;
232
}
233
234
// Filter out matches in a different language, since, e.g., PHP
235
// classes can not implement JS classes.
236
$same_lang = array();
237
foreach ($maybe as $xatom) {
238
if ($xatom->getAtom()->getLanguage() == $atom_lang) {
239
$same_lang[] = $xatom;
240
}
241
}
242
243
if (!$same_lang) {
244
continue;
245
}
246
247
// If we have duplicates remaining, just pick the first one. There's
248
// nothing more we can do to figure out which is the real one.
249
$extends[] = head($same_lang);
250
}
251
252
$atom->attachExtends($extends);
253
}
254
}
255
256
if ($this->needChildren) {
257
$child_hashes = $this->getAllChildHashes($atoms, $this->needExtends);
258
259
if ($child_hashes) {
260
$children = id(new DivinerAtomQuery())
261
->setViewer($this->getViewer())
262
->withNodeHashes($child_hashes)
263
->needAtoms($this->needAtoms)
264
->execute();
265
266
$children = mpull($children, null, 'getNodeHash');
267
} else {
268
$children = array();
269
}
270
271
$this->attachAllChildren($atoms, $children, $this->needExtends);
272
}
273
274
if ($this->needRepositories) {
275
$repositories = id(new PhabricatorRepositoryQuery())
276
->setViewer($this->getViewer())
277
->withPHIDs(mpull($atoms, 'getRepositoryPHID'))
278
->execute();
279
$repositories = mpull($repositories, null, 'getPHID');
280
281
foreach ($atoms as $key => $atom) {
282
if ($atom->getRepositoryPHID() === null) {
283
$atom->attachRepository(null);
284
continue;
285
}
286
287
$repository = idx($repositories, $atom->getRepositoryPHID());
288
289
if (!$repository) {
290
$this->didRejectResult($atom);
291
unset($atom[$key]);
292
continue;
293
}
294
295
$atom->attachRepository($repository);
296
}
297
}
298
299
return $atoms;
300
}
301
302
protected function buildWhereClause(AphrontDatabaseConnection $conn) {
303
$where = array();
304
305
if ($this->ids) {
306
$where[] = qsprintf(
307
$conn,
308
'id IN (%Ld)',
309
$this->ids);
310
}
311
312
if ($this->phids) {
313
$where[] = qsprintf(
314
$conn,
315
'phid IN (%Ls)',
316
$this->phids);
317
}
318
319
if ($this->bookPHIDs) {
320
$where[] = qsprintf(
321
$conn,
322
'bookPHID IN (%Ls)',
323
$this->bookPHIDs);
324
}
325
326
if ($this->types) {
327
$where[] = qsprintf(
328
$conn,
329
'type IN (%Ls)',
330
$this->types);
331
}
332
333
if ($this->names) {
334
$where[] = qsprintf(
335
$conn,
336
'name IN (%Ls)',
337
$this->names);
338
}
339
340
if ($this->titles) {
341
$hashes = array();
342
343
foreach ($this->titles as $title) {
344
$slug = DivinerAtomRef::normalizeTitleString($title);
345
$hash = PhabricatorHash::digestForIndex($slug);
346
$hashes[] = $hash;
347
}
348
349
$where[] = qsprintf(
350
$conn,
351
'titleSlugHash in (%Ls)',
352
$hashes);
353
}
354
355
if ($this->contexts) {
356
$with_null = false;
357
$contexts = $this->contexts;
358
359
foreach ($contexts as $key => $value) {
360
if ($value === null) {
361
unset($contexts[$key]);
362
$with_null = true;
363
continue;
364
}
365
}
366
367
if ($contexts && $with_null) {
368
$where[] = qsprintf(
369
$conn,
370
'context IN (%Ls) OR context IS NULL',
371
$contexts);
372
} else if ($contexts) {
373
$where[] = qsprintf(
374
$conn,
375
'context IN (%Ls)',
376
$contexts);
377
} else if ($with_null) {
378
$where[] = qsprintf(
379
$conn,
380
'context IS NULL');
381
}
382
}
383
384
if ($this->indexes) {
385
$where[] = qsprintf(
386
$conn,
387
'atomIndex IN (%Ld)',
388
$this->indexes);
389
}
390
391
if ($this->isDocumentable !== null) {
392
$where[] = qsprintf(
393
$conn,
394
'isDocumentable = %d',
395
(int)$this->isDocumentable);
396
}
397
398
if ($this->isGhost !== null) {
399
if ($this->isGhost) {
400
$where[] = qsprintf($conn, 'graphHash IS NULL');
401
} else {
402
$where[] = qsprintf($conn, 'graphHash IS NOT NULL');
403
}
404
}
405
406
if ($this->nodeHashes) {
407
$where[] = qsprintf(
408
$conn,
409
'nodeHash IN (%Ls)',
410
$this->nodeHashes);
411
}
412
413
if ($this->nameContains) {
414
// NOTE: This `CONVERT()` call makes queries case-insensitive, since
415
// the column has binary collation. Eventually, this should move into
416
// fulltext.
417
$where[] = qsprintf(
418
$conn,
419
'CONVERT(name USING utf8) LIKE %~',
420
$this->nameContains);
421
}
422
423
if ($this->repositoryPHIDs) {
424
$where[] = qsprintf(
425
$conn,
426
'repositoryPHID IN (%Ls)',
427
$this->repositoryPHIDs);
428
}
429
430
$where[] = $this->buildPagingClause($conn);
431
432
return $this->formatWhereClause($conn, $where);
433
}
434
435
/**
436
* Walk a list of atoms and collect all the node hashes of the atoms'
437
* children. When recursing, also walk up the tree and collect children of
438
* atoms they extend.
439
*
440
* @param list<DivinerLiveSymbol> List of symbols to collect child hashes of.
441
* @param bool True to collect children of extended atoms,
442
* as well.
443
* @return map<string, string> Hashes of atoms' children.
444
*/
445
private function getAllChildHashes(array $symbols, $recurse_up) {
446
assert_instances_of($symbols, 'DivinerLiveSymbol');
447
448
$hashes = array();
449
foreach ($symbols as $symbol) {
450
$child_hashes = array();
451
452
if ($symbol->getAtom()) {
453
$child_hashes = $symbol->getAtom()->getChildHashes();
454
}
455
456
foreach ($child_hashes as $hash) {
457
$hashes[$hash] = $hash;
458
}
459
460
if ($recurse_up) {
461
$hashes += $this->getAllChildHashes($symbol->getExtends(), true);
462
}
463
}
464
465
return $hashes;
466
}
467
468
/**
469
* Attach child atoms to existing atoms. In recursive mode, also attach child
470
* atoms to atoms that these atoms extend.
471
*
472
* @param list<DivinerLiveSymbol> List of symbols to attach children to.
473
* @param map<string, DivinerLiveSymbol> Map of symbols, keyed by node hash.
474
* @param bool True to attach children to extended atoms, as well.
475
* @return void
476
*/
477
private function attachAllChildren(
478
array $symbols,
479
array $children,
480
$recurse_up) {
481
482
assert_instances_of($symbols, 'DivinerLiveSymbol');
483
assert_instances_of($children, 'DivinerLiveSymbol');
484
485
foreach ($symbols as $symbol) {
486
$child_hashes = array();
487
$symbol_children = array();
488
489
if ($symbol->getAtom()) {
490
$child_hashes = $symbol->getAtom()->getChildHashes();
491
}
492
493
foreach ($child_hashes as $hash) {
494
if (isset($children[$hash])) {
495
$symbol_children[] = $children[$hash];
496
}
497
}
498
499
$symbol->attachChildren($symbol_children);
500
501
if ($recurse_up) {
502
$this->attachAllChildren($symbol->getExtends(), $children, true);
503
}
504
}
505
}
506
507
public function getQueryApplicationClass() {
508
return 'PhabricatorDivinerApplication';
509
}
510
511
}
512
513