Path: blob/master/src/applications/diviner/query/DivinerAtomQuery.php
12256 views
<?php12final class DivinerAtomQuery extends PhabricatorCursorPagedPolicyAwareQuery {34private $ids;5private $phids;6private $bookPHIDs;7private $names;8private $types;9private $contexts;10private $indexes;11private $isDocumentable;12private $isGhost;13private $nodeHashes;14private $titles;15private $nameContains;16private $repositoryPHIDs;1718private $needAtoms;19private $needExtends;20private $needChildren;21private $needRepositories;2223public function withIDs(array $ids) {24$this->ids = $ids;25return $this;26}2728public function withPHIDs(array $phids) {29$this->phids = $phids;30return $this;31}3233public function withBookPHIDs(array $phids) {34$this->bookPHIDs = $phids;35return $this;36}3738public function withTypes(array $types) {39$this->types = $types;40return $this;41}4243public function withNames(array $names) {44$this->names = $names;45return $this;46}4748public function withContexts(array $contexts) {49$this->contexts = $contexts;50return $this;51}5253public function withIndexes(array $indexes) {54$this->indexes = $indexes;55return $this;56}5758public function withNodeHashes(array $hashes) {59$this->nodeHashes = $hashes;60return $this;61}6263public function withTitles($titles) {64$this->titles = $titles;65return $this;66}6768public function withNameContains($text) {69$this->nameContains = $text;70return $this;71}7273public function needAtoms($need) {74$this->needAtoms = $need;75return $this;76}7778public function needChildren($need) {79$this->needChildren = $need;80return $this;81}8283/**84* Include or exclude "ghosts", which are symbols which used to exist but do85* not exist currently (for example, a function which existed in an older86* version of the codebase but was deleted).87*88* These symbols had PHIDs assigned to them, and may have other sorts of89* metadata that we don't want to lose (like comments or flags), so we don't90* delete them outright. They might also come back in the future: the change91* which deleted the symbol might be reverted, or the documentation might92* have been generated incorrectly by accident. In these cases, we can93* restore the original data.94*95* @param bool96* @return this97*/98public function withGhosts($ghosts) {99$this->isGhost = $ghosts;100return $this;101}102103public function needExtends($need) {104$this->needExtends = $need;105return $this;106}107108public function withIsDocumentable($documentable) {109$this->isDocumentable = $documentable;110return $this;111}112113public function withRepositoryPHIDs(array $repository_phids) {114$this->repositoryPHIDs = $repository_phids;115return $this;116}117118public function needRepositories($need_repositories) {119$this->needRepositories = $need_repositories;120return $this;121}122123protected function loadPage() {124$table = new DivinerLiveSymbol();125$conn_r = $table->establishConnection('r');126127$data = queryfx_all(128$conn_r,129'SELECT * FROM %T %Q %Q %Q',130$table->getTableName(),131$this->buildWhereClause($conn_r),132$this->buildOrderClause($conn_r),133$this->buildLimitClause($conn_r));134135return $table->loadAllFromArray($data);136}137138protected function willFilterPage(array $atoms) {139assert_instances_of($atoms, 'DivinerLiveSymbol');140141$books = array_unique(mpull($atoms, 'getBookPHID'));142143$books = id(new DivinerBookQuery())144->setViewer($this->getViewer())145->withPHIDs($books)146->execute();147$books = mpull($books, null, 'getPHID');148149foreach ($atoms as $key => $atom) {150$book = idx($books, $atom->getBookPHID());151if (!$book) {152$this->didRejectResult($atom);153unset($atoms[$key]);154continue;155}156$atom->attachBook($book);157}158159if ($this->needAtoms) {160$atom_data = id(new DivinerLiveAtom())->loadAllWhere(161'symbolPHID IN (%Ls)',162mpull($atoms, 'getPHID'));163$atom_data = mpull($atom_data, null, 'getSymbolPHID');164165foreach ($atoms as $key => $atom) {166$data = idx($atom_data, $atom->getPHID());167$atom->attachAtom($data);168}169}170171// Load all of the symbols this symbol extends, recursively. Commonly,172// this means all the ancestor classes and interfaces it extends and173// implements.174if ($this->needExtends) {175// First, load all the matching symbols by name. This does 99% of the176// work in most cases, assuming things are named at all reasonably.177$names = array();178foreach ($atoms as $atom) {179if (!$atom->getAtom()) {180continue;181}182183foreach ($atom->getAtom()->getExtends() as $xref) {184$names[] = $xref->getName();185}186}187188if ($names) {189$xatoms = id(new DivinerAtomQuery())190->setViewer($this->getViewer())191->withNames($names)192->withGhosts(false)193->needExtends(true)194->needAtoms(true)195->needChildren($this->needChildren)196->execute();197$xatoms = mgroup($xatoms, 'getName', 'getType', 'getBookPHID');198} else {199$xatoms = array();200}201202foreach ($atoms as $atom) {203$atom_lang = null;204$atom_extends = array();205206if ($atom->getAtom()) {207$atom_lang = $atom->getAtom()->getLanguage();208$atom_extends = $atom->getAtom()->getExtends();209}210211$extends = array();212213foreach ($atom_extends as $xref) {214// If there are no symbols of the matching name and type, we can't215// resolve this.216if (empty($xatoms[$xref->getName()][$xref->getType()])) {217continue;218}219220// If we found matches in the same documentation book, prefer them221// over other matches. Otherwise, look at all the matches.222$matches = $xatoms[$xref->getName()][$xref->getType()];223if (isset($matches[$atom->getBookPHID()])) {224$maybe = $matches[$atom->getBookPHID()];225} else {226$maybe = array_mergev($matches);227}228229if (!$maybe) {230continue;231}232233// Filter out matches in a different language, since, e.g., PHP234// classes can not implement JS classes.235$same_lang = array();236foreach ($maybe as $xatom) {237if ($xatom->getAtom()->getLanguage() == $atom_lang) {238$same_lang[] = $xatom;239}240}241242if (!$same_lang) {243continue;244}245246// If we have duplicates remaining, just pick the first one. There's247// nothing more we can do to figure out which is the real one.248$extends[] = head($same_lang);249}250251$atom->attachExtends($extends);252}253}254255if ($this->needChildren) {256$child_hashes = $this->getAllChildHashes($atoms, $this->needExtends);257258if ($child_hashes) {259$children = id(new DivinerAtomQuery())260->setViewer($this->getViewer())261->withNodeHashes($child_hashes)262->needAtoms($this->needAtoms)263->execute();264265$children = mpull($children, null, 'getNodeHash');266} else {267$children = array();268}269270$this->attachAllChildren($atoms, $children, $this->needExtends);271}272273if ($this->needRepositories) {274$repositories = id(new PhabricatorRepositoryQuery())275->setViewer($this->getViewer())276->withPHIDs(mpull($atoms, 'getRepositoryPHID'))277->execute();278$repositories = mpull($repositories, null, 'getPHID');279280foreach ($atoms as $key => $atom) {281if ($atom->getRepositoryPHID() === null) {282$atom->attachRepository(null);283continue;284}285286$repository = idx($repositories, $atom->getRepositoryPHID());287288if (!$repository) {289$this->didRejectResult($atom);290unset($atom[$key]);291continue;292}293294$atom->attachRepository($repository);295}296}297298return $atoms;299}300301protected function buildWhereClause(AphrontDatabaseConnection $conn) {302$where = array();303304if ($this->ids) {305$where[] = qsprintf(306$conn,307'id IN (%Ld)',308$this->ids);309}310311if ($this->phids) {312$where[] = qsprintf(313$conn,314'phid IN (%Ls)',315$this->phids);316}317318if ($this->bookPHIDs) {319$where[] = qsprintf(320$conn,321'bookPHID IN (%Ls)',322$this->bookPHIDs);323}324325if ($this->types) {326$where[] = qsprintf(327$conn,328'type IN (%Ls)',329$this->types);330}331332if ($this->names) {333$where[] = qsprintf(334$conn,335'name IN (%Ls)',336$this->names);337}338339if ($this->titles) {340$hashes = array();341342foreach ($this->titles as $title) {343$slug = DivinerAtomRef::normalizeTitleString($title);344$hash = PhabricatorHash::digestForIndex($slug);345$hashes[] = $hash;346}347348$where[] = qsprintf(349$conn,350'titleSlugHash in (%Ls)',351$hashes);352}353354if ($this->contexts) {355$with_null = false;356$contexts = $this->contexts;357358foreach ($contexts as $key => $value) {359if ($value === null) {360unset($contexts[$key]);361$with_null = true;362continue;363}364}365366if ($contexts && $with_null) {367$where[] = qsprintf(368$conn,369'context IN (%Ls) OR context IS NULL',370$contexts);371} else if ($contexts) {372$where[] = qsprintf(373$conn,374'context IN (%Ls)',375$contexts);376} else if ($with_null) {377$where[] = qsprintf(378$conn,379'context IS NULL');380}381}382383if ($this->indexes) {384$where[] = qsprintf(385$conn,386'atomIndex IN (%Ld)',387$this->indexes);388}389390if ($this->isDocumentable !== null) {391$where[] = qsprintf(392$conn,393'isDocumentable = %d',394(int)$this->isDocumentable);395}396397if ($this->isGhost !== null) {398if ($this->isGhost) {399$where[] = qsprintf($conn, 'graphHash IS NULL');400} else {401$where[] = qsprintf($conn, 'graphHash IS NOT NULL');402}403}404405if ($this->nodeHashes) {406$where[] = qsprintf(407$conn,408'nodeHash IN (%Ls)',409$this->nodeHashes);410}411412if ($this->nameContains) {413// NOTE: This `CONVERT()` call makes queries case-insensitive, since414// the column has binary collation. Eventually, this should move into415// fulltext.416$where[] = qsprintf(417$conn,418'CONVERT(name USING utf8) LIKE %~',419$this->nameContains);420}421422if ($this->repositoryPHIDs) {423$where[] = qsprintf(424$conn,425'repositoryPHID IN (%Ls)',426$this->repositoryPHIDs);427}428429$where[] = $this->buildPagingClause($conn);430431return $this->formatWhereClause($conn, $where);432}433434/**435* Walk a list of atoms and collect all the node hashes of the atoms'436* children. When recursing, also walk up the tree and collect children of437* atoms they extend.438*439* @param list<DivinerLiveSymbol> List of symbols to collect child hashes of.440* @param bool True to collect children of extended atoms,441* as well.442* @return map<string, string> Hashes of atoms' children.443*/444private function getAllChildHashes(array $symbols, $recurse_up) {445assert_instances_of($symbols, 'DivinerLiveSymbol');446447$hashes = array();448foreach ($symbols as $symbol) {449$child_hashes = array();450451if ($symbol->getAtom()) {452$child_hashes = $symbol->getAtom()->getChildHashes();453}454455foreach ($child_hashes as $hash) {456$hashes[$hash] = $hash;457}458459if ($recurse_up) {460$hashes += $this->getAllChildHashes($symbol->getExtends(), true);461}462}463464return $hashes;465}466467/**468* Attach child atoms to existing atoms. In recursive mode, also attach child469* atoms to atoms that these atoms extend.470*471* @param list<DivinerLiveSymbol> List of symbols to attach children to.472* @param map<string, DivinerLiveSymbol> Map of symbols, keyed by node hash.473* @param bool True to attach children to extended atoms, as well.474* @return void475*/476private function attachAllChildren(477array $symbols,478array $children,479$recurse_up) {480481assert_instances_of($symbols, 'DivinerLiveSymbol');482assert_instances_of($children, 'DivinerLiveSymbol');483484foreach ($symbols as $symbol) {485$child_hashes = array();486$symbol_children = array();487488if ($symbol->getAtom()) {489$child_hashes = $symbol->getAtom()->getChildHashes();490}491492foreach ($child_hashes as $hash) {493if (isset($children[$hash])) {494$symbol_children[] = $children[$hash];495}496}497498$symbol->attachChildren($symbol_children);499500if ($recurse_up) {501$this->attachAllChildren($symbol->getExtends(), $children, true);502}503}504}505506public function getQueryApplicationClass() {507return 'PhabricatorDivinerApplication';508}509510}511512513