[[elasticsearch.operations]]
= Elasticsearch Operations

Spring Data Elasticsearch uses several interfaces to define the operations that can be called against an Elasticsearch index (for a description of the reactive interfaces see xref:elasticsearch/reactive-template.adoc[]).

* `IndexOperations` defines actions on index level like creating or deleting an index.
* `DocumentOperations` defines actions to store, update and retrieve entities based on their id.
* `SearchOperations` define the actions to search for multiple entities using queries
* `ElasticsearchOperations` combines the `DocumentOperations` and `SearchOperations` interfaces.

These interfaces correspond to the structuring of the https://www.elastic.co/guide/en/elasticsearch/reference/current/rest-apis.html[Elasticsearch API].

The default implementations of the interfaces offer:

* index management functionality.
* Read/Write mapping support for domain types.
* A rich query and criteria api.
* Resource management and Exception translation.

[NOTE]
====
.Index management and automatic creation of indices and mappings.
The `IndexOperations` interface and the provided implementation which can be obtained from an `ElasticsearchOperations` instance - for example with a call to `operations.indexOps(clazz)`- give the user the ability to create indices, put mappings or store template and alias information in the Elasticsearch cluster.
Details of the index that will be created can be set by using the `@Setting` annotation, refer to xref:elasticsearch/misc.adoc#elasticsearc.misc.index.settings[Index settings] for further information.

**None of these operations are done automatically** by the implementations of `IndexOperations` or `ElasticsearchOperations`.
It is the user's responsibility to call the methods.

There is support for automatic creation of indices and writing the mappings when using Spring Data Elasticsearch repositories, see xref:elasticsearch/repositories/elasticsearch-repositories.adoc#elasticsearch.repositories.autocreation[Automatic creation of indices with the corresponding mapping]

====

[[elasticsearch.operations.usage]]
== Usage examples

