com.datastax.oss.driver.api.mapper.annotations.Query Maven / Gradle / Ivy
/*
* 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.
*/
package com.datastax.oss.driver.api.mapper.annotations;
import com.datastax.dse.driver.api.core.cql.reactive.ReactiveResultSet;
import com.datastax.dse.driver.api.mapper.reactive.MappedReactiveResultSet;
import com.datastax.oss.driver.api.core.CqlIdentifier;
import com.datastax.oss.driver.api.core.MappedAsyncPagingIterable;
import com.datastax.oss.driver.api.core.PagingIterable;
import com.datastax.oss.driver.api.core.cql.AsyncResultSet;
import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.ResultSet;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.session.Session;
import com.datastax.oss.driver.api.core.session.SessionBuilder;
import com.datastax.oss.driver.api.mapper.entity.saving.NullSavingStrategy;
import com.datastax.oss.driver.api.mapper.result.MapperResultProducer;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import java.util.function.UnaryOperator;
/**
* Annotates a {@link Dao} method that executes a user-provided query.
*
* Example:
*
*
* @Dao
* public interface SensorReadingDao {
* @Query("SELECT count(*) FROM sensor_readings WHERE id = :id")
* long countById(int id);
* }
*
*
* This is the equivalent of what was called "accessor methods" in the driver 3 mapper.
*
* Parameters
*
* The query string provided in {@link #value()} will typically contain CQL placeholders. The
* method's parameters must match those placeholders: same name and a compatible Java type.
*
*
* @Query("SELECT count(*) FROM sensor_readings WHERE id = :id AND year = :year")
* long countByIdAndYear(int id, int year);
*
*
* A {@link Function Function<BoundStatementBuilder, BoundStatementBuilder>} or {@link
* UnaryOperator UnaryOperator<BoundStatementBuilder>} can be added as the last
* parameter. It will be applied to the statement before execution. This allows you to customize
* certain aspects of the request (page size, timeout, etc) at runtime.
*
*
Return type
*
* The method can return:
*
*
* - {@code void}.
*
- a {@code boolean} or {@link Boolean}, which will be mapped to {@link
* ResultSet#wasApplied()}. This is intended for conditional queries.
*
- a {@code long} or {@link Long}, which will be mapped to the first column of the first row,
* expecting CQL type {@code BIGINT}. This is intended for count queries. The method will fail
* if the result set is empty, or does not match the expected format.
*
- a {@link Row}. This means the result is not converted, the mapper only extracts the first
* row of the result set and returns it. The method will return {@code null} if the result set
* is empty.
*
- a single instance of an {@link Entity} class. The method will extract the first row and
* convert it, or return {@code null} if the result set is empty.
*
- an {@link Optional} of an entity class. The method will extract the first row and convert
* it, or return {@code Optional.empty()} if the result set is empty.
*
- a {@link ResultSet}. The method will return the raw query result, without any conversion.
*
- a {@link BoundStatement}. This is intended for cases where you intend to execute this
* statement later or in a batch:
*
- a {@link PagingIterable}. The method will convert each row into an entity instance.
*
- a {@link CompletionStage} or {@link CompletableFuture} of any of the above. The method will
* execute the query asynchronously. Note that for result sets and iterables, you need to
* switch to the asynchronous equivalent {@link AsyncResultSet} and {@link
* MappedAsyncPagingIterable} respectively.
*
- a {@link ReactiveResultSet}, or a {@link MappedReactiveResultSet} of the entity class.
*
- a {@linkplain MapperResultProducer custom type}.
*
*
* Target keyspace and table
*
* To avoid hard-coding the keyspace and table name, the query string supports 3 additional
* placeholders: {@code ${keyspaceId}}, {@code ${tableId}} and {@code ${qualifiedTableId}}. They get
* substituted at DAO initialization time, with the keyspace and table that the DAO was built with
* (see {@link DaoFactory}).
*
* For example, given the following:
*
*
* @Dao
* public interface TestDao {
* @Query("SELECT * FROM ${keyspaceId}.${tableId}")
* ResultSet queryFromKeyspaceAndTable();
*
* @Query("SELECT * FROM ${qualifiedTableId}")
* ResultSet queryFromQualifiedTable();
* }
*
* @Mapper
* public interface TestMapper {
* @DaoFactory
* TestDao dao(@DaoKeyspace String keyspace, @DaoTable String table);
*
* @DaoFactory
* TestDao dao(@DaoTable String table);
* }
*
* TestDao dao1 = mapper.dao("ks", "t");
* TestDao dao2 = mapper.dao("t");
*
*
* Then:
*
*
* - {@code dao1.queryFromKeyspaceAndTable()} and {@code dao1.queryFromQualifiedTable()} both
* execute {@code SELECT * FROM ks.t}.
*
- {@code dao2.queryFromKeyspaceAndTable()} fails: no keyspace was specified for this DAO, so
* {@code ${keyspaceId}} can't be substituted.
*
- {@code dao1.queryFromQualifiedTable()} executes {@code SELECT * FROM t}. In other words,
* {@code ${qualifiedTableId}} uses the keyspace if it is available, but resolves to the table
* name only if it isn't. Whether the query succeeds or not depends on whether the {@link
* Session} that the mapper was built with has a {@linkplain
* SessionBuilder#withKeyspace(CqlIdentifier) default keyspace}.
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {
/**
* The query string to execute.
*
* It can contain CQL placeholders (e.g. {@code :id}) that will be bound with the method's
* parameters; and also special text placeholders {@code ${keyspaceId}}, {@code ${tableId}} and
* {@code ${qualifiedTableId}} that will be substituted with the keyspace and table that the DAO
* was built with. See the top-level javadocs of this class for more explanations.
*/
String value();
/**
* How to handle null query parameters.
*
*
This defaults either to the {@link DefaultNullSavingStrategy DAO-level strategy} (if set),
* or {@link NullSavingStrategy#DO_NOT_SET}.
*/
NullSavingStrategy nullSavingStrategy() default NullSavingStrategy.DO_NOT_SET;
}