Skip to content

Commit a404c8d

Browse files
committed
jdbc: support ParameterMetaData class
In addition to result set metadata it's possible to examine parameters of PreparedStatement using getParameterMetaData() method. Because Tarantool returns extra info related to query parameters as a result of PREPARE operation, we can fill ParameterMetaData by available info. However, the server sends always 'ANY' as a target parameter type for parameters and the driver treats all of them as UNKNOWN type. Once the server starts to send proper types (such as integer, string and so on) the driver should parse it automatically (required to be tested in future). Follows on: #173
1 parent 5b6524e commit a404c8d

File tree

4 files changed

+241
-1
lines changed

4 files changed

+241
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package org.tarantool.jdbc;
2+
3+
import org.tarantool.SqlProtoUtils;
4+
import org.tarantool.util.SQLStates;
5+
6+
import java.sql.ParameterMetaData;
7+
import java.sql.SQLException;
8+
import java.sql.SQLNonTransientException;
9+
import java.util.List;
10+
11+
public class SQLParameterMetaData implements ParameterMetaData {
12+
13+
private final List<SqlProtoUtils.SQLMetaData> metaData;
14+
15+
public SQLParameterMetaData(List<SqlProtoUtils.SQLMetaData> metaData) {
16+
this.metaData = metaData;
17+
}
18+
19+
@Override
20+
public int getParameterCount() {
21+
return metaData.size();
22+
}
23+
24+
@Override
25+
public int isNullable(int param) throws SQLException {
26+
checkParameterIndex(param);
27+
return ParameterMetaData.parameterNullableUnknown;
28+
}
29+
30+
@Override
31+
public boolean isSigned(int param) throws SQLException {
32+
return getAtIndex(param).getType().isSigned();
33+
}
34+
35+
@Override
36+
public int getPrecision(int param) throws SQLException {
37+
return getAtIndex(param).getType().getPrecision();
38+
}
39+
40+
@Override
41+
public int getScale(int param) throws SQLException {
42+
return getAtIndex(param).getType().getScale();
43+
}
44+
45+
@Override
46+
public int getParameterType(int param) throws SQLException {
47+
return getAtIndex(param).getType().getJdbcType().getTypeNumber();
48+
}
49+
50+
@Override
51+
public String getParameterTypeName(int param) throws SQLException {
52+
return getAtIndex(param).getType().getTypeName();
53+
}
54+
55+
@Override
56+
public String getParameterClassName(int param) throws SQLException {
57+
return getAtIndex(param).getType().getJdbcType().getJavaType().getName();
58+
}
59+
60+
@Override
61+
public int getParameterMode(int param) throws SQLException {
62+
checkParameterIndex(param);
63+
return ParameterMetaData.parameterModeIn;
64+
}
65+
66+
@Override
67+
public <T> T unwrap(Class<T> type) throws SQLException {
68+
if (isWrapperFor(type)) {
69+
return type.cast(this);
70+
}
71+
throw new SQLNonTransientException("SQLParameterMetaData does not wrap " + type.getName());
72+
}
73+
74+
@Override
75+
public boolean isWrapperFor(Class<?> type) throws SQLException {
76+
return type.isAssignableFrom(this.getClass());
77+
}
78+
79+
private SqlProtoUtils.SQLMetaData getAtIndex(int index) throws SQLException {
80+
checkParameterIndex(index);
81+
return metaData.get(index - 1);
82+
}
83+
84+
private void checkParameterIndex(int index) throws SQLException {
85+
int parameterCount = getParameterCount();
86+
if (index < 1 || index > parameterCount) {
87+
throw new SQLNonTransientException(
88+
String.format("Parameter index %d is out of range. Max index is %d", index, parameterCount),
89+
SQLStates.INVALID_PARAMETER_VALUE.getSqlState()
90+
);
91+
}
92+
}
93+
}

src/main/java/org/tarantool/jdbc/SQLPreparedStatement.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public class SQLPreparedStatement extends SQLStatement implements PreparedStatem
4444
private final int autoGeneratedKeys;
4545
private List<Map<Integer, Object>> batchParameters = new ArrayList<>();
4646
private ResultSetMetaData resultSetMetaData;
47+
private ParameterMetaData parameterMetaData;
4748

