001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.beanquery.sql;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021
022import java.sql.*;
023import java.time.*;
024import java.util.*;
025
026import org.apache.juneau.commons.bean.*;
027import org.apache.juneau.commons.beanquery.*;
028
029/**
030 * Turns the current {@link ResultSet} row into a bean, for {@link SqlBeanQuerySession#find(BeanQuery)} and
031 * {@link SqlBeanQuerySession#stream(BeanQuery)}.
032 *
033 * <p>
034 * The result set is positioned on the row; do not call {@link ResultSet#next()}.  {@code columns} lists the selected
035 * (logical) column names in {@code SELECT} order (the query's view, or every column), so column {@code i} of the
036 * result set is {@code columns.get(i - 1)} and a mapper can skip columns the query did not select.
037 *
038 * <p>
039 * Two built-in mappers need no dependency beyond this module:
040 * <ul>
041 *    <li>{@link #map()} &mdash; each row as a {@link LinkedHashMap} keyed by column name in view order.
042 *    <li>{@link #bean(Class)} &mdash; each row as a {@code record} (canonical constructor) or as a class with a public
043 *       no-arg constructor and public setters ({@code setX}/{@code withX}, void or fluent) or public non-final fields.
044 *       Columns are matched to Juneau bean property names by exact name; columns with no property are ignored, and
045 *       properties with no column keep their defaults.
046 *    <li>{@link #bean(BeanMeta)} &mdash; the same, but using existing bean metadata, so custom property namers apply and
047 *       column names match the property names a serializer built from that metadata emits.
048 * </ul>
049 *
050 * <p>
051 * When no mapper is set on the {@link SqlBeanQueryContext.Builder}, one is chosen from the row type: a {@code Map}
052 * type gets {@link #map()}, and any other type that {@link #bean(Class)} can plan gets {@link #bean(Class)}.
053 * {@code Object}, {@code Map} types a {@link LinkedHashMap} is not assignable to (such as {@code TreeMap}) and types
054 * that cannot be planned get none, and {@code find}/{@code stream} then throw {@link IllegalStateException}.
055 *
056 * <h5 class='section'>Example:</h5>
057 * <p class='bjava'>
058 *    <jc>// Custom mapper.</jc>
059 *    SqlRowMapper&lt;Person&gt; <jv>mapper</jv> = (<jv>rs</jv>, <jv>columns</jv>) -&gt; <jk>new</jk> Person(
060 *       <jv>columns</jv>.contains(<js>"name"</js>) ? <jv>rs</jv>.getString(<js>"full_name"</js>) : <jk>null</jk>,
061 *       <jv>columns</jv>.contains(<js>"age"</js>) ? <jv>rs</jv>.getInt(<js>"age"</js>) : 0);
062 *
063 *    <jc>// Rows as maps, or as a record / setter bean.</jc>
064 *    SqlRowMapper&lt;Map&lt;String,Object&gt;&gt; <jv>maps</jv> = SqlRowMapper.<jsm>map</jsm>();
065 *    SqlRowMapper&lt;Person&gt; <jv>beans</jv> = SqlRowMapper.<jsm>bean</jsm>(Person.<jk>class</jk>);
066 *
067 *    <jc>// Default selection: no rowMapper(...) call, so Person (a record) gets bean(Person.class).</jc>
068 *    SqlBeanQueryContext&lt;Person&gt; <jv>context</jv> = SqlBeanQueryContext.<jsm>create</jsm>(Person.<jk>class</jk>)
069 *       <jc>// ...dialect, table, columns, connection source...</jc>
070 *       .build();
071 * </p>
072 *
073 * @param <T> The row (bean) type.
074 * @since 10.0.0
075 */
076@FunctionalInterface
077public interface SqlRowMapper<T> {
078
079   /**
080    * Maps the current result-set row to a bean.
081    *
082    * @param rs The result set, positioned on the row to map.  Must not be advanced.
083    * @param columns The selected column names in {@code SELECT} order.
084    * @return The mapped bean.
085    * @throws SQLException If a column read fails.
086    */
087   T map(ResultSet rs, List<String> columns) throws SQLException;
088
089   /**
090    * A built-in row mapper that maps each row to a new {@link LinkedHashMap}, keyed by the logical column name in
091    * {@code SELECT} order, with each value read via {@link ResultSet#getObject(int)} (the driver's natural type;
092    * <jk>null</jk> for SQL {@code NULL}).
093    *
094    * <h5 class='section'>Example:</h5>
095    * <p class='bjava'>
096    *    SqlRowMapper&lt;Map&lt;String,Object&gt;&gt; <jv>mapper</jv> = SqlRowMapper.<jsm>map</jsm>();
097    * </p>
098    *
099    * @return A shared, stateless row mapper.  Always the same instance.
100    * @since 10.0.0
101    */
102   static SqlRowMapper<Map<String,Object>> map() {
103      return SqlRowMappers.MAP;
104   }
105
106   /**
107    * A built-in row mapper that maps each row to a new instance of {@code type}: a {@code record} (via its
108    * canonical constructor) or a concrete class with a public no-arg constructor and public setters
109    * ({@code setX}/{@code withX} returning {@code void} or the bean type, so fluent setters work) or public non-final
110    * fields.
111    *
112    * <p>
113    * Planning happens here, once; mapping never re-inspects {@code type}.  Columns match Juneau bean property names
114    * exactly (setters, public fields and record components, with {@code @BeanIgnore}, {@code @BeanProp} and the
115    * default property namer applied; see {@link QueryModel}).  A column with no
116    * property is ignored; a record component with no column, or a SQL {@code NULL} for a primitive, gets the type's
117    * default.  Values are read with {@code ResultSet.getObject(i, type)}, except {@link Instant} (via
118    * {@link OffsetDateTime}), {@link java.util.Date} (via {@code getTimestamp}) and enums (by constant name).
119    * A failure reading or setting a column throws a {@link BeanQueryExecutionException} naming the column.  The
120    * returned mapper is thread-safe.
121    *
122    * <h5 class='section'>Example:</h5>
123    * <p class='bjava'>
124    *    <jk>record</jk> Task(String name, <jk>int</jk> age) {}
125    *    SqlRowMapper&lt;Task&gt; <jv>mapper</jv> = SqlRowMapper.<jsm>bean</jsm>(Task.<jk>class</jk>);
126    * </p>
127    *
128    * @param <T> The bean type.
129    * @param type The bean type.  Must not be <jk>null</jk>.
130    * @return A row mapper for {@code type}.
131    * @throws IllegalArgumentException If {@code type} cannot be planned: an interface, abstract class, enum, array or
132    *    primitive type, a class with no public no-arg constructor, or a setter bean with an ambiguous (overloaded) setter.
133    * @since 10.0.0
134    */
135   static <T> SqlRowMapper<T> bean(Class<T> type) {
136      reqnn("type", type);
137      var plan = SqlRowMappers.tryBean(type);
138      if (plan.reason() != null)
139         throw new IllegalArgumentException(format("Cannot map rows to '%s': %s.", type.getSimpleName(), plan.reason()));
140      return plan.mapper();
141   }
142
143   /**
144    * Same as {@link #bean(Class)}, but follows the bean rules of existing bean metadata, for example the metadata of
145    * the serializer that renders the rows, so column names match the property names it emits.
146    *
147    * <h5 class='section'>Example:</h5>
148    * <p class='bjava'>
149    *    BeanConfigContext <jv>config</jv> = BeanConfigContext.<jsm>create</jsm>().propertyNamer(PropertyNamerDLC.<jsf>INSTANCE</jsf>).build();
150    *    SqlRowMapper&lt;Task&gt; <jv>mapper</jv> = SqlRowMapper.<jsm>bean</jsm>(BeanMeta.<jsm>of</jsm>(Task.<jk>class</jk>, <jv>config</jv>));
151    * </p>
152    *
153    * @param <T> The bean type.
154    * @param beanMeta The bean metadata.  Must not be <jk>null</jk>.
155    * @return A row mapper for the bean type.
156    * @throws IllegalArgumentException For the same reasons as {@link #bean(Class)}.
157    * @since 10.0.0
158    */
159   static <T> SqlRowMapper<T> bean(BeanMeta<T> beanMeta) {
160      reqnn("beanMeta", beanMeta);
161      var plan = SqlRowMappers.tryBean(beanMeta);
162      if (plan.reason() != null)
163         throw new IllegalArgumentException(format("Cannot map rows to '%s': %s.", beanMeta.getClassInfo().inner().getSimpleName(), plan.reason()));
164      return plan.mapper();
165   }
166}