| <!-- |
| 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/) 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/)). 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 |