/*
* Licensed 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 org.jdbi.v3.core.result;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collector;
import org.jdbi.v3.core.collector.ElementTypeNotFoundException;
import org.jdbi.v3.core.collector.NoSuchCollectorException;
import org.jdbi.v3.core.config.Configurable;
import org.jdbi.v3.core.generic.GenericType;
import org.jdbi.v3.core.mapper.ColumnMapper;
import org.jdbi.v3.core.mapper.ColumnMapperFactory;
import org.jdbi.v3.core.mapper.MapMapper;
import org.jdbi.v3.core.mapper.RowMapper;
import org.jdbi.v3.core.mapper.RowMapperFactory;
import org.jdbi.v3.core.mapper.SingleColumnMapper;
import org.jdbi.v3.core.mapper.reflect.BeanMapper;
import org.jdbi.v3.core.statement.StatementContext;
/**
* Provides access to the contents of a {@link ResultSet}
*/
public interface ResultBearing {
/**
* Returns a ResultBearing backed by the given result set supplier and context.
*
* @param resultSetSupplier result set supplier
* @param ctx the statement context
* @return a ResultBearing
*/
static ResultBearing of(Supplier<ResultSet> resultSetSupplier, StatementContext ctx) {
return new ResultBearing() {
@Override
public <R> R mapResultSet(ResultSetMapper<R> mapper) {
try {
return mapper.mapResultSet(resultSetSupplier, ctx);
}
catch (SQLException e) {
throw new ResultSetException("Error reading result set", e, ctx);
}
}
};
}
/**
* Invokes the mapper with a result set supplier, and returns the value returned by the mapper.
* @param mapper result set mapper
* @param <R> result type returned by the mapper.
* @return the value returned by the mapper.
*/
<R> R mapResultSet(ResultSetMapper<R> mapper);
/**
* Maps this result set to a {@link ResultIterable} of the given element type.
*
* @param type the type to map the result set rows to
* @param <T> the type to map the result set rows to
* @return a {@link ResultIterable} of the given type.
* @see Configurable#registerRowMapper(RowMapper)
* @see Configurable#registerRowMapper(RowMapperFactory)
* @see Configurable#registerColumnMapper(ColumnMapperFactory)
* @see Configurable#registerColumnMapper(ColumnMapper)
*/
@SuppressWarnings("unchecked")
default <T> ResultIterable<T> mapTo(Class<T> type) {
return (ResultIterable<T>) mapTo((Type) type);
}
/**
* Maps this result set to a {@link ResultIterable} of the given element type.
*
* @param type the type to map the result set rows to
* @param <T> the type to map the result set rows to
* @return a {@link ResultIterable} of the given type.
* @see Configurable#registerRowMapper(RowMapper)
* @see Configurable#registerRowMapper(RowMapperFactory)
* @see Configurable#registerColumnMapper(ColumnMapperFactory)
* @see Configurable#registerColumnMapper(ColumnMapper)
*/
@SuppressWarnings("unchecked")
default <T> ResultIterable<T> mapTo(GenericType<T> type) {
return (ResultIterable<T>) mapTo(type.getType());
}
/**
* Maps this result set to a {@link ResultIterable} of the given element type.
*
* @param type the type to map the result set rows to
* @return a {@link ResultIterable} of the given type.
* @see Configurable#registerRowMapper(RowMapper)
* @see Configurable#registerRowMapper(RowMapperFactory)
* @see Configurable#registerColumnMapper(ColumnMapperFactory)
* @see Configurable#registerColumnMapper(ColumnMapper)
*/
default ResultIterable<?> mapTo(Type type) {
return mapResultSet((supplier, ctx) -> {
RowMapper<?> mapper = ctx.findRowMapperFor(type)
.orElseThrow(() -> new UnsupportedOperationException("No mapper registered for type " + type));
return ResultIterable.of(supplier, mapper, ctx);
});
}
/**
* Maps this result set to a {@link ResultIterable} of the given element type, using {@link BeanMapper}.
*
* @param type the bean type to map the result set rows to
* @param <T> the bean type to map the result set rows to
* @return a {@link ResultIterable} of the given type.
*/
default <T> ResultIterable<T> mapToBean(Class<T> type) {
return map(BeanMapper.of(type));
}
/**
* Maps this result set to a {@link ResultIterable} of {@code Map<String,Object>}. Keys are column names, and
* values are column values.
*
* @return a {@link ResultIterable ResultIterable<Map<String,Object>>}.
*/
default ResultIterable<Map<String, Object>> mapToMap() {
return map(new MapMapper());
}
/**
* Maps this result set to a {@link ResultIterable}, using the given column mapper.
*
* @param mapper column mapper used to map the first column of each row
* @param <T> the type to map the result set rows to
* @return a {@link ResultIterable} of type {@code <T>}.
*/
default <T> ResultIterable<T> map(ColumnMapper<T> mapper) {
return map(new SingleColumnMapper<>(mapper));
}
/**
* Maps this result set to a {@link ResultIterable}, using the given row mapper.
*
* @param mapper mapper used to map each row
* @param <T> the type to map the result set rows to
* @return a {@link ResultIterable} of type {@code <T>}.
*/
default <T> ResultIterable<T> map(RowMapper<T> mapper) {
return mapResultSet((supplier, ctx) -> ResultIterable.of(supplier, mapper, ctx));
}
/**
* Reduce the results. Using a {@code BiFunction<U, RowView, U>}, repeatedly
* combine query results until only a single value remains.
*
* @param seed the {@code U} to combine with the first result
* @param accumulator the function to apply repeatedly
* @return the final {@code U}
*/
default <U> U reduceRows(U seed, BiFunction<U, RowView, U> accumulator) {
return mapResultSet((supplier, ctx) -> {
try (ResultSet rs = supplier.get()) {
RowView rv = new RowView(rs, ctx);
U result = seed;
while (rs.next()) {
result = accumulator.apply(result, rv);
}
return result;
}
catch (SQLException e) {
throw new UnableToProduceResultException(e, ctx);
}
finally {
ctx.close();
}
});
}
/**
* Reduce the results. Using a {@code ResultSetAccumulator}, repeatedly
* combine query results until only a single value remains.
*
* @param seed the {@code U} to combine with the first result
* @param accumulator the function to apply repeatedly
* @return the final {@code U}
*/
default <U> U reduceResultSet(U seed, ResultSetAccumulator<U> accumulator) {
return mapResultSet((supplier, ctx) -> {
try (ResultSet rs = supplier.get()) {
U result = seed;
while (rs.next()) {
result = accumulator.apply(result, rs, ctx);
}
return result;
}
catch (SQLException e) {
throw new UnableToProduceResultException(e, ctx);
}
finally {
ctx.close();
}
});
}
/**
* Collect the results into a container of the given type. A collector
* must be registered for the container type, which knows the element type
* for the container. A mapper must be registered for the element type.
* <p>
* This method is equivalent to {@code ResultBearing.mapTo(elementType).collect(containerCollector)}.
* </p>
* @param containerType the container type into which results will be collected
* @param <R> the result container type
* @return a container into which result rows have been collected
*/
@SuppressWarnings("unchecked")
default <R> R collectInto(Class<R> containerType) {
return (R) collectInto((Type) containerType);
}
/**
* Collect the results into a container of the given generic type. A collector
* must be registered for the container type, which knows the element type
* for the container. A mapper must be registered for the element type.
* <p>
* This method is equivalent to {@code ResultBearing.mapTo(elementType).collect(containerCollector)}.
* </p>
* <p>
* Example:
* </p>
* <pre>
* Map<Long, User> usersById = handle.createQuery("select * from user")
* .configure(MapEntryMapper.Config.class, cfg -> cfg.setKeyColumn("id"))
* .collectInto(new GenericType<Map<Long, User>>() {});
* </pre>
*
* @param containerType the container type into which results will be collected
* @param <R> the result container type
* @return a container into which result rows have been collected
*/
@SuppressWarnings("unchecked")
default <R> R collectInto(GenericType<R> containerType) {
return (R) collectInto(containerType.getType());
}
/**
* Collect the results into a container of the given type. A collector
* must be registered for the container type, which knows the element type
* for the container. A mapper must be registered for the element type.
* <p>
* This method is equivalent to {@code ResultBearing.mapTo(elementType).collect(containerCollector)}.
* </p>
*
* @param containerType the container type into which results will be collected
* @return a container into which result rows have been collected
*/
@SuppressWarnings("unchecked")
default Object collectInto(Type containerType) {
return mapResultSet((rs, ctx) -> {
Collector collector = ctx.findCollectorFor(containerType)
.orElseThrow(() -> new NoSuchCollectorException("No collector registered for container type " + containerType));
Type elementType = ctx.findElementTypeFor(containerType)
.orElseThrow(() -> new ElementTypeNotFoundException("Unknown element type for container type " + containerType));
RowMapper<?> mapper = ctx.findRowMapperFor(elementType)
.orElseThrow(() -> new UnsupportedOperationException("No mapper registered for element type " + elementType));
return ResultIterable.of(rs, mapper, ctx).collect(collector);
});
}
}