<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
-->

## Query provider methods

Annotate a DAO method with [@QueryProvider] to delegate the execution of the query to one of your
own classes:

```java
@Dao
public interface SensorDao {
  @QueryProvider(providerClass = FindSliceProvider.class, entityHelpers = SensorReading.class)
  PagingIterable<SensorReading> findSlice(int id, Integer month, Integer day);
}

/* Schema:
   CREATE TABLE sensor_reading(sensor_id int, month int, day int, value double,
       PRIMARY KEY (id, month, day)
       WITH CLUSTERING ORDER BY (month DESC, day DESC);
 */
```

Use this for requests that can't be expressed as static query strings. For example, we want the
`month` and `day` parameters above to be optional:

* if both are present, we query for a particular day: `WHERE id = ? AND month = ? AND day = ?`
* if `day` is null, we query for the whole month: `WHERE id = ? AND month = ?`
* if `month` is also null, we query the whole partition: `WHERE id = ?`

We assume that you've already written a corresponding [entity](../../entities/README.md) class:

```java
@Entity
public class SensorReading {
  @PartitionKey private int id;
  @ClusteringColumn(1) private int month;
  @ClusteringColumn(2) private int day;
  private double value;
  // constructors, getters and setters omitted for conciseness
}
```

### Provider class

[@QueryProvider.providerClass()][providerClass] indicates which class to delegate to. The mapper
will create one instance for each DAO instance.

This class must expose a constructor that is accessible from the DAO interface's package.

The first constructor argument must always be [MapperContext]. This is a utility type that
provides access to mapper- and DAO-level state. In particular, this is how you get hold of the
session.

If [@QueryProvider.entityHelpers()][entityHelpers] is specified, the constructor must take an
additional [EntityHelper] argument for each provided entity class. We specified
`SensorReading.class` so our argument types are `(MapperContext, EntityHelper<SensorReading>)`.

An entity helper is a utility type generated by the mapper. One thing it can do is construct query
templates (with the [query builder](../../../query_builder/README.md)). We want to retrieve entities so we
use `selectStart()`, chain a first WHERE clause for the id (which is always present), and store the
result in a field for later use:

```java
public class FindSliceProvider {
  private final CqlSession session;
  private final EntityHelper<SensorReading> sensorReadingHelper;
  private final Select selectStart;

  public FindSliceProvider(
      MapperContext context, EntityHelper<SensorReading> sensorReadingHelper) {
    this.session = context.getSession();
    this.sensorReadingHelper = sensorReadingHelper;
    this.selectStart =
        sensorReadingHelper.selectStart().whereColumn("id").isEqualTo(bindMarker());
  }
  
  ... // (to be continued)
```
  
### Provider method

[@QueryProvider.providerMethod()][providerMethod] indicates which method to invoke on the provider
class. When it's not specified (as is our case), it defaults to the same name as the DAO method.

The provider method must be accessible from the DAO interface's package, and have the same
parameters and return type as the DAO method.

Here is the full implementation:

```java
  ... // public class FindSliceProvider (continued)
  
  public PagingIterable<SensorReading> findSlice(int id, Integer month, Integer day) {

    // (1) complete the query
    Select select = this.selectStart;
    if (month != null) {
      select = select.whereColumn("month").isEqualTo(bindMarker());
      if (day != null) {
        select = select.whereColumn("day").isEqualTo(bindMarker());
      }
    }
    
    // (2) prepare
    PreparedStatement preparedStatement = session.prepare(select.build());
    
    // (3) bind
    BoundStatementBuilder boundStatementBuilder =
        preparedStatement.boundStatementBuilder().setInt("id", id);
    if (month != null) {
      boundStatementBuilder = boundStatementBuilder.setInt("month", month);
      if (day != null) {
        boundStatementBuilder = boundStatementBuilder.setInt("day", day);
      }
    }
    
    // (4) execute and map the results
    return session.execute(boundStatementBuilder.build()).map(sensorReadingHelper::get);
  }
}
```

1. Retrieve the SELECT query that was started in the constructor, and append additional WHERE
   clauses as appropriate.
   
    Note that all query builder objects are immutable, so this creates a new instance every time,
    there is no risk of corrupting the original field.
    
2. Prepare the resulting statement.

    `session.prepare` caches its results, so if we already prepared that particular combination,
    there is no network call at this step.
   
3. Bind the parameters, according to the WHERE clauses we've generated.

4. Execute the request.

    Another useful helper feature is mapping entities to/from low-level driver data structures:
    `get` extracts a `SensorReading` from a `Row`, so by mapping it to the [ResultSet] we get back
     the desired [PagingIterable<SensorReading>][PagingIterable].


[@QueryProvider]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/annotations/QueryProvider.html
[providerClass]:  https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/annotations/QueryProvider.html#providerClass--
[entityHelpers]:  https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/annotations/QueryProvider.html#entityHelpers--
[providerMethod]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/annotations/QueryProvider.html#providerMethod--
[MapperContext]:  https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/MapperContext.html
[EntityHelper]:   https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/mapper/EntityHelper.html
[ResultSet]:      https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/cql/ResultSet.html
[PagingIterable]: https://docs.datastax.com/en/drivers/java/4.17/com/datastax/oss/driver/api/core/PagingIterable.html
