diff --git a/fluent-jdbc/.gitignore b/fluent-jdbc/.gitignore
new file mode 100644
index 0000000..072e996
--- /dev/null
+++ b/fluent-jdbc/.gitignore
@@ -0,0 +1,5 @@
+/target/
+/.settings/
+/.classpath
+/.project
+/*.log
\ No newline at end of file
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/mapper/CallableMappers.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/mapper/CallableMappers.java
new file mode 100644
index 0000000..105a11d
--- /dev/null
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/mapper/CallableMappers.java
@@ -0,0 +1,57 @@
+package org.codejargon.fluentjdbc.api.mapper;
+
+import java.math.BigDecimal;
+
+import org.codejargon.fluentjdbc.api.query.CallableMapper;
+
+/**
+ *
A set of common mappers for convenience.
+ * @see org.codejargon.fluentjdbc.api.mapper.ObjectMappers
+ */
+public abstract class CallableMappers {
+ private static final CallableMapper singleInteger = (cs) -> cs.getInt(1);
+ private static final CallableMapper singleLong = (cs) -> cs.getLong(1);
+ private static final CallableMapper singleString = (cs) -> cs.getString(1);
+ private static final CallableMapper singleBigDecimal = (cs) -> cs.getBigDecimal(1);
+ private static final CallableMapper singleBoolean = (cs) -> cs.getBoolean(1);
+
+ /**
+ * Maps the first Integer column.
+ * @return first Integer column
+ */
+ public static CallableMapper singleInteger() {
+ return singleInteger;
+ }
+
+ /**
+ * Maps the first Long column.
+ * @return first Long column
+ */
+ public static CallableMapper singleLong() {
+ return singleLong;
+ }
+
+ /**
+ * Maps the first string column
+ * @return first string column
+ */
+ public static CallableMapper singleString() {
+ return singleString;
+ }
+
+ /**
+ * Maps the first BigDecimal column
+ * @return first BigDecimal column
+ */
+ public static CallableMapper singleBigDecimal() {
+ return singleBigDecimal;
+ }
+
+ /**
+ * Maps the first Boolean column
+ * @return first Boolean column
+ */
+ public static CallableMapper singleBoolean() {
+ return singleBoolean;
+ }
+}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/CallableMapper.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/CallableMapper.java
new file mode 100644
index 0000000..55a34a5
--- /dev/null
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/CallableMapper.java
@@ -0,0 +1,20 @@
+/*
+* $Id: $
+*
+* Copyright (c) 2021, CGI. All rights reserved.
+* CGI PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+*/
+package org.codejargon.fluentjdbc.api.query;
+
+import java.sql.CallableStatement;
+import java.sql.SQLException;
+
+/**
+ * The {@code CallableResultMapper} class implements ...
+ * TODO write proper class description
+ *
+ * @author wiedermanna
+ */
+public interface CallableMapper {
+ T map(CallableStatement statement) throws SQLException;
+}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/CallableQuery.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/CallableQuery.java
new file mode 100644
index 0000000..2f04547
--- /dev/null
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/CallableQuery.java
@@ -0,0 +1,61 @@
+package org.codejargon.fluentjdbc.api.query;
+
+import java.sql.CallableStatement;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Select query for a SQL statement. A SelectQuery is mutable, non-threadsafe.
+ */
+public interface CallableQuery {
+ /**
+ * Adds positional query parameters.
+ *
+ * @param params additional query parameters
+ * @return this
+ */
+ CallableQuery params(List> params);
+
+ /**
+ * Adds positional query parameters.
+ *
+ * @param params additional query parameters
+ * @return this
+ */
+ CallableQuery params(Object... params);
+
+ /**
+ * Adds named query paramaters.
+ *
+ * @param namedParams additional named query parameters
+ * @return this
+ */
+ CallableQuery namedParams(Map namedParams);
+
+ /**
+ * Adds a named query parameter
+ *
+ * @param name name of parameter
+ * @param parameter value of parameter
+ * @return this
+ */
+ CallableQuery namedParam(String name, Object parameter);
+
+ /**
+ * Sets a custom error handler
+ *
+ * @param sqlErrorHandler
+ * @return this
+ */
+ CallableQuery errorHandler(SqlErrorHandler sqlErrorHandler);
+
+ /**
+ * executes the callable statement and returns a single result.
+ *
+ * @param mapper {@link CallableStatement} mapper
+ * @param result type
+ * @return exactly one result
+ * @throws org.codejargon.fluentjdbc.api.FluentJdbcException if no result found
+ */
+ T result(CallableMapper mapper);
+}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/Query.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/Query.java
index daed639..b003106 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/Query.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/Query.java
@@ -2,10 +2,6 @@
import org.codejargon.fluentjdbc.api.query.inspection.DatabaseInspection;
-import java.sql.Connection;
-import java.sql.SQLException;
-import java.util.function.Function;
-
/**
* FluentJdbc Query API to create select, update/insert, and batch update/insert queries. Immutable, thread-safe.
* @see org.codejargon.fluentjdbc.api.integration.ConnectionProvider
@@ -35,6 +31,14 @@ public interface Query {
*/
BatchQuery batch(String sql);
+ /**
+ * Creates a callable query for SQL statement
+ *
+ * @param sql callable SQL statement
+ * @return callable query for SQL statement
+ */
+ CallableQuery call(String sql);
+
/**
* Transaction control
*
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/SqlErrorHandler.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/SqlErrorHandler.java
index 2d2c10a..71d9762 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/SqlErrorHandler.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/SqlErrorHandler.java
@@ -3,19 +3,24 @@
import java.sql.SQLException;
import java.util.Optional;
+import org.codejargon.fluentjdbc.api.query.listen.QueryInfo;
+
public interface SqlErrorHandler {
enum Action {
RETRY
}
/**
- * Handles SQL errors, may implement logging, etc. The handler should always rethrow an exception in case of
- * a critical error. Otherwise retry action can be triggered. The handler is responsible to implement
- * delay or limitations for the retry.
+ * Handles SQL errors, may implement logging, etc. The handler should always
+ * re-throw an exception in case of a critical error. Otherwise retry action can
+ * be triggered. The handler is responsible to implement delay or limitations
+ * for the retry.
*
- * @param e the error
- * @param sql The sql query. Always present unless the error was thrown by direct plainConnection() usage.
- * @return In case no exception is thrown, otherwise action needs to be returned ( eg retry ).
+ * @param e the error
+ * @param queryInfo Query info including the SQL query. Always present unless
+ * the error was thrown by direct plainConnection() usage.
+ * @return In case no exception is thrown, otherwise action needs to be returned
+ * ( eg retry ).
*/
- Action handle(SQLException e, Optional sql) throws SQLException;
+ Action handle(SQLException e, Optional queryInfo) throws SQLException;
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/listen/ExecutionDetails.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/listen/ExecutionDetails.java
index e5b96ed..e34bf1d 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/listen/ExecutionDetails.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/listen/ExecutionDetails.java
@@ -5,7 +5,7 @@
public interface ExecutionDetails {
Boolean success();
- String sql();
+ QueryInfo queryInfo();
Long executionTimeMs();
Optional sqlException();
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/listen/QueryInfo.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/listen/QueryInfo.java
new file mode 100644
index 0000000..6063b40
--- /dev/null
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/api/query/listen/QueryInfo.java
@@ -0,0 +1,32 @@
+/**
+ *
+ */
+package org.codejargon.fluentjdbc.api.query.listen;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Information about what was executed - query + parameters. Parameters are not available for batch executions
+ *
+ * @author bwc
+ */
+public interface QueryInfo {
+
+ /**
+ * @return the sql query which was executed
+ */
+ String sql();
+
+ /**
+ * @return optional parameters
+ */
+ Optional> params();
+
+ /**
+ * @return optional named parameters
+ */
+ Optional> namedParams();
+
+}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/BatchQueryInternal.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/BatchQueryInternal.java
index 5c945ef..fe8336d 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/BatchQueryInternal.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/BatchQueryInternal.java
@@ -1,28 +1,34 @@
package org.codejargon.fluentjdbc.internal.query;
-import org.codejargon.fluentjdbc.api.FluentJdbcException;
-import org.codejargon.fluentjdbc.api.query.*;
-import org.codejargon.fluentjdbc.internal.query.namedparameter.NamedTransformedSql;
-import org.codejargon.fluentjdbc.internal.query.namedparameter.NamedTransformedSqlFactory;
-import org.codejargon.fluentjdbc.internal.support.Ints;
-import org.codejargon.fluentjdbc.internal.query.namedparameter.SqlAndParamsForNamed;
-import org.codejargon.fluentjdbc.internal.support.Preconditions;
+import static java.util.Optional.empty;
+import static java.util.stream.Collectors.toList;
+import static org.codejargon.fluentjdbc.internal.support.Iterables.stream;
+import static org.codejargon.fluentjdbc.internal.support.Sneaky.consumer;
-import java.awt.geom.AffineTransform;
import java.sql.Connection;
import java.sql.PreparedStatement;
-import java.sql.ResultSet;
import java.sql.SQLException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.function.Supplier;
-import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
-import static java.util.Optional.empty;
-import static java.util.stream.Collectors.toList;
-import static org.codejargon.fluentjdbc.internal.support.Sneaky.consumer;
-import static org.codejargon.fluentjdbc.internal.support.Iterables.stream;
+import org.codejargon.fluentjdbc.api.FluentJdbcException;
+import org.codejargon.fluentjdbc.api.query.BatchQuery;
+import org.codejargon.fluentjdbc.api.query.Mapper;
+import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
+import org.codejargon.fluentjdbc.api.query.UpdateResult;
+import org.codejargon.fluentjdbc.api.query.UpdateResultGenKeys;
+import org.codejargon.fluentjdbc.internal.query.namedparameter.NamedTransformedSql;
+import org.codejargon.fluentjdbc.internal.query.namedparameter.NamedTransformedSqlFactory;
+import org.codejargon.fluentjdbc.internal.query.namedparameter.SqlAndParamsForNamed;
+import org.codejargon.fluentjdbc.internal.support.Ints;
+import org.codejargon.fluentjdbc.internal.support.Preconditions;
class BatchQueryInternal implements BatchQuery {
private static final String namedSet = "Named parameters are already set.";
@@ -111,7 +117,7 @@ private List run(FetchGenKey fetchGen) {
Preconditions.checkArgument(params.isPresent() || namedParams.isPresent(), "Parameters must be set to run a batch query");
return query.query(
connection -> params.isPresent() ? positional(connection, fetchGen) : named(connection, fetchGen),
- Optional.of(sql),
+ Optional.of(QueryInfoInternal.of(sql)),
sqlErrorHandler.get()
);
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/CallableQueryInternal.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/CallableQueryInternal.java
new file mode 100644
index 0000000..00398fe
--- /dev/null
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/CallableQueryInternal.java
@@ -0,0 +1,68 @@
+package org.codejargon.fluentjdbc.internal.query;
+
+import java.sql.CallableStatement;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.List;
+import java.util.Map;
+
+import org.codejargon.fluentjdbc.api.FluentJdbcException;
+import org.codejargon.fluentjdbc.api.query.CallableMapper;
+import org.codejargon.fluentjdbc.api.query.CallableQuery;
+import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
+
+class CallableQueryInternal extends SingleQueryBase implements CallableQuery {
+
+ CallableQueryInternal(String sql, QueryInternal query) {
+ super(query, sql);
+ }
+
+ @Override
+ public CallableQuery params(List> params) {
+ addParameters(params);
+ return this;
+ }
+
+ @Override
+ public CallableQuery params(Object... params) {
+ addParameters(params);
+ return this;
+ }
+
+ @Override
+ public CallableQuery namedParams(Map namedParams) {
+ addNamedParameters(namedParams);
+ return this;
+ }
+
+ @Override
+ public CallableQuery namedParam(String name, Object parameter) {
+ addNamedParameter(name, parameter);
+ return this;
+ }
+
+ @Override
+ public CallableQuery errorHandler(SqlErrorHandler sqlErrorHandler ) {
+ this.sqlErrorHandler = () -> sqlErrorHandler;
+ return this;
+ }
+
+
+ @Override
+ public T result(CallableMapper mapper) {
+ return runQuery(ps -> {
+ if (ps instanceof CallableStatement) {
+ CallableStatement cs = (CallableStatement)ps;
+ cs.execute();
+ return mapper.map(cs);
+ } else {
+ throw new FluentJdbcException("No out parameters specified for call");
+ }
+ }, sqlErrorHandler.get());
+ }
+
+ @Override
+ void customizeQuery(PreparedStatement statement, QueryConfig config) throws SQLException {
+ // nothing to do here for callable statements
+ }
+}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DatabaseInspectionInternal.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DatabaseInspectionInternal.java
index fbcd1d8..ac4d428 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DatabaseInspectionInternal.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DatabaseInspectionInternal.java
@@ -1,12 +1,12 @@
package org.codejargon.fluentjdbc.internal.query;
+import java.util.Optional;
+
import org.codejargon.fluentjdbc.api.query.inspection.DatabaseInspection;
import org.codejargon.fluentjdbc.api.query.inspection.MetaDataAccess;
import org.codejargon.fluentjdbc.api.query.inspection.MetaDataResultSet;
import org.codejargon.fluentjdbc.api.query.inspection.MetaDataSelect;
-import java.util.Optional;
-
class DatabaseInspectionInternal implements DatabaseInspection {
private final QueryInternal query;
@@ -18,7 +18,7 @@ class DatabaseInspectionInternal implements DatabaseInspection {
public T accessMetaData(MetaDataAccess access) {
return query.query(
connection -> access.access(connection.getMetaData()),
- Optional.of("JDBC Database Inspection"),
+ Optional.of(QueryInfoInternal.of("JDBC Database Inspection")),
query.config.defaultSqlErrorHandler.get()
);
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DefaultParamSetters.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DefaultParamSetters.java
index 925ac35..ede0783 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DefaultParamSetters.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DefaultParamSetters.java
@@ -1,13 +1,25 @@
package org.codejargon.fluentjdbc.internal.query;
-import org.codejargon.fluentjdbc.api.ParamSetter;
-
-import java.time.*;
+import java.sql.CallableStatement;
+import java.sql.JDBCType;
+import java.sql.SQLType;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
+import java.time.OffsetDateTime;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
+import org.codejargon.fluentjdbc.api.FluentJdbcException;
+import org.codejargon.fluentjdbc.api.ParamSetter;
+
class DefaultParamSetters {
private static final Map setters;
@@ -17,6 +29,7 @@ class DefaultParamSetters {
javaDate(ss);
javaTime(ss);
javaBinary(ss);
+ callableOutParams(ss);
setters = Collections.unmodifiableMap(ss);
}
@@ -41,6 +54,28 @@ private static void javaBinary(Map ss) {
reg(ss, byte[].class, (param, ps, i) -> ps.setBytes(i, param));
}
+ /**
+ * CallableStatement out parameters are indicated by specifying parameter with type of {@link JDBCType}
+ *
+ * @param ss map of setters
+ */
+ private static void callableOutParams(Map ss) {
+ reg(ss, SQLType.class, (param, ps, i) -> {
+ if (ps instanceof CallableStatement) {
+ ((CallableStatement)ps).registerOutParameter(i, param);
+ } else {
+ throw new FluentJdbcException("use SQLType only for CallableStatements as out parameters");
+ }
+ });
+ reg(ss, JDBCType.class, (param, ps, i) -> {
+ if (ps instanceof CallableStatement) {
+ ((CallableStatement)ps).registerOutParameter(i, param);
+ } else {
+ throw new FluentJdbcException("use JDBCType only for CallableStatements as out parameters");
+ }
+ });
+ }
+
static Map setters() {
return setters;
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DefaultSqlHandler.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DefaultSqlHandler.java
index 89a5d5b..372f459 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DefaultSqlHandler.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/DefaultSqlHandler.java
@@ -1,13 +1,14 @@
package org.codejargon.fluentjdbc.internal.query;
-import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
-
import java.sql.SQLException;
import java.util.Optional;
+import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
+import org.codejargon.fluentjdbc.api.query.listen.QueryInfo;
+
public class DefaultSqlHandler implements SqlErrorHandler {
@Override
- public Action handle(SQLException e, Optional sql) throws SQLException {
+ public Action handle(SQLException e, Optional queryInfo) throws SQLException {
throw e;
}
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/ExecutionDetailsInternal.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/ExecutionDetailsInternal.java
index e4434e7..ba80552 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/ExecutionDetailsInternal.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/ExecutionDetailsInternal.java
@@ -1,20 +1,21 @@
package org.codejargon.fluentjdbc.internal.query;
-import org.codejargon.fluentjdbc.api.query.listen.ExecutionDetails;
-
import java.sql.SQLException;
import java.util.Optional;
+import org.codejargon.fluentjdbc.api.query.listen.ExecutionDetails;
+import org.codejargon.fluentjdbc.api.query.listen.QueryInfo;
+
class ExecutionDetailsInternal implements ExecutionDetails {
- private final String sql;
+ private final QueryInfo queryInfo;
private final Long executionTimeMs;
private final Optional sqlException;
public ExecutionDetailsInternal(
- String sql,
+ QueryInfo queryInfo,
Long executionTimeMs,
Optional sqlException) {
- this.sql = sql;
+ this.queryInfo = queryInfo;
this.executionTimeMs = executionTimeMs;
this.sqlException = sqlException;
}
@@ -25,8 +26,8 @@ public Boolean success() {
}
@Override
- public String sql() {
- return sql;
+ public QueryInfo queryInfo() {
+ return queryInfo;
}
@Override
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/PreparedStatementFactory.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/PreparedStatementFactory.java
index 0fbd53a..74cf1d6 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/PreparedStatementFactory.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/PreparedStatementFactory.java
@@ -1,5 +1,6 @@
package org.codejargon.fluentjdbc.internal.query;
+import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
@@ -14,9 +15,11 @@ class PreparedStatementFactory {
this.config = config;
}
- PreparedStatement createSingle(Connection con, SingleQueryBase singleQueryBase, boolean fetchGenerated, String[] genColumns) throws SQLException {
+ PreparedStatement createSingle(Connection con, SingleQueryBase singleQueryBase, boolean fetchGenerated,
+ String[] genColumns) throws SQLException {
SqlAndParams sqlAndParams = singleQueryBase.sqlAndParams(config);
- PreparedStatement statement = prepareStatement(con, sqlAndParams.sql(), fetchGenerated, genColumns);
+ PreparedStatement statement = sqlAndParams.hasOutParameters() ? prepareCall(con, sqlAndParams.sql())
+ : prepareStatement(con, sqlAndParams.sql(), fetchGenerated, genColumns);
singleQueryBase.customizeQuery(statement, config);
assignParams(statement, sqlAndParams.params());
return statement;
@@ -39,7 +42,9 @@ private PreparedStatement prepareStatement(Connection con, String sql, Boolean f
con.prepareStatement(sql);
}
-
+ private CallableStatement prepareCall(Connection con, String sql) throws SQLException {
+ return con.prepareCall(sql);
+ }
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/QueryInfoInternal.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/QueryInfoInternal.java
new file mode 100644
index 0000000..06f964f
--- /dev/null
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/QueryInfoInternal.java
@@ -0,0 +1,69 @@
+/**
+ *
+ */
+package org.codejargon.fluentjdbc.internal.query;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.codejargon.fluentjdbc.api.query.listen.QueryInfo;
+
+/**
+ * Implementation of {@link QueryInfo}
+ *
+ * @author bwc
+ */
+public class QueryInfoInternal implements QueryInfo {
+ private final String sql;
+ private final Optional> params;
+ private final Optional> namedParams;
+
+ /**
+ * @param sql
+ * @param params
+ * @param namedParams
+ */
+ public QueryInfoInternal(String sql, Optional> params, Optional> namedParams) {
+ this.sql = sql;
+ this.params = params;
+ this.namedParams = namedParams;
+ }
+
+ public static Optional optional(String sql, List params, Map namedParams) {
+ return Optional.of(of(sql, params, namedParams));
+ }
+
+ public static QueryInfo of(String sql, List params, Map namedParams) {
+ return new QueryInfoInternal(sql, params.isEmpty() ? Optional.empty() : Optional.of(params),
+ namedParams.isEmpty() ? Optional.empty() : Optional.of(namedParams));
+ }
+
+ public static QueryInfo of(String sql) {
+ return new QueryInfoInternal(sql, Optional.empty(), Optional.empty());
+ }
+
+ /**
+ * @return the sql
+ */
+ @Override
+ public String sql() {
+ return sql;
+ }
+
+ /**
+ * @return the params
+ */
+ @Override
+ public Optional> params() {
+ return params;
+ }
+
+ /**
+ * @return the namedParams
+ */
+ @Override
+ public Optional> namedParams() {
+ return namedParams;
+ }
+}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/QueryInternal.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/QueryInternal.java
index 9f5f919..f9b2e91 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/QueryInternal.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/QueryInternal.java
@@ -1,20 +1,27 @@
package org.codejargon.fluentjdbc.internal.query;
-import org.codejargon.fluentjdbc.api.FluentJdbcException;
-import org.codejargon.fluentjdbc.api.FluentJdbcSqlException;
-import org.codejargon.fluentjdbc.api.integration.ConnectionProvider;
-import org.codejargon.fluentjdbc.api.query.*;
-import org.codejargon.fluentjdbc.api.query.inspection.DatabaseInspection;
-import org.codejargon.fluentjdbc.internal.integration.QueryConnectionReceiverInternal;
+import static org.codejargon.fluentjdbc.internal.support.Preconditions.checkNotNull;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
-import java.util.function.Function;
-import static org.codejargon.fluentjdbc.internal.support.Preconditions.checkNotNull;
+import org.codejargon.fluentjdbc.api.FluentJdbcException;
+import org.codejargon.fluentjdbc.api.FluentJdbcSqlException;
+import org.codejargon.fluentjdbc.api.integration.ConnectionProvider;
+import org.codejargon.fluentjdbc.api.query.BatchQuery;
+import org.codejargon.fluentjdbc.api.query.CallableQuery;
+import org.codejargon.fluentjdbc.api.query.PlainConnectionQuery;
+import org.codejargon.fluentjdbc.api.query.Query;
+import org.codejargon.fluentjdbc.api.query.SelectQuery;
+import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
+import org.codejargon.fluentjdbc.api.query.Transaction;
+import org.codejargon.fluentjdbc.api.query.UpdateQuery;
+import org.codejargon.fluentjdbc.api.query.inspection.DatabaseInspection;
+import org.codejargon.fluentjdbc.api.query.listen.QueryInfo;
+import org.codejargon.fluentjdbc.internal.integration.QueryConnectionReceiverInternal;
public class QueryInternal implements Query {
@@ -46,6 +53,11 @@ public BatchQuery batch(String sql) {
return new BatchQueryInternal(sql, this);
}
+ @Override
+ public CallableQuery call(String sql) {
+ return new CallableQueryInternal(sql, this);
+ }
+
@Override
public Transaction transaction() {
return new TransactionInternal(this);
@@ -57,10 +69,10 @@ public DatabaseInspection databaseInspection() {
return new DatabaseInspectionInternal(this);
}
- T query(QueryRunnerConnection runner, Optional sql, SqlErrorHandler sqlErrorHandler) {
+ T query(QueryRunnerConnection runner, Optional queryInfo, SqlErrorHandler sqlErrorHandler) {
AttemptResult ret = new AttemptResult<>(null, false);
while(!ret.success) {
- ret = attemptQuery(runner, sql, sqlErrorHandler);
+ ret = attemptQuery(runner, queryInfo, sqlErrorHandler);
}
return ret.result;
}
@@ -70,21 +82,22 @@ public T plainConnection(PlainConnectionQuery plainConnectionQuery) {
return query(plainConnectionQuery::operation, Optional.empty(), config.defaultSqlErrorHandler.get());
}
- FluentJdbcException queryException(String sql, Optional reason, Optional e) {
- String message = String.format(
- "Error running query" + (reason.isPresent() ? ": " + reason.get() : "") + ", %s", sql
- );
+ FluentJdbcException queryException(Optional queryInfo, Optional reason, Optional e) {
+ String sql = queryInfo.map(QueryInfo::sql).orElse("");
+ String message = reason.isPresent()
+ ? String.format("Error running query: %s, %s", reason.get(), sql)
+ : String.format("Error running query, %s", sql);
return e.isPresent() ? new FluentJdbcSqlException(message, e.get()) : new FluentJdbcException(message);
}
- private AttemptResult attemptQuery(QueryRunnerConnection runner, Optional sql, SqlErrorHandler sqlErrorHandler) {
+ private AttemptResult attemptQuery(QueryRunnerConnection runner, Optional queryInfo, SqlErrorHandler sqlErrorHandler) {
long start = System.currentTimeMillis();
try {
T returnValue = doQuery(runner);
- listen(sql, start, Optional.empty());
+ listen(queryInfo, start, Optional.empty());
return new AttemptResult<>(returnValue, true);
} catch (SQLException e) {
- handleError(sql, sqlErrorHandler, start, e);
+ handleError(queryInfo, sqlErrorHandler, start, e);
return new AttemptResult<>(null, false);
}
}
@@ -105,21 +118,21 @@ void assignParams(PreparedStatement statement, List> params) throws SQLExcepti
preparedStatementFactory.assignParams(statement, params);
}
- private void listen(Optional sql, long start, Optional e) {
+ private void listen(Optional queryInfo, long start, Optional e) {
config.afterQueryListener.ifPresent(
afterQueryListener ->
- sql.ifPresent(sqlQuery -> afterQueryListener.listen(
- new ExecutionDetailsInternal(sqlQuery, System.currentTimeMillis() - start, e)
+ queryInfo.ifPresent(sqlQueryInfo -> afterQueryListener.listen(
+ new ExecutionDetailsInternal(sqlQueryInfo, System.currentTimeMillis() - start, e)
))
);
}
- private void handleError(Optional sql, SqlErrorHandler sqlErrorHandler, long start, SQLException e) {
+ private void handleError(Optional queryInfo, SqlErrorHandler sqlErrorHandler, long start, SQLException e) {
try {
- checkNotNull(sqlErrorHandler.handle(e, sql), "Action in SqlErrorHandler");
+ checkNotNull(sqlErrorHandler.handle(e, queryInfo), "Action in SqlErrorHandler");
} catch(SQLException sqle) {
- listen(sql, start, Optional.of(e));
- throw queryException(sql.orElse(""), Optional.empty(), Optional.of(e));
+ listen(queryInfo, start, Optional.of(e));
+ throw queryException(queryInfo, Optional.empty(), Optional.of(e));
}
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SelectQueryInternal.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SelectQueryInternal.java
index a4368c1..87796ef 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SelectQueryInternal.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SelectQueryInternal.java
@@ -1,22 +1,28 @@
package org.codejargon.fluentjdbc.internal.query;
-import org.codejargon.fluentjdbc.api.FluentJdbcException;
-import org.codejargon.fluentjdbc.api.query.Mapper;
-import org.codejargon.fluentjdbc.api.query.SelectQuery;
-import org.codejargon.fluentjdbc.api.query.SqlConsumer;
-import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
-import org.codejargon.fluentjdbc.internal.support.Predicates;
+import static java.util.Optional.empty;
+import static org.codejargon.fluentjdbc.internal.support.Preconditions.checkArgument;
+import static org.codejargon.fluentjdbc.internal.support.Preconditions.checkNotNull;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
-import static java.util.Optional.empty;
-import static org.codejargon.fluentjdbc.internal.support.Preconditions.checkArgument;
-import static org.codejargon.fluentjdbc.internal.support.Preconditions.checkNotNull;
+import org.codejargon.fluentjdbc.api.FluentJdbcException;
+import org.codejargon.fluentjdbc.api.query.Mapper;
+import org.codejargon.fluentjdbc.api.query.SelectQuery;
+import org.codejargon.fluentjdbc.api.query.SqlConsumer;
+import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
+import org.codejargon.fluentjdbc.internal.support.Predicates;
class SelectQueryInternal extends SingleQueryBase implements SelectQuery {
@@ -105,7 +111,7 @@ public Optional firstResult(Mapper mapper) {
public T singleResult(Mapper mapper) {
Optional firstResult = firstResult(mapper);
if (!firstResult.isPresent()) {
- throw query.queryException(sql, Optional.of("At least one result expected"), empty());
+ throw query.queryException(QueryInfoInternal.optional(sql, params, namedParams), Optional.of("At least one result expected"), empty());
}
return firstResult.get();
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SingleQueryBase.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SingleQueryBase.java
index c1c347d..26b062c 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SingleQueryBase.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SingleQueryBase.java
@@ -1,14 +1,18 @@
package org.codejargon.fluentjdbc.internal.query;
-import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
-import org.codejargon.fluentjdbc.internal.query.namedparameter.SqlAndParamsForNamed;
-import org.codejargon.fluentjdbc.internal.support.Preconditions;
-
import java.sql.PreparedStatement;
import java.sql.SQLException;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.function.Supplier;
+import org.codejargon.fluentjdbc.api.query.SqlErrorHandler;
+import org.codejargon.fluentjdbc.internal.query.namedparameter.SqlAndParamsForNamed;
+import org.codejargon.fluentjdbc.internal.support.Preconditions;
+
abstract class SingleQueryBase {
protected final String sql;
protected final QueryInternal query;
@@ -68,7 +72,7 @@ private T runQuery(
try (PreparedStatement ps = query.preparedStatementFactory.createSingle(connection, this, fetchGenerated, genColumns)) {
return queryRunnerPreparedStatement.run(ps);
}
- }, Optional.of(sql),
+ }, QueryInfoInternal.optional(sql, params, namedParams),
sqlErrorHandler);
}
diff --git a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SqlAndParams.java b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SqlAndParams.java
index f9cfcf9..9319107 100644
--- a/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SqlAndParams.java
+++ b/fluent-jdbc/src/main/java/org/codejargon/fluentjdbc/internal/query/SqlAndParams.java
@@ -1,5 +1,6 @@
package org.codejargon.fluentjdbc.internal.query;
+import java.sql.SQLType;
import java.util.List;
public class SqlAndParams {
@@ -18,4 +19,13 @@ String sql() {
List params() {
return params;
}
+
+ /**
+ * check whether parameters assume callable statement or prepared statement
+ *
+ * @return true if at least one of parameters is out parameter
+ */
+ public boolean hasOutParameters() {
+ return params.parallelStream().anyMatch(param -> param instanceof SQLType);
+ }
}
diff --git a/fluent-jdbc/src/test/groovy/org/codejargon/fluentjdbc/internal/query/FluentJdbcCallTest.groovy b/fluent-jdbc/src/test/groovy/org/codejargon/fluentjdbc/internal/query/FluentJdbcCallTest.groovy
new file mode 100644
index 0000000..7641902
--- /dev/null
+++ b/fluent-jdbc/src/test/groovy/org/codejargon/fluentjdbc/internal/query/FluentJdbcCallTest.groovy
@@ -0,0 +1,78 @@
+package org.codejargon.fluentjdbc.internal.query
+
+import org.codejargon.fluentjdbc.api.FluentJdbcException
+import org.codejargon.fluentjdbc.api.query.CallableMapper;
+import org.codejargon.fluentjdbc.api.FluentJdbcBuilder;
+import org.codejargon.fluentjdbc.api.query.Query
+import org.junit.Test
+import spock.lang.Specification
+
+import javax.naming.OperationNotSupportedException;
+import java.sql.Connection
+import java.sql.ParameterMetaData;
+import java.sql.CallableStatement;
+import java.sql.SQLException
+import java.sql.Types;
+import java.util.*
+
+class FluentJdbcCallTest extends Specification {
+
+ static String result1 = "res"
+ static String param1 = "lille"
+ static String param2 = "lamb"
+
+ def connection = Mock(Connection)
+ def callableStatement = Mock(CallableStatement)
+
+ Query query;
+
+ def setup() {
+ query = new FluentJdbcBuilder().connectionProvider(
+ { q -> q.receive(connection)}
+ ).build().query();
+ }
+
+ def "Call with ordered parameters"() {
+ given:
+ def sql = "{? = call pack.func(?, ?)}"
+ connection.prepareCall(sql) >> callableStatement
+ mockSelectData()
+ def orderedParams = [java.sql.JDBCType.VARCHAR, param1, param2]
+ when:
+ query.call(sql).params(orderedParams).result(dummyMapper)
+ then:
+ 1 * callableStatement.registerOutParameter(1, java.sql.JDBCType.VARCHAR)
+ 1 * callableStatement.setObject(2, param1)
+ 1 * callableStatement.setObject(3, param2)
+ }
+
+ def "Call with named parameters"() {
+ given:
+ def namedParamSql = "{:outPar = call pack.func(:param1, :param2, :param1)}"
+ def expectedSql = "{? = call pack.func(?, ?, ?)}"
+ connection.prepareCall(expectedSql) >> callableStatement
+ mockSelectData()
+ def namedParams = ["param1": param1, "outPar": java.sql.JDBCType.VARCHAR, "param2": param2]
+ when:
+ query.call(namedParamSql).namedParams(namedParams).result(dummyMapper)
+ then:
+ 1 * callableStatement.registerOutParameter(1, java.sql.JDBCType.VARCHAR)
+ 1 * callableStatement.setObject(2, param1)
+ 1 * callableStatement.setObject(3, param2)
+ 1 * callableStatement.setObject(4, param1)
+ }
+
+ private void mockSelectData() {
+ callableStatement.getString(1) >> result1
+ }
+
+ static CallableMapper dummyMapper = { cs -> new Dummy(cs.getString(1)) }
+
+ static class Dummy {
+ final String foo
+
+ Dummy(String foo) {
+ this.foo = foo
+ }
+ }
+}
diff --git a/fluent-jdbc/src/test/groovy/org/codejargon/fluentjdbc/internal/query/QueryListenerTest.groovy b/fluent-jdbc/src/test/groovy/org/codejargon/fluentjdbc/internal/query/QueryListenerTest.groovy
index 14b58ed..6008bd5 100644
--- a/fluent-jdbc/src/test/groovy/org/codejargon/fluentjdbc/internal/query/QueryListenerTest.groovy
+++ b/fluent-jdbc/src/test/groovy/org/codejargon/fluentjdbc/internal/query/QueryListenerTest.groovy
@@ -12,6 +12,7 @@ import java.sql.SQLException
class QueryListenerTest extends Specification {
protected static final def sql = "UPDATE FOO SET BAR = 'x' WHERE COL1 = ? AND COL2 = ?"
+ protected static final def namedSql = "UPDATE FOO SET BAR = 'x' WHERE COL1 = :name AND COL2 = :name"
Query query
def preparedStatement = Mock(PreparedStatement)
Connection connection = Mock(Connection)
@@ -29,13 +30,41 @@ class QueryListenerTest extends Specification {
.query()
}
- def "Listener invoked"() {
+ def "Listener invoked without params"() {
when:
query.update(sql).run()
then:
executionDetails != null
executionDetails.success()
- executionDetails.sql() == sql
+ executionDetails.queryInfo().sql() == sql
+ !executionDetails.queryInfo().params().isPresent()
+ !executionDetails.queryInfo().namedParams().isPresent()
+ executionDetails.executionTimeMs() >= 0
+ !executionDetails.sqlException().isPresent()
+ }
+
+ def "Listener invoked with param"() {
+ when:
+ query.update(sql).params(5).run()
+ then:
+ executionDetails != null
+ executionDetails.success()
+ executionDetails.queryInfo().sql() == sql
+ executionDetails.queryInfo().params().isPresent()
+ !executionDetails.queryInfo().namedParams().isPresent()
+ executionDetails.executionTimeMs() >= 0
+ !executionDetails.sqlException().isPresent()
+ }
+
+ def "Listener invoked with named param"() {
+ when:
+ query.update(namedSql).namedParam("name", "param").run()
+ then:
+ executionDetails != null
+ executionDetails.success()
+ executionDetails.queryInfo().sql() == namedSql
+ !executionDetails.queryInfo().params().isPresent()
+ executionDetails.queryInfo().namedParams().isPresent()
executionDetails.executionTimeMs() >= 0
!executionDetails.sqlException().isPresent()
}
@@ -49,7 +78,7 @@ class QueryListenerTest extends Specification {
thrown(FluentJdbcSqlException)
executionDetails != null
!executionDetails.success()
- executionDetails.sql() == sql
+ executionDetails.queryInfo().sql() == sql
executionDetails.executionTimeMs() >= 0
executionDetails.sqlException().isPresent()
}