The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller.
The example assumes that `Person` is a class that is annotated with `@Document`, `@Id` etc (see xref:elasticsearch/object-mapping.adoc#elasticsearch.mapping.meta-model.annotations[Mapping Annotation Overview]).

.ElasticsearchOperations usage
====
[source,java]
----
@RestController
@RequestMapping("/")
public class TestController {

  private  ElasticsearchOperations elasticsearchOperations;

  public TestController(ElasticsearchOperations elasticsearchOperations) { <.>
    this.elasticsearchOperations = elasticsearchOperations;
  }

  @PostMapping("/person")
  public String save(@RequestBody Person person) {                         <.>
    Person savedEntity = elasticsearchOperations.save(person);
    return savedEntity.getId();
  }

  @GetMapping("/person/{id}")
  public Person findById(@PathVariable("id")  Long id) {                   <.>
    Person person = elasticsearchOperations.get(id.toString(), Person.class);
    return person;
  }
}

----

<.> Let Spring inject the provided `ElasticsearchOperations` bean in the constructor.
<.> Store some entity in the Elasticsearch cluster.
The id is read from the returned entity, as it might have been null in the `person` object and been created by Elasticsearch.
<.> Retrieve the entity with a get by id.
====

To see the full possibilities of `ElasticsearchOperations` please refer to the API documentation.


[[elasticsearch.operations.searchresulttypes]]
== Search Result Types

When a document is retrieved with the methods of the  `DocumentOperations` interface, just the found entity will be returned.
When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.

In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information.
These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations.
The following classes and interfaces are now available:

.SearchHit<T>
Contains the following information:

* Id
* Score
* Sort Values
* Highlight fields
* Inner hits (this is an embedded `SearchHits` object containing eventually returned inner hits)
* The retrieved entity of type <T>

.SearchHits<T>
Contains the following information:

* Number of total hits
* Total hits relation
* Maximum score
* A list of `SearchHit<T>` objects
* Returned aggregations
* Returned suggest results

.SearchPage<T>
Defines a Spring Data `Page` that contains a `SearchHits<T>` element and can be used for paging access using repository methods.

.SearchScrollHits<T>
Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, it enriches a `SearchHits<T>` with the Elasticsearch scroll id.

.SearchHitsIterator<T>
An Iterator returned by the streaming functions of the `SearchOperations` interface.

.ReactiveSearchHits
`ReactiveSearchOperations` has methods returning a `Mono<ReactiveSearchHits<T>>`, this contains the same information as a `SearchHits<T>` object, but will provide the contained `SearchHit<T>` objects as a `Flux<SearchHit<T>>` and not as a list.

[[elasticsearch.operations.queries]]
== Queries

Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchOperations` interface take a `Query` parameter that defines the query to execute for searching. `Query` is an interface and Spring Data Elasticsearch provides three implementations: `CriteriaQuery`, `StringQuery` and `NativeQuery`.

[[elasticsearch.operations.criteriaquery]]
=== CriteriaQuery

`CriteriaQuery` based queries allow the creation of queries to search for data without knowing the syntax or basics of Elasticsearch queries.
They allow the user to build queries by simply chaining and combining `Criteria` objects that specify the criteria the searched documents must fulfill.

NOTE: when talking about AND or OR when combining criteria keep in mind, that in Elasticsearch AND are converted to a **must** condition and OR to a **should**

`Criteria` and their usage are best explained by example (let's assume we have a `Book` entity with a `price` property):

.Get books with a given price
====
[source,java]
----
Criteria criteria = new Criteria("price").is(42.0);
Query query = new CriteriaQuery(criteria);
----
====

Conditions for the same field can be chained, they will be combined with a logical AND:

.Get books with a given price
====
[source,java]
----
Criteria criteria = new Criteria("price").greaterThan(42.0).lessThan(34.0);
Query query = new CriteriaQuery(criteria);
----
====

When chaining `Criteria`, by default a AND logic is used:

.Get all persons with first name _James_ and last name _Miller_:
====
[source,java]
----
Criteria criteria = new Criteria("lastname").is("Miller") <1>
  .and("firstname").is("James")                           <2>
Query query = new CriteriaQuery(criteria);
----

<1> the first `Criteria`
<2> the and() creates a new `Criteria` and chaines it to the first one.
====

If you want to create nested queries, you need to use subqueries for this.
Let's assume we want to find all persons with a last name of _Miller_ and a first name of either _Jack_ or _John_:

.Nested subqueries
====
[source,java]
----
Criteria miller = new Criteria("lastName").is("Miller")  <.>
  .subCriteria(                                          <.>
    new Criteria().or("firstName").is("John")            <.>
      .or("firstName").is("Jack")                        <.>
  );
Query query = new CriteriaQuery(criteria);
----

<.> create a first `Criteria` for the last name
<.> this is combined with AND to a subCriteria
<.> This sub Criteria is an OR combination for the first name _John_
<.> and the first name Jack
====

Please refer to the API documentation of the `Criteria` class for a complete overview of the different available operations.

[[elasticsearch.operations.stringquery]]
=== StringQuery

This class takes an Elasticsearch query as JSON String.
The following code shows a query that searches for persons having the first name "Jack":

====
[source,java]
----

Query query = new StringQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
SearchHits<Person> searchHits = operations.search(query, Person.class);

----
====

Using `StringQuery` may be appropriate if you already have an Elasticsearch query to use.

[[elasticsearch.operations.nativequery]]
=== NativeQuery

`NativeQuery` is the class to use when you have a complex query, or a query that cannot be expressed by using the `Criteria` API, for example when building queries and using aggregates.
It allows to use all the different `co.elastic.clients.elasticsearch._types.query_dsl.Query` implementations from the Elasticsearch library therefore named "native".

The following code shows how to search for persons with a given `firstName` and for the found documents have a terms aggregation that counts the number of occurrences of the `lastName` for these persons:

====
[source,java]
----
Query query = NativeQuery.builder()
	.withAggregation("lastNames", Aggregation.of(a -> a
		.terms(ta -> ta.field("lastName").size(10))))
	.withQuery(q -> q
		.match(m -> m
			.field("firstName")
			.query(firstName)
		)
	)
	.withPageable(pageable)
	.build();

SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====

[[elasticsearch.operations.searchtemplatequery]]
=== SearchTemplateQuery

This is a special implementation of the `Query` interface to be used in combination with a stored search template.
See xref:elasticsearch/misc.adoc#elasticsearch.misc.searchtemplates[Search Template support] for further information.
