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