[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/search/engine/ -> PhabricatorSearchEngineElastic.php (source)

   1  <?php
   2  
   3  final class PhabricatorSearchEngineElastic extends PhabricatorSearchEngine {
   4    private $uri;
   5    private $index;
   6    private $timeout;
   7  
   8    public function __construct($uri, $index) {
   9      $this->uri = $uri;
  10      $this->index = $index;
  11    }
  12  
  13    public function setTimeout($timeout) {
  14      $this->timeout = $timeout;
  15      return $this;
  16    }
  17  
  18    public function getTimeout() {
  19      return $this->timeout;
  20    }
  21  
  22    public function reindexAbstractDocument(
  23      PhabricatorSearchAbstractDocument $doc) {
  24  
  25      $type = $doc->getDocumentType();
  26      $phid = $doc->getPHID();
  27      $handle = id(new PhabricatorHandleQuery())
  28        ->setViewer(PhabricatorUser::getOmnipotentUser())
  29        ->withPHIDs(array($phid))
  30        ->executeOne();
  31  
  32      // URL is not used internally but it can be useful externally.
  33      $spec = array(
  34        'title'         => $doc->getDocumentTitle(),
  35        'url'           => PhabricatorEnv::getProductionURI($handle->getURI()),
  36        'dateCreated'   => $doc->getDocumentCreated(),
  37        '_timestamp'    => $doc->getDocumentModified(),
  38        'field'         => array(),
  39        'relationship'  => array(),
  40      );
  41  
  42      foreach ($doc->getFieldData() as $field) {
  43        $spec['field'][] = array_combine(array('type', 'corpus', 'aux'), $field);
  44      }
  45  
  46      foreach ($doc->getRelationshipData() as $relationship) {
  47        list($rtype, $to_phid, $to_type, $time) = $relationship;
  48        $spec['relationship'][$rtype][] = array(
  49          'phid'      => $to_phid,
  50          'phidType'  => $to_type,
  51          'when'      => $time,
  52        );
  53      }
  54  
  55      $this->executeRequest(
  56        "/{$type}/{$phid}/",
  57        $spec,
  58        $is_write = true);
  59    }
  60  
  61    public function reconstructDocument($phid) {
  62      $type = phid_get_type($phid);
  63  
  64      $response = $this->executeRequest("/{$type}/{$phid}", array());
  65  
  66      if (empty($response['exists'])) {
  67        return null;
  68      }
  69  
  70      $hit = $response['_source'];
  71  
  72      $doc = new PhabricatorSearchAbstractDocument();
  73      $doc->setPHID($phid);
  74      $doc->setDocumentType($response['_type']);
  75      $doc->setDocumentTitle($hit['title']);
  76      $doc->setDocumentCreated($hit['dateCreated']);
  77      $doc->setDocumentModified($hit['_timestamp']);
  78  
  79      foreach ($hit['field'] as $fdef) {
  80        $doc->addField($fdef['type'], $fdef['corpus'], $fdef['aux']);
  81      }
  82  
  83      foreach ($hit['relationship'] as $rtype => $rships) {
  84        foreach ($rships as $rship) {
  85          $doc->addRelationship(
  86            $rtype,
  87            $rship['phid'],
  88            $rship['phidType'],
  89            $rship['when']);
  90        }
  91      }
  92  
  93      return $doc;
  94    }
  95  
  96    private function buildSpec(PhabricatorSavedQuery $query) {
  97      $spec = array();
  98      $filter = array();
  99  
 100      if (strlen($query->getParameter('query'))) {
 101        $spec[] = array(
 102          'match' => array(
 103            'field.corpus' => array(
 104              'operator' => 'and',
 105              'query' => $query->getParameter('query'),
 106            ),
 107          ),
 108        );
 109      }
 110  
 111      $exclude = $query->getParameter('exclude');
 112      if ($exclude) {
 113        $filter[] = array(
 114          'not' => array(
 115            'ids' => array(
 116              'values' => array($exclude),
 117            ),
 118          ),
 119        );
 120      }
 121  
 122      $relationship_map = array(
 123        PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR =>
 124          $query->getParameter('authorPHIDs', array()),
 125        PhabricatorSearchRelationship::RELATIONSHIP_OWNER =>
 126          $query->getParameter('ownerPHIDs', array()),
 127        PhabricatorSearchRelationship::RELATIONSHIP_SUBSCRIBER =>
 128          $query->getParameter('subscriberPHIDs', array()),
 129        PhabricatorSearchRelationship::RELATIONSHIP_PROJECT =>
 130          $query->getParameter('projectPHIDs', array()),
 131        PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY =>
 132          $query->getParameter('repositoryPHIDs', array()),
 133      );
 134  
 135      $statuses = $query->getParameter('statuses', array());
 136      $statuses = array_fuse($statuses);
 137  
 138      $rel_open = PhabricatorSearchRelationship::RELATIONSHIP_OPEN;
 139      $rel_closed = PhabricatorSearchRelationship::RELATIONSHIP_CLOSED;
 140      $rel_unowned = PhabricatorSearchRelationship::RELATIONSHIP_UNOWNED;
 141  
 142      $include_open = !empty($statuses[$rel_open]);
 143      $include_closed = !empty($statuses[$rel_closed]);
 144  
 145      if ($include_open && !$include_closed) {
 146        $relationship_map[$rel_open] = true;
 147      } else if (!$include_open && $include_closed) {
 148        $relationship_map[$rel_closed] = true;
 149      }
 150  
 151      if ($query->getParameter('withUnowned')) {
 152        $relationship_map[$rel_unowned] = true;
 153      }
 154  
 155      foreach ($relationship_map as $field => $param) {
 156        if (is_array($param) && $param) {
 157          $should = array();
 158          foreach ($param as $val) {
 159            $should[] = array(
 160              'match' => array(
 161                "relationship.{$field}.phid" => array(
 162                  'query' => $val,
 163                  'type' => 'phrase',
 164                ),
 165              ),
 166            );
 167          }
 168          // We couldn't solve it by minimum_number_should_match because it can
 169          // match multiple owners without matching author.
 170          $spec[] = array('bool' => array('should' => $should));
 171        } else if ($param) {
 172          $filter[] = array(
 173            'exists' => array(
 174              'field' => "relationship.{$field}.phid",
 175            ),
 176          );
 177        }
 178      }
 179  
 180      if ($spec) {
 181        $spec = array('query' => array('bool' => array('must' => $spec)));
 182      }
 183  
 184      if ($filter) {
 185        $filter = array('filter' => array('and' => $filter));
 186        if (!$spec) {
 187          $spec = array('query' => array('match_all' => new stdClass()));
 188        }
 189        $spec = array(
 190          'query' => array(
 191            'filtered' => $spec + $filter,
 192          ),
 193        );
 194      }
 195  
 196      if (!$query->getParameter('query')) {
 197        $spec['sort'] = array(
 198          array('dateCreated' => 'desc'),
 199        );
 200      }
 201  
 202      $spec['from'] = (int)$query->getParameter('offset', 0);
 203      $spec['size'] = (int)$query->getParameter('limit', 25);
 204  
 205      return $spec;
 206    }
 207  
 208    public function executeSearch(PhabricatorSavedQuery $query) {
 209      $types = $query->getParameter('types');
 210      if (!$types) {
 211        $types = array_keys(
 212          PhabricatorSearchApplicationSearchEngine::getIndexableDocumentTypes());
 213      }
 214  
 215      // Don't use '/_search' for the case that there is something
 216      // else in the index (for example if 'phabricator' is only an alias to
 217      // some bigger index). Use '/$types/_search' instead.
 218      $uri = '/'.implode(',', $types).'/_search';
 219  
 220      try {
 221        $response = $this->executeRequest($uri, $this->buildSpec($query));
 222      } catch (HTTPFutureHTTPResponseStatus $ex) {
 223        // elasticsearch probably uses Lucene query syntax:
 224        // http://lucene.apache.org/core/3_6_1/queryparsersyntax.html
 225        // Try literal search if operator search fails.
 226        if (!strlen($query->getParameter('query'))) {
 227          throw $ex;
 228        }
 229        $query = clone $query;
 230        $query->setParameter(
 231          'query',
 232          addcslashes(
 233            $query->getParameter('query'), '+-&|!(){}[]^"~*?:\\'));
 234        $response = $this->executeRequest($uri, $this->buildSpec($query));
 235      }
 236  
 237      $phids = ipull($response['hits']['hits'], '_id');
 238      return $phids;
 239    }
 240  
 241    private function executeRequest($path, array $data, $is_write = false) {
 242      $uri = new PhutilURI($this->uri);
 243      $uri->setPath($this->index);
 244      $uri->appendPath($path);
 245      $data = json_encode($data);
 246  
 247      $future = new HTTPSFuture($uri, $data);
 248      if ($is_write) {
 249        $future->setMethod('PUT');
 250      }
 251      if ($this->getTimeout()) {
 252        $future->setTimeout($this->getTimeout());
 253      }
 254      list($body) = $future->resolvex();
 255  
 256      if ($is_write) {
 257        return null;
 258      }
 259  
 260      $body = json_decode($body, true);
 261      if (!is_array($body)) {
 262        throw new Exception('elasticsearch server returned invalid JSON!');
 263      }
 264  
 265      return $body;
 266    }
 267  
 268  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1