4849
public SQLPreparedStatement(SQLConnection connection, String sql, int autoGeneratedKeys) throws SQLException {
4950
super(connection);
@@ -78,6 +79,7 @@ private void prepareQuery(String sql) throws SQLException {
7879
if (!preparedHolder.getResultMetadata().isEmpty()) {
7980
resultSetMetaData = new SQLResultSetMetaData(preparedHolder.getResultMetadata(), connection.isReadOnly());
8081
}
82+
parameterMetaData = new SQLParameterMetaData(preparedHolder.getParamsMetadata());
8183
}
8284

8385
@Override
@@ -402,7 +404,8 @@ public void setURL(int parameterIndex, URL parameterValue) throws SQLException {
402404

403405
@Override
404406
public ParameterMetaData getParameterMetaData() throws SQLException {
405-
return null;
407+
checkNotClosed();
408+
return parameterMetaData;
406409
}
407410

408411
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package org.tarantool.jdbc;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
import static org.junit.jupiter.api.Assertions.assertThrows;
7+
import static org.junit.jupiter.api.Assertions.assertTrue;
8+
import static org.tarantool.TestAssumptions.assumeMinimalServerVersion;
9+
10+
import org.tarantool.TarantoolTestHelper;
11+
import org.tarantool.util.SQLStates;
12+
import org.tarantool.util.ServerVersion;
13+
14+
import org.junit.jupiter.api.AfterAll;
15+
import org.junit.jupiter.api.AfterEach;
16+
import org.junit.jupiter.api.BeforeAll;
17+
import org.junit.jupiter.api.BeforeEach;
18+
import org.junit.jupiter.api.DisplayName;
19+
import org.junit.jupiter.api.Test;
20+
21+
import java.sql.Connection;
22+
import java.sql.DriverManager;
23+
import java.sql.JDBCType;
24+
import java.sql.ParameterMetaData;
25+
import java.sql.PreparedStatement;
26+
import java.sql.SQLException;
27+
28+
@DisplayName("A parameter metadata")
29+
public class JdbcParameterMetaDataIT {
30+
31+
private static final String[] INIT_SQL = new String[] {
32+
"CREATE TABLE test(id INT PRIMARY KEY, val VARCHAR(100), bin_val SCALAR)",
33+
};
34+
35+
private static final String[] CLEAN_SQL = new String[] {
36+
"DROP TABLE IF EXISTS test"
37+
};
38+
39+
private static TarantoolTestHelper testHelper;
40+
private static Connection connection;
41+
42+
@BeforeAll
43+
public static void setupEnv() throws SQLException {
44+
testHelper = new TarantoolTestHelper("jdbc-param-metadata-it");
45+
testHelper.createInstance();
46+
testHelper.startInstance();
47+
48+
connection = DriverManager.getConnection(SqlTestUtils.makeDefaultJdbcUrl());
49+
}
50+
51+
@AfterAll
52+
public static void teardownEnv() throws SQLException {
53+
if (connection != null) {
54+
connection.close();
55+
}
56+
testHelper.stopInstance();
57+
}
58+
59+
@BeforeEach
60+
public void setUpTest() throws SQLException {
61+
assumeMinimalServerVersion(testHelper.getInstanceVersion(), ServerVersion.V_2_3);
62+
testHelper.executeSql(INIT_SQL);
63+
}
64+
65+
@AfterEach
66+
public void tearDownTest() throws SQLException {
67+
assumeMinimalServerVersion(testHelper.getInstanceVersion(), ServerVersion.V_2_3);
68+
testHelper.executeSql(CLEAN_SQL);
69+
}
70+
71+
@Test
72+
@DisplayName("fetched parameter metadata")
73+
public void testPreparedParameterMetaData() throws SQLException {
74+
try (PreparedStatement statement =
75+
connection.prepareStatement("SELECT val FROM test WHERE id = ? AND val = ?")) {
76+
ParameterMetaData parameterMetaData = statement.getParameterMetaData();
77+
assertNotNull(parameterMetaData);
78+
assertEquals(2, parameterMetaData.getParameterCount());
79+
assertEquals(JDBCType.OTHER.getVendorTypeNumber(), parameterMetaData.getParameterType(1));
80+
assertEquals(ParameterMetaData.parameterModeIn, parameterMetaData.getParameterMode(1));
81+
assertEquals(ParameterMetaData.parameterNullableUnknown, parameterMetaData.isNullable(1));
82+
}
83+
}
84+
85+
@Test
86+
@DisplayName("failed to get info by wrong parameter index")
87+
public void testWrongParameterIndex() throws SQLException {
88+
try (PreparedStatement statement = connection.prepareStatement("INSERT INTO test VALUES (?, ?, ?)")) {
89+
ParameterMetaData parameterMetaData = statement.getParameterMetaData();
90+
assertNotNull(parameterMetaData);
91+
SQLException biggerThanMaxError = assertThrows(
92+
SQLException.class,
93+
() -> parameterMetaData.getParameterMode(4)
94+
);
95+
SQLException lessThanZeroError = assertThrows(
96+
SQLException.class,
97+
() -> parameterMetaData.getParameterMode(-4)
98+
);
99+
100+
assertEquals(biggerThanMaxError.getSQLState(), SQLStates.INVALID_PARAMETER_VALUE.getSqlState());
101+
assertEquals(lessThanZeroError.getSQLState(), SQLStates.INVALID_PARAMETER_VALUE.getSqlState());
102+
}
103+
}
104+
105+
@Test
106+
@DisplayName("unwrapped correct")
107+
public void testUnwrap() throws SQLException {
108+
try (PreparedStatement statement = connection.prepareStatement("SELECT val FROM test")) {
109+
ParameterMetaData metaData = statement.getParameterMetaData();
110+
assertEquals(metaData, metaData.unwrap(SQLParameterMetaData.class));
111+
assertThrows(SQLException.class, () -> metaData.unwrap(Integer.class));
112+
}
113+
}
114+
115+
@Test
116+
@DisplayName("checked as a proper wrapper")
117+
public void testIsWrapperFor() throws SQLException {
118+
try (PreparedStatement statement = connection.prepareStatement("SELECT * FROM test")) {
119+
ParameterMetaData metaData = statement.getParameterMetaData();
120+
assertTrue(metaData.isWrapperFor(SQLParameterMetaData.class));
121+
assertFalse(statement.isWrapperFor(Integer.class));
122+
}
123+
}
124+
125+
}

src/test/java/org/tarantool/jdbc/JdbcPreparedStatementIT.java

+19
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import java.sql.Connection;
4040
import java.sql.DriverManager;
4141
import java.sql.JDBCType;
42+
import java.sql.ParameterMetaData;
4243
import java.sql.PreparedStatement;
4344
import java.sql.ResultSet;
4445
import java.sql.ResultSetMetaData;
@@ -949,6 +950,24 @@ public void testDeallocateExpiredPreparedStatement() throws SQLException {
949950
assertEquals(0, preparedCount);
950951
}
951952

953+
@Test
954+
public void testPreparedParameterMetaData() throws SQLException {
955+
assumeMinimalServerVersion(testHelper.getInstanceVersion(), ServerVersion.V_2_3);
956+
prep = conn.prepareStatement("SELECT val FROM test WHERE id = ? AND val = ?");
957+
ParameterMetaData parameterMetaData = prep.getParameterMetaData();
958+
assertNotNull(parameterMetaData);
959+
assertEquals(2, parameterMetaData.getParameterCount());
960+
}
961+
962+
@Test
963+
public void testEmptyParameterMetaData() throws SQLException {
964+
assumeMinimalServerVersion(testHelper.getInstanceVersion(), ServerVersion.V_2_3);
965+
prep = conn.prepareStatement("SELECT * FROM test");
966+
ParameterMetaData parameterMetaData = prep.getParameterMetaData();
967+
assertNotNull(parameterMetaData);
968+
assertEquals(0, parameterMetaData.getParameterCount());
969+
}
970+
952971
private List<?> consoleSelect(Object key) {
953972
List<?> list = testHelper.evaluate(TestUtils.toLuaSelect("TEST", key));
954973
return list == null ? Collections.emptyList() : (List<?>) list.get(0);

0 commit comments

Comments
 (0)