forked from helidon-io/helidon
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue helidon-io#6991 - Blocking DB Client: Named parameters support …
…fixed and simple DML tests are now passing. Signed-off-by: Tomáš Kraus <[email protected]>
- Loading branch information
1 parent
8655f58
commit 7511994
Showing
16 changed files
with
2,359 additions
and
108 deletions.
There are no files selected for viewing
327 changes: 327 additions & 0 deletions
327
dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcCallableStatement.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.