/**
  * Migrate a new version of an index.
  *
  * @param \Phroggyy\Discover\Contracts\Searchable $model
  * @param  int $version
  * @param  array $properties
  * @param  int $shards
  * @param  int $replicas
  */
 public function migrateIndex(Searchable $model, int $version, array $properties, $shards = 2, $replicas = 1)
 {
     $client = app(Client::class);
     $alias = $model->getSearchIndex();
     $type = $model->getSearchType();
     $index = $alias . '-' . $version;
     $currentIndex = $alias . '-' . $version - 1;
     $description = ['index' => $index, 'body' => ['settings' => ['index' => ['number_of_shards' => $shards, 'number_of_replicas' => $replicas]], 'mappings' => [$type => ['properties' => $properties]]]];
     $client->indices()->create($description);
     // Reindex the existing data if we're migrating
     if ($version > 1) {
         $timestamp = null;
         while (true) {
             if ($timestamp) {
                 $query = ['filtered' => ['query' => ['match_all' => []], 'filter' => ['range' => ['created_at' => ['gte' => $timestamp]]]]];
             } else {
                 $query = ['match_all' => []];
             }
             $timestamp = Carbon::now()->format('Y-m-d H:i:s');
             $search = $client->search(['search_type' => 'scan', 'scroll' => '1m', 'size' => 1000, 'index' => $currentIndex, 'sort' => ['_doc'], 'body' => ['query' => [$query]]]);
             $scrollId = $search['_scroll_id'];
             while (true) {
                 $response = $client->scroll(['scroll_id' => $scrollId, 'scroll' => '1m']);
                 if (!count($response['hits']['hits'])) {
                     break;
                 }
                 $scrollId = $response['_scroll_id'];
                 $results = array_map(function ($result) {
                     return ['create' => $result['_source']];
                 }, $response['hits']['hits']);
                 $client->bulk(['index' => $index, 'type' => $type, 'body' => $results]);
             }
             if ($scrollId == $search['_scroll_id']) {
                 break;
             }
         }
         $client->indices()->deleteAlias(['index' => $currentIndex, 'name' => $alias]);
     }
     $client->indices()->putAlias(['index' => $index, 'name' => $alias]);
 }
 /**
  * Structure the matches array.
  *
  * @param  \Phroggyy\Discover\Contracts\Searchable $model
  * @param  array $query
  * @return array|string
  */
 private function structureMatches(Searchable $model, $query)
 {
     // If the query is an array of arrays, we assume the
     // developer knows exactly what they're doing and
     // are providing a complete match query for us.
     if (is_array($query) && !Arr::isAssoc($query)) {
         return $query;
     }
     $key = '';
     // If the search index turns out to actually be nested, we
     // want to make sure we use the name of the subdocument
     // when such a query is performed. This is necessary.
     if ($this->indexIsNested($model->getSearchIndex())) {
         $key = $this->retrieveNestedIndex($model->getSearchIndex())[1] . '.';
     }
     // If the query is just a string, we assume the user
     // intends to just do a simple match query on the
     // default search field defined in their model.
     if (is_string($query)) {
         return [['match' => [$key . $model->getDefaultSearchField() => $query]]];
     }
     $query = Collection::make($query);
     return $query->map(function ($constraint, $property) use($key) {
         if (strpos($property, '.') === false) {
             $property = $key . $property;
         }
         return ['match' => [$property => $constraint]];
     })->values()->all();
 }