diff --git a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php --- a/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php +++ b/src/applications/search/fulltextstorage/PhabricatorMySQLFulltextStorageEngine.php @@ -153,71 +153,105 @@ } public function executeSearch(PhabricatorSavedQuery $query) { - $where = array(); - $join = array(); - $order = 'ORDER BY documentCreated DESC'; + $table = new PhabricatorSearchDocument(); + $document_table = $table->getTableName(); + $conn = $table->establishConnection('r'); - $dao_doc = new PhabricatorSearchDocument(); - $dao_field = new PhabricatorSearchDocumentField(); + $subquery = $this->newFulltextSubquery($query, $conn); - $t_doc = $dao_doc->getTableName(); - $t_field = $dao_field->getTableName(); + $offset = (int)$query->getParameter('offset', 0); + $limit = (int)$query->getParameter('limit', 25); + + // NOTE: We must JOIN the subquery in order to apply a limit. + $results = queryfx_all( + $conn, + 'SELECT + documentPHID, + MAX(fieldScore) AS documentScore + FROM (%Q) query + JOIN %T root ON query.documentPHID = root.phid + GROUP BY documentPHID + ORDER BY documentScore DESC + LIMIT %d, %d', + $subquery, + $document_table, + $offset, + $limit); + + return ipull($results, 'documentPHID'); + } + + private function newFulltextSubquery( + PhabricatorSavedQuery $query, + AphrontDatabaseConnection $conn) { + + $field = new PhabricatorSearchDocumentField(); + $field_table = $field->getTableName(); - $conn_r = $dao_doc->establishConnection('r'); + $document = new PhabricatorSearchDocument(); + $document_table = $document->getTableName(); + + $select = array(); + $select[] = 'document.phid AS documentPHID'; + + $join = array(); + $where = array(); $raw_query = $query->getParameter('query'); - $q = $this->compileQuery($raw_query); + $compiled_query = $this->compileQuery($raw_query); + if (strlen($compiled_query)) { + $select[] = qsprintf( + $conn, + 'MATCH(corpus) AGAINST (%s IN BOOLEAN MODE) AS fieldScore', + $compiled_query); - if (strlen($q)) { - $join[] = qsprintf( - $conn_r, + $join[] = qsprintf( + $conn, '%T field ON field.phid = document.phid', - $t_field); + $field_table); + $where[] = qsprintf( - $conn_r, + $conn, 'MATCH(corpus) AGAINST (%s IN BOOLEAN MODE)', - $q); - - // When searching for a string, promote user listings above other - // listings. - $order = qsprintf( - $conn_r, - 'ORDER BY - IF(documentType = %s, 0, 1) ASC, - MAX(MATCH(corpus) AGAINST (%s)) DESC', - 'USER', - $q); - - $field = $query->getParameter('field'); - if ($field) { + $compiled_query); + + if ($query->getParameter('field')) { $where[] = qsprintf( - $conn_r, + $conn, 'field.field = %s', $field); } + } else { + $select[] = qsprintf( + $conn, + 'document.dateCreated AS fieldScore'); } $exclude = $query->getParameter('exclude'); if ($exclude) { - $where[] = qsprintf($conn_r, 'document.phid != %s', $exclude); + $where[] = qsprintf( + $conn, + 'document.phid != %s', + $exclude); } $types = $query->getParameter('types'); if ($types) { - if (strlen($q)) { + if (strlen($compiled_query)) { $where[] = qsprintf( - $conn_r, + $conn, 'field.phidType IN (%Ls)', $types); } + $where[] = qsprintf( - $conn_r, + $conn, 'document.documentType IN (%Ls)', $types); } $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'authorPHIDs', PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR); @@ -231,14 +265,14 @@ if ($include_open && !$include_closed) { $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'statuses', $open_rel, true); } else if ($include_closed && !$include_open) { $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'statuses', $closed_rel, @@ -247,46 +281,47 @@ if ($query->getParameter('withAnyOwner')) { $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'withAnyOwner', PhabricatorSearchRelationship::RELATIONSHIP_OWNER, true); } else if ($query->getParameter('withUnowned')) { $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'withUnowned', PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED, true); } else { $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'ownerPHIDs', PhabricatorSearchRelationship::RELATIONSHIP_OWNER); } $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'subscriberPHIDs', PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER); $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'projectPHIDs', PhabricatorSearchRelationship::RELATIONSHIP_PROJECT); $join[] = $this->joinRelationship( - $conn_r, + $conn, $query, 'repository', PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY); - $join = array_filter($join); + $select = implode(', ', $select); + $join = array_filter($join); foreach ($join as $key => $clause) { $join[$key] = ' JOIN '.$clause; } @@ -298,27 +333,13 @@ $where = ''; } - $offset = (int)$query->getParameter('offset', 0); - $limit = (int)$query->getParameter('limit', 25); - - $hits = queryfx_all( - $conn_r, - 'SELECT - document.phid - FROM %T document - %Q - %Q - GROUP BY document.phid - %Q - LIMIT %d, %d', - $t_doc, + return qsprintf( + $conn, + 'SELECT %Q FROM %T document %Q %Q LIMIT 1000', + $select, + $document_table, $join, - $where, - $order, - $offset, - $limit); - - return ipull($hits, 'phid'); + $where); } protected function joinRelationship(