Skip to content

Commit

Permalink
Issue helidon-io#6991 - Blocking DB Client: Named parameters support …
Browse files Browse the repository at this point in the history
…fixed and simple DML tests are now passing.

Signed-off-by: Tomáš Kraus <[email protected]>
  • Loading branch information
Tomas-Kraus committed Jun 26, 2023
1 parent 8655f58 commit 7511994
Show file tree
Hide file tree
Showing 16 changed files with 2,359 additions and 108 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
/*
* Copyright (c) 2023 Oracle and/or its affiliates.
*
* 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 io.helidon.dbclient.jdbc;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.helidon.dbclient.DbNamedParameterException;

/**
* Extends JDBC {@link PreparedStatement} with named parameters support.
*/
public class JdbcCallableStatement {

/** Local logger instance. */
private static final System.Logger LOGGER = System.getLogger(JdbcCallableStatement.class.getName());

// JDBC statement String for logger and exception messages.
private final String statementString;
// Use PreparedStatement as delegate.
private final PreparedStatement statement;
// Parameter names in the same order as in the statement String.
private final List<String> namesOrder;
// Parameters values set by user.
// Each supported value type has its own handler to use proper PreparedStatement set method for its type.
private final Map<String, ValueHandler> parameters = new HashMap<>();

// Instance shall be always created by factory method.
private JdbcCallableStatement(String statementString, PreparedStatement statement, List<String> namesOrder) {
this.statementString = statementString;
this.statement = statement;
this.namesOrder = namesOrder;
}

// JDBC statement setters. Those methods resemble to PreparedStatement indexed setters.

void setObject(String name, Object parameter) {
parameters.put(name, new ObjectHandler(parameter));
}

void setBoolean(String name, boolean parameter) {
parameters.put(name, new BooleanHandler(parameter));
}

void setString(String name, String parameter) {
parameters.put(name, new StringHandler(parameter));
}

void setByte(String name, byte parameter) {
parameters.put(name, new ByteHandler(parameter));
}

void setShort(String name, short parameter) {
parameters.put(name, new ShortHandler(parameter));
}

void setInt(String name, int parameter) {
parameters.put(name, new IntHandler(parameter));
}

void setLong(String name, long parameter) {
parameters.put(name, new LongHandler(parameter));
}

void setFloat(String name, float parameter) {
parameters.put(name, new FloatHandler(parameter));
}

void setDouble(String name, double parameter) {
parameters.put(name, new DoubleHandler(parameter));
}

void setBigDecimal(String name, BigDecimal parameter) {
parameters.put(name, new BigDecimalHandler(parameter));
}

void setBytes(String name, byte[] parameter) {
parameters.put(name, new BytesHandler(parameter));
}

ResultSet executeQuery() throws SQLException {
setParameters();
return statement.executeQuery();
}

int executeUpdate() throws SQLException {
setParameters();
return statement.executeUpdate();
}

void close() throws SQLException {
statement.close();
}

// Set PreparedStatement parameters before statement is executed.
private void setParameters() throws SQLException {
// Index starts from 1
int i = 1;
// Walk through query parameters names order list
for (String name : namesOrder) {
if (parameters.containsKey(name)) {
ValueHandler handler = parameters.get(name);
try {
handler.set(statement, i);
} catch (SQLException ex) {
throw new DbNamedParameterException("Failed to set named parameter", name, statementString, ex);
}
LOGGER.log(System.Logger.Level.DEBUG,
String.format("Mapped parameter %d: %s -> %s", i, name, handler.valueToString()));
i++;
// Throw an exception when some parameter name was not set.
} else {
throw new DbNamedParameterException(
String.format("Missing named parameter %s from the statement", name), name, statementString);
}
}
}

/**
* Creates an instance of JDBC {@link PreparedStatement} with named parameters support.
*
* @param connection JDBC database connection
* @param statementString JDBC statement {@link String} with named parameters.
* @return new instance of JDBC {@link PreparedStatement} with named parameters support
* @throws SQLException if a database access error occurs or this method is called on a closed result set
*/
static JdbcCallableStatement create(Connection connection, String statementString) throws SQLException {
NamedStatementParser parser = new NamedStatementParser(statementString);
String jdbcStatement = parser.convert();
LOGGER.log(System.Logger.Level.TRACE, () -> String.format("Converted statement: %s", jdbcStatement));
return new JdbcCallableStatement(statementString,
connection.prepareStatement(jdbcStatement),
parser.namesOrder());
}

// Value handlers with type specific PreparedStatement setter call.

private interface ValueHandler {

// Type specific setter.
void set(PreparedStatement statement, int index) throws SQLException;

// String representation of the value for logger and exception messages.
String valueToString();

}

// Common code for Objects.
// Primitive types are stored directly in their own records.
private static abstract class AbstractHandler<T> implements ValueHandler {

T value;

AbstractHandler(T value) {
this.value = value;
}

@Override
public String valueToString() {
return value.toString();
}

}

private static final class ObjectHandler extends AbstractHandler<Object> {

ObjectHandler(Object value) {
super(value);
}

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setObject(index, value);
}

}

