package com.github.davidmoten.rx.jdbc; import static com.github.davidmoten.rx.jdbc.Conditions.checkNotNull; import static com.github.davidmoten.rx.jdbc.Queries.bufferedParameters; import java.sql.ResultSet; import java.util.List; import com.github.davidmoten.rx.Functions; import com.github.davidmoten.rx.jdbc.NamedParameters.JdbcQuery; import com.github.davidmoten.rx.jdbc.tuple.Tuple2; import com.github.davidmoten.rx.jdbc.tuple.Tuple3; import com.github.davidmoten.rx.jdbc.tuple.Tuple4; import com.github.davidmoten.rx.jdbc.tuple.Tuple5; import com.github.davidmoten.rx.jdbc.tuple.Tuple6; import com.github.davidmoten.rx.jdbc.tuple.Tuple7; import com.github.davidmoten.rx.jdbc.tuple.TupleN; import com.github.davidmoten.rx.jdbc.tuple.Tuples; import rx.Observable; import rx.Observable.Transformer; import rx.functions.Func1; /** * A query and its executable context. */ final public class QuerySelect implements Query { // Note has one ? to match the expected one parameter static final String RETURN_GENERATED_KEYS = "RETURN_GENERATED_KEYS?"; private final Observable<Parameter> parameters; private final QueryContext context; private Observable<?> depends = Observable.empty(); private final JdbcQuery jdbcQuery; private final Func1<ResultSet, ? extends ResultSet> resultSetTransform; /** * Constructor. * * @param sql * jdbc select statement or the word RETURN_GENERATED_KEYS * @param parameters * if sql == RETURN_GENERATED_KEYS then the first parameter will * be the ResultSet to be used as source * @param depends * @param context * @param resultSetTransform * @param resultSetTransform */ QuerySelect(String sql, Observable<Parameter> parameters, Observable<?> depends, QueryContext context, Func1<ResultSet, ? extends ResultSet> resultSetTransform) { checkNotNull(sql); checkNotNull(parameters); checkNotNull(depends); checkNotNull(context); checkNotNull(resultSetTransform); this.jdbcQuery = NamedParameters.parse(sql); this.parameters = parameters; this.depends = depends; this.context = context; this.resultSetTransform = resultSetTransform; } @Override public String sql() { return jdbcQuery.sql(); } @Override public QueryContext context() { return context; } @Override public Observable<Parameter> parameters() { return parameters; } @Override public List<String> names() { return jdbcQuery.names(); } @Override public String toString() { return "QuerySelect [sql=" + sql() + "]"; } @Override public Observable<?> depends() { return depends; } Func1<ResultSet, ? extends ResultSet> resultSetTransform() { return resultSetTransform; } /** * Returns the results of running a select query with all sets of * parameters. * * @return */ public <T> Observable<T> execute(ResultSetMapper<? extends T> function) { return bufferedParameters(this) // execute once per set of parameters .concatMap(executeOnce(function)); } /** * Returns a {@link Func1} that itself returns the results of pushing one * set of parameters through a select query. * * @param query * @return */ private <T> Func1<List<Parameter>, Observable<T>> executeOnce( final ResultSetMapper<? extends T> function) { return new Func1<List<Parameter>, Observable<T>>() { @Override public Observable<T> call(List<Parameter> params) { return executeOnce(params, function); } }; } /** * Returns an Observable of the results of pushing one set of parameters * through a select query. * * @param params * one set of parameters to be run with the query * @return */ private <T> Observable<T> executeOnce(final List<Parameter> params, ResultSetMapper<? extends T> function) { return QuerySelectOnSubscribe.execute(this, params, function) .subscribeOn(context.scheduler()); } /** * Builds a {@link QuerySelect}. */ public static final class Builder { private static final int DEFAULT_FETCH_SIZE = 0; /** * Builds the standard stuff. */ private final QueryBuilder builder; private int fetchSize = DEFAULT_FETCH_SIZE; /** * The {@link ResultSet} is transformed before use. */ private Func1<ResultSet, ? extends ResultSet> resultSetTransform = Functions.identity(); /** * Constructor. * * @param sql * @param db */ public Builder(String sql, Database db) { builder = new QueryBuilder(sql, db); } /** * Appends the given parameters to the parameter list for the query. If * there are more parameters than required for one execution of the * query then more than one execution of the query will occur. * * @param parameters * @return this */ public <T> Builder parameters(Observable<T> parameters) { builder.parameters(parameters); return this; } /** * Appends the given parameter values to the parameter list for the * query. If there are more parameters than required for one execution * of the query then more than one execution of the query will occur. * * @param objects * @return this */ public Builder parameters(Object... objects) { builder.parameters(objects); return this; } /** * Appends a parameter to the parameter list for the query. If there are * more parameters than required for one execution of the query then * more than one execution of the query will occur. * * @param value * @return this */ public Builder parameter(Object value) { builder.parameter(value); return this; } /** * Sets a named parameter. If name is null throws a * {@link NullPointerException}. If value is instance of Observable then * throws an {@link IllegalArgumentException}. * * @param name * the parameter name. Cannot be null. * @param value * the parameter value */ public Builder parameter(String name, Object value) { builder.parameter(name, value); return this; } /** * Sets the {@code FETCH_SIZE} to be used by the query. * * @param fetchSize The fetch size to be used. A non-positive value will be ignored. */ public Builder fetchSize(int fetchSize) { this.fetchSize = fetchSize; return this; } /** * Appends a dependency to the dependencies that have to complete their * emitting before the query is executed. * * @param dependency * @return this */ public Builder dependsOn(Observable<?> dependency) { builder.dependsOn(dependency); return this; } /** * Appends a dependency on the result of the last transaction ( * <code>true</code> for commit or <code>false</code> for rollback) to * the dependencies that have to complete their emitting before the * query is executed. * * @return this */ public Builder dependsOnLastTransaction() { builder.dependsOnLastTransaction(); return this; } /** * The ResultSet is transformed by the given transform before the * results are traversed. * * @param transform * transforms the ResultSet * @return this */ public Builder resultSetTransform(Func1<ResultSet, ? extends ResultSet> transform) { this.resultSetTransform = transform; return this; } /** * Transforms the results using the given function. * * @param function * @return the results of the query as an Observable */ public <T> Observable<T> get(ResultSetMapper<? extends T> function) { return get(function, builder, resultSetTransform); } <T> Observable<T> get(ResultSetMapper<? extends T> function, QueryBuilder builder, Func1<ResultSet, ? extends ResultSet> resultSetTransform) { final QueryContext ctxt; if (fetchSize > 1) { ctxt = builder.context().fetchSize(fetchSize); } else { ctxt = builder.context(); } return new QuerySelect(builder.sql(), builder.parameters(), builder.depends(), ctxt, resultSetTransform).execute(function); } /** * <p> * Transforms each row of the {@link ResultSet} into an instance of * <code>T</code> using <i>automapping</i> of the ResultSet columns into * corresponding constructor parameters that are assignable. Beyond * normal assignable criteria (for example Integer 123 is assignable to * a Double) other conversions exist to facilitate the automapping: * </p> * <p> * They are: * <ul> * <li>java.sql.Blob ➟ byte[]</li> * <li>java.sql.Blob ➟ java.io.InputStream</li> * <li>java.sql.Clob ➟ String</li>s * <li>java.sql.Clob ➟ java.io.Reader</li> * <li>java.sql.Date ➟ java.util.Date</li> * <li>java.sql.Date ➟ Long</li> * <li>java.sql.Timestamp ➟ java.util.Date</li> * <li>java.sql.Timestamp ➟ Long</li> * <li>java.sql.Time ➟ java.util.Date</li> * <li>java.sql.Time ➟ Long</li> * <li>java.math.BigInteger ➟ * Short,Integer,Long,Float,Double,BigDecimal</li> * <li>java.math.BigDecimal ➟ * Short,Integer,Long,Float,Double,BigInteger</li> * </p> * * @param cls * @return */ public <T> Observable<T> autoMap(Class<T> cls) { return autoMap(cls, builder, resultSetTransform); } <T> Observable<T> autoMap(Class<T> cls, QueryBuilder builder, Func1<ResultSet, ? extends ResultSet> resultSetTransform) { Util.setSqlFromQueryAnnotation(cls, builder); return get(Util.autoMap(cls), builder, resultSetTransform); } /** * Automaps the first column of the ResultSet into the target class * <code>cls</code>. * * @param cls * @return */ public <S> Observable<S> getAs(Class<S> cls) { return get(Tuples.single(cls)); } /** * Automaps all the columns of the {@link ResultSet} into the target * class <code>cls</code>. See {@link #autoMap(Class) autoMap()}. * * @param cls * @return */ public <S> Observable<TupleN<S>> getTupleN(Class<S> cls) { return get(Tuples.tupleN(cls)); } /** * Automaps all the columns of the {@link ResultSet} into {@link Object} * . See {@link #autoMap(Class) autoMap()}. * * @param cls * @return */ public <S> Observable<TupleN<Object>> getTupleN() { return get(Tuples.tupleN(Object.class)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @return */ public <T1, T2> Observable<Tuple2<T1, T2>> getAs(Class<T1> cls1, Class<T2> cls2) { return get(Tuples.tuple(cls1, cls2)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @return */ public <T1, T2, T3> Observable<Tuple3<T1, T2, T3>> getAs(Class<T1> cls1, Class<T2> cls2, Class<T3> cls3) { return get(Tuples.tuple(cls1, cls2, cls3)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @param cls4 * @return */ public <T1, T2, T3, T4> Observable<Tuple4<T1, T2, T3, T4>> getAs(Class<T1> cls1, Class<T2> cls2, Class<T3> cls3, Class<T4> cls4) { return get(Tuples.tuple(cls1, cls2, cls3, cls4)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @param cls4 * @param cls5 * @return */ public <T1, T2, T3, T4, T5> Observable<Tuple5<T1, T2, T3, T4, T5>> getAs(Class<T1> cls1, Class<T2> cls2, Class<T3> cls3, Class<T4> cls4, Class<T5> cls5) { return get(Tuples.tuple(cls1, cls2, cls3, cls4, cls5)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @param cls4 * @param cls5 * @param cls6 * @return */ public <T1, T2, T3, T4, T5, T6> Observable<Tuple6<T1, T2, T3, T4, T5, T6>> getAs( Class<T1> cls1, Class<T2> cls2, Class<T3> cls3, Class<T4> cls4, Class<T5> cls5, Class<T6> cls6) { return get(Tuples.tuple(cls1, cls2, cls3, cls4, cls5, cls6)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @param cls4 * @param cls5 * @param cls6 * @param cls7 * @return */ public <T1, T2, T3, T4, T5, T6, T7> Observable<Tuple7<T1, T2, T3, T4, T5, T6, T7>> getAs( Class<T1> cls1, Class<T2> cls2, Class<T3> cls3, Class<T4> cls4, Class<T5> cls5, Class<T6> cls6, Class<T7> cls7) { return get(Tuples.tuple(cls1, cls2, cls3, cls4, cls5, cls6, cls7)); } public Observable<Integer> count() { return get(Util.toOne()).count(); } /** * Returns an {@link Transformer} to allow the query to be pushed * parameters via the {@link Observable#compose(Transformer)} method. * * @return Transformer that acts on parameters */ public TransformerBuilder<Object> parameterTransformer() { return new TransformerBuilder<Object>(this, OperatorType.PARAMETER); } /** * Returns an {@link Transformer} to allow the query to be pushed * dependencies via the {@link Observable#compose(Transformer)} method. * * @return Transformer that acts on dependencies */ public TransformerBuilder<Object> dependsOnTransformer() { return new TransformerBuilder<Object>(this, OperatorType.DEPENDENCY); } /** * Returns an {@link Transformer} that runs a select query for each list * of parameter objects in the source observable. * * @return */ public TransformerBuilder<Observable<Object>> parameterListTransformer() { return new TransformerBuilder<Observable<Object>>(this, OperatorType.PARAMETER_LIST); } } /** * Builder pattern for select query {@link Transformer}. */ public static class TransformerBuilder<R> { private final Builder builder; private final OperatorType operatorType; /** * Constructor. * * @param builder * @param operatorType */ public TransformerBuilder(Builder builder, OperatorType operatorType) { this.builder = builder; this.operatorType = operatorType; } /** * Transforms the results using the given function. * * @param function * @return */ public <T> Transformer<R, T> get(ResultSetMapper<? extends T> function) { return new QuerySelectTransformer<T, R>(builder, function, operatorType); } /** * See {@link Builder#autoMap(Class)}. * * @param cls * @return */ public <S> Transformer<R, S> autoMap(Class<S> cls) { return get(Util.autoMap(cls)); } /** * Automaps the first column of the ResultSet into the target class * <code>cls</code>. * * @param cls * @return */ public <S> Transformer<R, S> getAs(Class<S> cls) { return get(Tuples.single(cls)); } /** * Automaps all the columns of the {@link ResultSet} into the target * class <code>cls</code>. See {@link #autoMap(Class) autoMap()}. * * @param cls * @return */ public <S> Transformer<R, TupleN<S>> getTupleN(Class<S> cls) { return get(Tuples.tupleN(cls)); } /** * Automaps all the columns of the {@link ResultSet} into {@link Object} * . See {@link #autoMap(Class) autoMap()}. * * @param cls * @return */ public <S> Transformer<R, TupleN<Object>> getTupleN() { return get(Tuples.tupleN(Object.class)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @return */ public <T1, T2> Transformer<R, Tuple2<T1, T2>> getAs(Class<T1> cls1, Class<T2> cls2) { return get(Tuples.tuple(cls1, cls2)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @return */ public <T1, T2, T3> Transformer<R, Tuple3<T1, T2, T3>> getAs(Class<T1> cls1, Class<T2> cls2, Class<T3> cls3) { return get(Tuples.tuple(cls1, cls2, cls3)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @param cls4 * @return */ public <T1, T2, T3, T4> Transformer<R, Tuple4<T1, T2, T3, T4>> getAs(Class<T1> cls1, Class<T2> cls2, Class<T3> cls3, Class<T4> cls4) { return get(Tuples.tuple(cls1, cls2, cls3, cls4)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @param cls4 * @param cls5 * @return */ public <T1, T2, T3, T4, T5> Transformer<R, Tuple5<T1, T2, T3, T4, T5>> getAs(Class<T1> cls1, Class<T2> cls2, Class<T3> cls3, Class<T4> cls4, Class<T5> cls5) { return get(Tuples.tuple(cls1, cls2, cls3, cls4, cls5)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @param cls4 * @param cls5 * @param cls6 * @return */ public <T1, T2, T3, T4, T5, T6> Transformer<R, Tuple6<T1, T2, T3, T4, T5, T6>> getAs( Class<T1> cls1, Class<T2> cls2, Class<T3> cls3, Class<T4> cls4, Class<T5> cls5, Class<T6> cls6) { return get(Tuples.tuple(cls1, cls2, cls3, cls4, cls5, cls6)); } /** * Automaps the columns of the {@link ResultSet} into the specified * classes. See {@link #autoMap(Class) autoMap()}. * * @param cls1 * @param cls2 * @param cls3 * @param cls4 * @param cls5 * @param cls6 * @param cls7 * @return */ public <T1, T2, T3, T4, T5, T6, T7> Transformer<R, Tuple7<T1, T2, T3, T4, T5, T6, T7>> getAs( Class<T1> cls1, Class<T2> cls2, Class<T3> cls3, Class<T4> cls4, Class<T5> cls5, Class<T6> cls6, Class<T7> cls7) { return get(Tuples.tuple(cls1, cls2, cls3, cls4, cls5, cls6, cls7)); } } }