Skip to content

Commit b28e657

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 ade7cee commit b28e657

File tree

4 files changed

+247
-4
lines changed

4 files changed

+247
-4
lines changed
Lines changed: 93 additions & 0 deletions
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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public class SQLPreparedStatement extends SQLStatement implements PreparedStatem
4141
private final int autoGeneratedKeys;
4242
private List<Map<Integer, Object>> batchParameters = new ArrayList<>();
4343
private ResultSetMetaData resultSetMetaData;
44+
private ParameterMetaData parameterMetaData;
4445

4546
public SQLPreparedStatement(SQLConnection connection, String sql, int autoGeneratedKeys) throws SQLException {
4647
super(connection);
@@ -332,10 +333,11 @@ public ResultSetMetaData getMetaData() throws SQLException {
332333
}
333334

334335
private void fetchMetaData() throws SQLException {
335-
SQLPreparedHolder prepare = connection.prepare(0, sql);
336-
if (!prepare.getResultMetadata().isEmpty()) {
337-
resultSetMetaData = new SQLResultSetMetaData(prepare.getResultMetadata(), connection.isReadOnly());
336+
SQLPreparedHolder preparedHolder = connection.prepare(0, sql);
337+
if (!preparedHolder.getResultMetadata().isEmpty()) {
338+
resultSetMetaData = new SQLResultSetMetaData(preparedHolder.getResultMetadata(), connection.isReadOnly());
338339
}
340+
parameterMetaData = new SQLParameterMetaData(preparedHolder.getParamsMetadata());
339341
}
340342

341343
@Override
@@ -345,7 +347,11 @@ public void setURL(int parameterIndex, URL parameterValue) throws SQLException {
345347

346348
@Override
347349
public ParameterMetaData getParameterMetaData() throws SQLException {
348-
return null;
350+
checkNotClosed();
351+
if (parameterMetaData == null) {
352+
fetchMetaData();
353+
}
354+
return parameterMetaData;
349355
}
350356

351357
@Override
Lines changed: 125 additions & 0 deletions
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.ServerVersion;
11+
import org.tarantool.TarantoolTestHelper;
12+
import org.tarantool.util.SQLStates;
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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import java.sql.Connection;
3838
import java.sql.DriverManager;
3939
import java.sql.JDBCType;
40+
import java.sql.ParameterMetaData;
4041
import java.sql.PreparedStatement;
4142
import java.sql.ResultSet;
4243
import java.sql.ResultSetMetaData;
@@ -782,6 +783,24 @@ public void testUnsupportedPreparedStatement() throws SQLException {
782783
assertThrows(SQLException.class, () -> prep.getMetaData());
783784
}
784785

786+
@Test
787+
public void testPreparedParameterMetaData() throws SQLException {
788+
assumeMinimalServerVersion(testHelper.getInstanceVersion(), ServerVersion.V_2_3);
789+
prep = conn.prepareStatement("SELECT val FROM test WHERE id = ? AND val = ?");
790+
ParameterMetaData parameterMetaData = prep.getParameterMetaData();
791+
assertNotNull(parameterMetaData);
792+
assertEquals(2, parameterMetaData.getParameterCount());
793+
}
794+
795+
@Test
796+
public void testEmptyParameterMetaData() throws SQLException {
797+
assumeMinimalServerVersion(testHelper.getInstanceVersion(), ServerVersion.V_2_3);
798+
prep = conn.prepareStatement("SELECT * FROM test");
799+
ParameterMetaData parameterMetaData = prep.getParameterMetaData();
800+
assertNotNull(parameterMetaData);
801+
assertEquals(0, parameterMetaData.getParameterCount());
802+
}
803+
785804
private List<?> consoleSelect(Object key) {
786805
List<?> list = testHelper.evaluate(TestUtils.toLuaSelect("TEST", key));
787806
return list == null ? Collections.emptyList() : (List<?>) list.get(0);

0 commit comments

Comments
 (0)