buckelieg.fn.db.AbstractQuery Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of db-fn Show documentation
Show all versions of db-fn Show documentation
Functional style programming with plain JDBC
/*
* Copyright 2016- Anatoly Kutyakov
*
* 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 buckelieg.fn.db;
import javax.annotation.Nonnull;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static buckelieg.fn.db.Utils.newSQLRuntimeException;
abstract class AbstractQuery implements Query {
private static final Pattern PARAM = Pattern.compile("\\?");
private S statement;
private final String query;
AbstractQuery(TrySupplier connectionSupplier, String query, Object... params) {
Objects.requireNonNull(query, "SQL query must be provided");
this.statement = prepareStatement(connectionSupplier, query, params);
this.query = asSQL(query, params);
}
@Override
public final void close() {
jdbcTry(statement::close); // by JDBC spec: subsequently closes all result sets opened by this statement
}
final > Q setTimeout(int timeout) {
return setStatementParameter(statement -> statement.setQueryTimeout(timeout > 0 ? timeout : 0));
}
final > Q setPoolable(boolean poolable) {
return setStatementParameter(statement -> statement.setPoolable(poolable));
}
final > Q setEscapeProcessing(boolean escapeProcessing) {
return setStatementParameter(statement -> statement.setEscapeProcessing(escapeProcessing));
}
@SuppressWarnings("unchecked")
final > Q log(Consumer printer) {
Objects.requireNonNull(printer, "Printer must be provided").accept(query);
return (Q) this;
}
final O jdbcTry(TrySupplier supplier) {
O result = null;
try {
result = supplier.get();
} catch (SQLException e) {
throw newSQLRuntimeException(e);
} catch (AbstractMethodError ame) {
// ignore this possible vendor-specific JDBC driver's error.
}
return result;
}
private void jdbcTry(TryAction action) {
try {
Objects.requireNonNull(action, "Action must be provided").doTry();
} catch (SQLException e) {
throw newSQLRuntimeException(e);
}
}
final S setQueryParameters(S statement, Object... params) throws SQLException {
Objects.requireNonNull(params, "Parameters must be provided");
int pNum = 0;
for (Object p : params) {
statement.setObject(++pNum, p); // introduce type conversion here?
}
return statement;
}
@Nonnull
final O withStatement(TryFunction action) {
try {
return action.apply(statement);
} catch (SQLException e) {
throw newSQLRuntimeException(e);
}
}
@SuppressWarnings("unchecked")
final > Q setStatementParameter(TryConsumer action) {
jdbcTry(() -> action.accept(statement));
return (Q) this;
}
abstract S prepareStatement(TrySupplier connectionSupplier, String query, Object... params);
String asSQL(String query, Object... params) {
String replaced = query;
int idx = 0;
Matcher matcher = PARAM.matcher(query);
while (matcher.find()) {
Object p = params[idx];
replaced = replaced.replaceFirst(
"\\?",
(p != null && p.getClass().isArray() ? Arrays.stream((Object[]) p) : Stream.of(Optional.ofNullable(p).orElse("null")))
.map(Object::toString)
.collect(Collectors.joining(","))
);
idx++;
}
return replaced;
}
@Override
public String toString() {
return query;
}
}