private static final class StringHandler extends AbstractHandler<String> {

StringHandler(String value) {
super(value);
}

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setString(index, value);
}

}

private record BooleanHandler(boolean value) implements ValueHandler {

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setBoolean(index, value);
}

@Override
public String valueToString() {
return Boolean.toString(value);
}
}

private record ByteHandler(byte value) implements ValueHandler {

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setByte(index, value);
}

@Override
public String valueToString() {
return Byte.toString(value);
}
}

private record ShortHandler(short value) implements ValueHandler {

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setShort(index, value);
}

@Override
public String valueToString() {
return Short.toString(value);
}
}

private record IntHandler(int value) implements ValueHandler {

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setInt(index, value);
}

@Override
public String valueToString() {
return Integer.toString(value);
}
}

private record LongHandler(long value) implements ValueHandler {

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setLong(index, value);
}

@Override
public String valueToString() {
return Long.toString(value);
}
}

private record FloatHandler(float value) implements ValueHandler {

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setFloat(index, value);
}

@Override
public String valueToString() {
return Float.toString(value);
}
}

private record DoubleHandler(double value) implements ValueHandler {

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setDouble(index, value);
}

@Override
public String valueToString() {
return Double.toString(value);
}
}

private static final class BigDecimalHandler extends AbstractHandler<BigDecimal> {

BigDecimalHandler(BigDecimal value) {
super(value);
}

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setBigDecimal(index, value);
}

}

private static final class BytesHandler extends AbstractHandler<byte[]> {

BytesHandler(byte[] value) {
super(value);
}

@Override
public void set(PreparedStatement statement, int index) throws SQLException {
statement.setBytes(index, value);
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ class JdbcClient extends CommonClient implements DbClient {

@Override
public JdbcExecute execute() {

return null;
return JdbcExecute.create(context(), connectionPool.connection());
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class JdbcExecute extends CommonExecute {

private final Connection connection;

JdbcExecute(CommonClientContext context, Connection connection) {
private JdbcExecute(CommonClientContext context, Connection connection) {
super(context);
this.connection = connection;
}
Expand All @@ -41,7 +41,7 @@ public DbStatementQuery createNamedQuery(String statementName, String statement)

@Override
public DbStatementGet createNamedGet(String statementName, String statement) {
return null;
return new JdbcStatementGet(connection, JdbcExecuteContext.create(statementName, statement, context()));
}

@Override
Expand All @@ -67,4 +67,8 @@ public void close() {
}
}

static JdbcExecute create(CommonClientContext context, Connection connection) {
return new JdbcExecute(context, connection);
}

}
Loading

0 comments on commit 7511994

Please sign in to comment.