From 325eacb1ab9ef83213143508ae9616e75eeecb71 Mon Sep 17 00:00:00 2001 From: Amit Mittal Date: Fri, 20 Dec 2024 13:22:14 +0000 Subject: [PATCH] Fuller implementation of ParameterMetadata --- src/jni/duckdb_java.cpp | 40 ++++++++++--- .../org/duckdb/DuckDBParameterMetaData.java | 60 ++++++++++++++----- .../org/duckdb/DuckDBPreparedStatement.java | 2 +- .../org/duckdb/DuckDBResultSetMetaData.java | 53 +++++++++++++++- src/test/java/org/duckdb/TestDuckDBJDBC.java | 32 ++++++++++ 5 files changed, 161 insertions(+), 26 deletions(-) diff --git a/src/jni/duckdb_java.cpp b/src/jni/duckdb_java.cpp index ef051abe..10b8d183 100644 --- a/src/jni/duckdb_java.cpp +++ b/src/jni/duckdb_java.cpp @@ -269,9 +269,9 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { J_DuckResultSetMeta = (jclass)env->NewGlobalRef(tmpLocalRef); env->DeleteLocalRef(tmpLocalRef); - J_DuckResultSetMeta_init = - env->GetMethodID(J_DuckResultSetMeta, "", - "(II[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V"); + J_DuckResultSetMeta_init = env->GetMethodID(J_DuckResultSetMeta, "", + "(II[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;Ljava/" + "lang/String;[Ljava/lang/String;[Ljava/lang/String;)V"); tmpLocalRef = env->FindClass("org/duckdb/DuckDBVector"); J_DuckVector = (jclass)env->NewGlobalRef(tmpLocalRef); @@ -782,7 +782,8 @@ static std::string type_to_jduckdb_type(LogicalType logical_type) { } static jobject build_meta(JNIEnv *env, size_t column_count, size_t n_param, const duckdb::vector &names, - const duckdb::vector &types, StatementProperties properties) { + const duckdb::vector &types, StatementProperties properties, + const duckdb::vector ¶m_types) { auto name_array = env->NewObjectArray(column_count, J_String, nullptr); auto type_array = env->NewObjectArray(column_count, J_String, nullptr); auto type_detail_array = env->NewObjectArray(column_count, J_String, nullptr); @@ -802,10 +803,26 @@ static jobject build_meta(JNIEnv *env, size_t column_count, size_t n_param, cons env->NewStringUTF(type_to_jduckdb_type(types[col_idx]).c_str())); } + auto param_type_array = env->NewObjectArray(n_param, J_String, nullptr); + auto param_type_detail_array = env->NewObjectArray(n_param, J_String, nullptr); + + for (idx_t param_idx = 0; param_idx < n_param; param_idx++) { + std::string param_name; + if (param_types[param_idx].id() == LogicalTypeId::ENUM) { + param_name = "ENUM"; + } else { + param_name = param_types[param_idx].ToString(); + } + + env->SetObjectArrayElement(param_type_array, param_idx, env->NewStringUTF(param_name.c_str())); + env->SetObjectArrayElement(param_type_detail_array, param_idx, + env->NewStringUTF(type_to_jduckdb_type(param_types[param_idx]).c_str())); + } + auto return_type = env->NewStringUTF(StatementReturnTypeToString(properties.return_type).c_str()); return env->NewObject(J_DuckResultSetMeta, J_DuckResultSetMeta_init, n_param, column_count, name_array, type_array, - type_detail_array, return_type); + type_detail_array, return_type, param_type_array, param_type_detail_array); } jobject _duckdb_jdbc_query_result_meta(JNIEnv *env, jclass, jobject res_ref_buf) { @@ -815,9 +832,11 @@ jobject _duckdb_jdbc_query_result_meta(JNIEnv *env, jclass, jobject res_ref_buf) } auto &result = res_ref->res; - auto n_param = -1; // no params now + auto n_param = 0; // no params now + duckdb::vector param_types(n_param); - return build_meta(env, result->ColumnCount(), n_param, result->names, result->types, result->properties); + return build_meta(env, result->ColumnCount(), n_param, result->names, result->types, result->properties, + param_types); } jobject _duckdb_jdbc_prepared_statement_meta(JNIEnv *env, jclass, jobject stmt_ref_buf) { @@ -829,9 +848,14 @@ jobject _duckdb_jdbc_prepared_statement_meta(JNIEnv *env, jclass, jobject stmt_r auto &stmt = stmt_ref->stmt; auto n_param = stmt->named_param_map.size(); + auto expected_parameter_types = stmt->GetExpectedParameterTypes(); + duckdb::vector param_types(n_param); + for (auto &it : stmt->named_param_map) { + param_types[it.second - 1] = expected_parameter_types[it.first]; + } return build_meta(env, stmt->ColumnCount(), n_param, stmt->GetNames(), stmt->GetTypes(), - stmt->GetStatementProperties()); + stmt->GetStatementProperties(), param_types); } jobject ProcessVector(JNIEnv *env, Connection *conn_ref, Vector &vec, idx_t row_count); diff --git a/src/main/java/org/duckdb/DuckDBParameterMetaData.java b/src/main/java/org/duckdb/DuckDBParameterMetaData.java index 6d55226f..4aed69a1 100644 --- a/src/main/java/org/duckdb/DuckDBParameterMetaData.java +++ b/src/main/java/org/duckdb/DuckDBParameterMetaData.java @@ -2,13 +2,20 @@ import java.sql.ParameterMetaData; import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; public class DuckDBParameterMetaData implements ParameterMetaData { - private DuckDBResultSetMetaData meta; - public DuckDBParameterMetaData(DuckDBResultSetMetaData meta) { - this.meta = meta; + private int param_count; + private DuckDBColumnType[] param_types; + private DuckDBColumnTypeMetaData[] param_types_meta; + private String[] param_types_string; + + public DuckDBParameterMetaData(int param_count, String[] param_types_string, DuckDBColumnType[] param_types, + DuckDBColumnTypeMetaData[] param_types_meta) { + this.param_count = param_count; + this.param_types_string = param_types_string; + this.param_types = param_types; + this.param_types_meta = param_types_meta; } @Override @@ -23,7 +30,7 @@ public boolean isWrapperFor(Class iface) { @Override public int getParameterCount() throws SQLException { - return meta.param_count; + return param_count; } @Override @@ -33,35 +40,60 @@ public int isNullable(int param) throws SQLException { @Override public boolean isSigned(int param) throws SQLException { - return true; + if (param > param_count) { + throw new SQLException("Parameter index out of bounds"); + } + return DuckDBResultSetMetaData.is_signed(param_types[param - 1]); } @Override public int getPrecision(int param) throws SQLException { - throw new SQLFeatureNotSupportedException("getPrecision"); + if (param > param_count) { + throw new SQLException("Parameter index out of bounds"); + } + DuckDBColumnTypeMetaData typeMetaData = param_types_meta[param - 1]; + if (typeMetaData == null) { + return 0; + } + + return typeMetaData.width; } @Override public int getScale(int param) throws SQLException { - throw new SQLFeatureNotSupportedException("getScale"); + if (param > param_count) { + throw new SQLException("Parameter index out of bounds"); + } + DuckDBColumnTypeMetaData typeMetaData = param_types_meta[param - 1]; + if (typeMetaData == null) { + return 0; + } + + return typeMetaData.scale; } @Override public int getParameterType(int param) throws SQLException { - // TODO Auto-generated method stub - return 0; + if (param > param_count) { + throw new SQLException("Parameter index out of bounds"); + } + return DuckDBResultSetMetaData.type_to_int(param_types[param - 1]); } @Override public String getParameterTypeName(int param) throws SQLException { - // TODO Auto-generated method stub - return null; + if (param > param_count) { + throw new SQLException("Parameter index out of bounds"); + } + return param_types_string[param - 1]; } @Override public String getParameterClassName(int param) throws SQLException { - // TODO Auto-generated method stub - return null; + if (param > param_count) { + throw new SQLException("Parameter index out of bounds"); + } + return DuckDBResultSetMetaData.type_to_javaString(param_types[param - 1]); } @Override diff --git a/src/main/java/org/duckdb/DuckDBPreparedStatement.java b/src/main/java/org/duckdb/DuckDBPreparedStatement.java index 1b078f03..b46a4760 100644 --- a/src/main/java/org/duckdb/DuckDBPreparedStatement.java +++ b/src/main/java/org/duckdb/DuckDBPreparedStatement.java @@ -233,7 +233,7 @@ public ParameterMetaData getParameterMetaData() throws SQLException { if (stmt_ref == null) { throw new SQLException("Prepare something first"); } - return new DuckDBParameterMetaData(meta); + return meta.param_meta; } @Override diff --git a/src/main/java/org/duckdb/DuckDBResultSetMetaData.java b/src/main/java/org/duckdb/DuckDBResultSetMetaData.java index d9105ab3..d1a7e9b6 100644 --- a/src/main/java/org/duckdb/DuckDBResultSetMetaData.java +++ b/src/main/java/org/duckdb/DuckDBResultSetMetaData.java @@ -2,6 +2,7 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.sql.ParameterMetaData; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Timestamp; @@ -17,7 +18,9 @@ public class DuckDBResultSetMetaData implements ResultSetMetaData { public DuckDBResultSetMetaData(int param_count, int column_count, String[] column_names, - String[] column_types_string, String[] column_types_details, String return_type) { + String[] column_types_string, String[] column_types_details, String return_type, + String[] param_types_string, String[] param_types_details) { + this.param_count = param_count; this.column_count = column_count; this.column_names = column_names; @@ -41,6 +44,26 @@ public DuckDBResultSetMetaData(int param_count, int column_count, String[] colum } } this.column_types_meta = column_types_meta.toArray(new DuckDBColumnTypeMetaData[column_count]); + + ArrayList param_types_al = new ArrayList(param_count); + ArrayList param_types_meta_al = new ArrayList(param_count); + + for (String param_type_string : param_types_string) { + param_types_al.add(TypeNameToType(param_type_string)); + } + DuckDBColumnType[] param_types = param_types_al.toArray(new DuckDBColumnType[param_count]); + + for (String param_type_detail : param_types_details) { + if (TypeNameToType(param_type_detail) == DuckDBColumnType.DECIMAL) { + param_types_meta_al.add(DuckDBColumnTypeMetaData.parseColumnTypeMetadata(param_type_detail)); + } else { + param_types_meta_al.add(null); + } + } + DuckDBColumnTypeMetaData[] param_types_meta = + param_types_meta_al.toArray(new DuckDBColumnTypeMetaData[param_count]); + + this.param_meta = new DuckDBParameterMetaData(param_count, param_types_string, param_types, param_types_meta); } public static DuckDBColumnType TypeNameToType(String type_name) { @@ -75,6 +98,7 @@ public static DuckDBColumnType TypeNameToType(String type_name) { protected DuckDBColumnType[] column_types; protected DuckDBColumnTypeMetaData[] column_types_meta; protected final StatementReturnType return_type; + protected ParameterMetaData param_meta; public StatementReturnType getReturnType() { return return_type; @@ -150,7 +174,14 @@ public int getColumnType(int column) throws SQLException { } public String getColumnClassName(int column) throws SQLException { - switch (column_types[column - 1]) { + if (column > column_count) { + throw new SQLException("Column index out of bounds"); + } + return type_to_javaString(column_types[column - 1]); + } + + protected static String type_to_javaString(DuckDBColumnType type) { + switch (type) { case BOOLEAN: return Boolean.class.getName(); case TINYINT: @@ -249,7 +280,23 @@ public boolean isCurrency(int column) throws SQLException { } public boolean isSigned(int column) throws SQLException { - return false; + if (column > column_count) { + throw new SQLException("Column index out of bounds"); + } + return is_signed(column_types[column - 1]); + } + + protected static boolean is_signed(DuckDBColumnType type) { + switch (type) { + case UTINYINT: + case USMALLINT: + case UINTEGER: + case UBIGINT: + case UHUGEINT: + return false; + default: + return true; + } } public int getColumnDisplaySize(int column) throws SQLException { diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index 1908d89e..1770f66d 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -1032,6 +1032,38 @@ public static void test_multiple_statements_exception() throws Exception { } } + public static void test_parameter_metadata() throws Exception { + Connection conn = DriverManager.getConnection(JDBC_URL); + Statement stmt = conn.createStatement(); + stmt.execute("CREATE TABLE q (id DECIMAL(3,0), dec16 DECIMAL(4,1), dec32 DECIMAL(9,4), dec64 DECIMAL(18,7), " + + "dec128 DECIMAL(38,10), int INTEGER, uint UINTEGER)"); + PreparedStatement ps1 = conn.prepareStatement( + "INSERT INTO q (id, dec16, dec32, dec64, dec128, int, uint) VALUES (?, ?, ?, ?, ?, ?, ?)"); + ParameterMetaData meta = ps1.getParameterMetaData(); + assertEquals(3, meta.getPrecision(1)); + assertEquals(0, meta.getScale(1)); + assertEquals(4, meta.getPrecision(2)); + assertEquals(1, meta.getScale(2)); + assertEquals(9, meta.getPrecision(3)); + assertEquals(4, meta.getScale(3)); + assertEquals(18, meta.getPrecision(4)); + assertEquals(7, meta.getScale(4)); + assertEquals(38, meta.getPrecision(5)); + assertEquals(10, meta.getScale(5)); + assertEquals(0, meta.getPrecision(6)); + assertEquals(0, meta.getScale(6)); + assertEquals(0, meta.getPrecision(7)); + assertEquals(0, meta.getScale(7)); + + assertTrue(meta.isSigned(5)); + assertTrue(meta.isSigned(6)); + assertFalse(meta.isSigned(7)); + + assertTrue(BigDecimal.class.getName().equals(meta.getParameterClassName(1))); + assertTrue(Integer.class.getName().equals(meta.getParameterClassName(6))); + assertTrue(Long.class.getName().equals(meta.getParameterClassName(7))); + } + public static void test_bigdecimal() throws Exception { Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement();