Mercurial > hg > monetdb-java
changeset 719:2f42195e9c58
Improved implementation of PreparedStatement.getParameterMetaData().
The previous implementation created a new ParameterMetaData object each time this method is called which is quite costly if it is called from inside a loop.
As the ParameterMetaData is static for a PreparedStatement it is better to create it once,
cache it in the PreparedStatement object and return the cached object for next calls to PreparedStatement.getParameterMetaData().
We also now create dedicated 1-based meta data arrays, such that we no longer have to call getParamIdx(param) in methods of ParameterMetaData.
author | Martin van Dinther <martin.van.dinther@monetdbsolutions.com> |
---|---|
date | Thu, 19 Jan 2023 16:46:44 +0100 (2023-01-19) |
parents | 7138bbc6952e |
children | 99baab703566 |
files | src/main/java/org/monetdb/jdbc/MonetParameterMetaData.java src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java tests/JDBC_API_Tester.java |
diffstat | 3 files changed, 416 insertions(+), 279 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/src/main/java/org/monetdb/jdbc/MonetParameterMetaData.java @@ -0,0 +1,376 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * Copyright 1997 - July 2008 CWI, August 2008 - 2023 MonetDB B.V. + */ + +package org.monetdb.jdbc; + +import java.sql.ParameterMetaData; +import java.sql.SQLDataException; +import java.sql.SQLException; +import java.sql.Types; + +/** + *<pre> + * A {@link ParameterMetaData} suitable for the MonetDB database. + * + * An object that can be used to get information about the types and properties + * for each parameter marker in a PreparedStatement or CallableStatement object. + *</pre> + * + * @author Martin van Dinther + * @version 1.0 + */ +final class MonetParameterMetaData + extends MonetWrapper + implements ParameterMetaData +{ + /** The parental Connection object */ + private final MonetConnection conn; + + /** The number of parameters, it can be zero !! */ + private final int paramCount; + + /** The MonetDB type names of the parameters in the PreparedStatement */ + private final String[] monetdbTypes; + /** The JDBC SQL type codes of the parameters in the PreparedStatement */ + private final int[] JdbcSQLTypes; + /** The precisions of the parameters in the PreparedStatement */ + private final int[] precisions; + /** The scales of the parameters in the PreparedStatement */ + private final int[] scales; + + /** + * Constructor backed by the given connection and metadata arrays. + * It is used by MonetPreparedStatement. + * + * @param connection the parent connection + * @param paramCount the number of parameters, it can be zero !! + * @param types the MonetDB type names + * @param jdbcTypes the JDBC SQL type codes + * @param precisions the precision for each parameter + * @param scales the scale for each parameter + * @throws IllegalArgumentException if called with null for one of the arguments + */ + MonetParameterMetaData( + final MonetConnection connection, + final int paramcount, + final String[] types, + final int[] jdbcTypes, + final int[] precisions, + final int[] scales) + throws IllegalArgumentException + { + if (connection == null) { + throw new IllegalArgumentException("Connection may not be null!"); + } + if (types == null) { + throw new IllegalArgumentException("MonetDB Types may not be null!"); + } + if (jdbcTypes == null) { + throw new IllegalArgumentException("JDBC Types may not be null!"); + } + if (precisions == null) { + throw new IllegalArgumentException("Precisions may not be null!"); + } + if (scales == null) { + throw new IllegalArgumentException("Scales may not be null!"); + } + if (types.length != precisions.length || types.length != (paramcount +1)) { + throw new IllegalArgumentException("Inconsistent Parameters metadata"); + } + this.conn = connection; + this.paramCount = paramcount; + this.monetdbTypes = types; + this.JdbcSQLTypes = jdbcTypes; + this.precisions = precisions; + this.scales = scales; + } + + /** + * Retrieves the number of parameters in the PreparedStatement object + * for which this ParameterMetaData object contains information. + * + * @return the number of parameters + */ + @Override + public int getParameterCount() { + return paramCount; + } + + /** + * Retrieves whether null values are allowed in the + * designated parameter. + * + * This is currently always unknown for MonetDB/SQL. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return the nullability status of the given parameter; + * one of ParameterMetaData.parameterNoNulls, + * ParameterMetaData.parameterNullable, or + * ParameterMetaData.parameterNullableUnknown + */ + @Override + public int isNullable(final int param) throws SQLException { + checkParameterIndexValidity(param); + return ParameterMetaData.parameterNullableUnknown; + } + + /** + * Retrieves whether values for the designated parameter can + * be signed numbers. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return true if so; false otherwise + * @throws SQLException if a database access error occurs + */ + @Override + public boolean isSigned(final int param) throws SQLException { + // we can hardcode this, based on the parameter type + switch (getParameterType(param)) { + case Types.TINYINT: + case Types.SMALLINT: + case Types.INTEGER: + case Types.REAL: + case Types.FLOAT: + case Types.DOUBLE: + case Types.DECIMAL: + case Types.NUMERIC: + return true; + case Types.BIGINT: + final String monettype = getParameterTypeName(param); + if (monettype != null && monettype.length() == 3) { + // data of type oid or ptr is not signed + if ("oid".equals(monettype) + || "ptr".equals(monettype)) + return false; + } + return true; + // All other types should return false + // case Types.BOOLEAN: + // case Types.DATE: // can year be negative? + // case Types.TIME: // can time be negative? + // case Types.TIME_WITH_TIMEZONE: + // case Types.TIMESTAMP: // can year be negative? + // case Types.TIMESTAMP_WITH_TIMEZONE: + default: + return false; + } + } + + /** + * Retrieves the designated parameter's specified column size. + * The returned value represents the maximum column size for + * the given parameter. + * For numeric data, this is the maximum precision. + * For character data, this is the length in characters. + * For datetime datatypes, this is the length in characters + * of the String representation (assuming the maximum allowed + * precision of the fractional seconds component). + * For binary data, this is the length in bytes. + * For the ROWID datatype, this is the length in bytes. + * 0 is returned for data types where the column size is not applicable. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return precision + * @throws SQLException if a database access error occurs + */ + @Override + public int getPrecision(final int param) throws SQLException { + switch (getParameterType(param)) { + case Types.BIGINT: + return 19; + case Types.INTEGER: + return 10; + case Types.SMALLINT: + return 5; + case Types.TINYINT: + return 3; + case Types.REAL: + return 7; + case Types.FLOAT: + case Types.DOUBLE: + return 15; + case Types.DECIMAL: + case Types.NUMERIC: + // these data types have a variable precision (max precision is 38) + try { + return precisions[param]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidParameterIndexException(param); + } + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.CLOB: + // these data types have a variable length + try { + return precisions[param]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidParameterIndexException(param); + } + case Types.BINARY: + case Types.VARBINARY: + case Types.BLOB: + // these data types have a variable length + // It expect number of bytes, not number of hex chars + try { + return precisions[param]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidParameterIndexException(param); + } + case Types.DATE: + return 10; // 2020-10-08 + case Types.TIME: + return 15; // 21:51:34.399753 + case Types.TIME_WITH_TIMEZONE: + return 21; // 21:51:34.399753+02:00 + case Types.TIMESTAMP: + return 26; // 2020-10-08 21:51:34.399753 + case Types.TIMESTAMP_WITH_TIMEZONE: + return 32; // 2020-10-08 21:51:34.399753+02:00 + case Types.BOOLEAN: + return 1; + default: + // All other types should return 0 + return 0; + } + } + + /** + * Retrieves the designated parameter's number of digits to + * right of the decimal point. + * 0 is returned for data types where the scale is not applicable. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return scale + * @throws SQLException if a database access error occurs + */ + @Override + public int getScale(final int param) throws SQLException { + checkParameterIndexValidity(param); + try { + return scales[param]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidParameterIndexException(param); + } + } + + /** + * Retrieves the designated parameter's SQL type. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return SQL type from java.sql.Types + * @throws SQLException if a database access error occurs + */ + @Override + public int getParameterType(final int param) throws SQLException { + checkParameterIndexValidity(param); + try { + return JdbcSQLTypes[param]; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidParameterIndexException(param); + } + } + + /** + * Retrieves the designated parameter's database-specific type name. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return type the name used by the database. If the + * parameter type is a user-defined type, then a + * fully-qualified type name is returned. + * @throws SQLException if a database access error occurs + */ + @Override + public String getParameterTypeName(final int param) throws SQLException { + checkParameterIndexValidity(param); + try { + final String monettype = monetdbTypes[param]; + if (monettype.endsWith("_interval")) { + /* convert the interval type names to valid SQL data type names */ + if ("day_interval".equals(monettype)) + return "interval day"; + if ("month_interval".equals(monettype)) + return "interval month"; + if ("sec_interval".equals(monettype)) + return "interval second"; + } + return monettype; + } catch (IndexOutOfBoundsException e) { + throw newSQLInvalidParameterIndexException(param); + } + } + + /** + * Retrieves the fully-qualified name of the Java class whose instances + * should be passed to the method PreparedStatement.setObject. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return the fully-qualified name of the class in the Java + * programming language that would be used by the + * method PreparedStatement.setObject to set the + * value in the specified parameter. This is the + * class name used for custom mapping. + * @throws SQLException if a database access error occurs + */ + @Override + public String getParameterClassName(final int param) throws SQLException { + final String MonetDBType = getParameterTypeName(param); + final java.util.Map<String,Class<?>> map = conn.getTypeMap(); + final Class<?> type; + if (map != null && map.containsKey(MonetDBType)) { + type = (Class)map.get(MonetDBType); + } else { + type = MonetDriver.getClassForType(getParameterType(param)); + } + if (type != null) { + return type.getCanonicalName(); + } + throw new SQLException("parameter type mapping null: " + MonetDBType, "M0M03"); + } + + /** + * Retrieves the designated parameter's mode. + * For MonetDB/SQL we currently only support INput parameters. + * + * @param param - the first parameter is 1, the second is 2, ... + * @return mode of the parameter; one of + * ParameterMetaData.parameterModeIn, + * ParameterMetaData.parameterModeOut, + * ParameterMetaData.parameterModeInOut or + * ParameterMetaData.parameterModeUnknown. + */ + @Override + public int getParameterMode(final int param) throws SQLException { + checkParameterIndexValidity(param); + return ParameterMetaData.parameterModeIn; + } + + + /** + * A private utility method to check validity of parameter index number + * + * @param param - the first parameter is 1, the second is 2, ... + * @throws SQLDataException when invalid parameter index number + */ + private final void checkParameterIndexValidity(final int param) throws SQLDataException { + if (param < 1 || param > paramCount) + throw newSQLInvalidParameterIndexException(param); + } + + /** + * Small helper method that formats the "Invalid Parameter Index number ..." + * message and creates a new SQLDataException object whose SQLState is set + * to "22010": invalid indicator parameter value. + * + * @param paramIdx the parameter index number + * @return a new created SQLDataException object with SQLState 22010 + */ + private final SQLDataException newSQLInvalidParameterIndexException(final int paramIdx) { + return new SQLDataException("Invalid Parameter Index number: " + paramIdx, "22010"); + } +}
--- a/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java +++ b/src/main/java/org/monetdb/jdbc/MonetPreparedStatement.java @@ -38,7 +38,6 @@ import java.sql.Timestamp; import java.sql.Types; import java.text.SimpleDateFormat; import java.util.Calendar; -import java.util.Map; /** *<pre> @@ -82,9 +81,14 @@ public class MonetPreparedStatement private int paramCount = 0; private final String[] paramValues; - /** A cache to reduce the number of ResultSetMetaData objects created by getMetaData() to maximum 1 per PreparedStatement */ + /** A cache to reduce the number of ResultSetMetaData objects created + * by getMetaData() to maximum 1 per PreparedStatement */ private ResultSetMetaData rsmd; + /** A cache to reduce the number of ParameterMetaData objects created + * by getParameterMetaData() to maximum 1 per PreparedStatement */ + private ParameterMetaData pmd; + /* placeholders for date/time pattern formats created once (only when needed), used multiple times */ /** Format of a timestamp with RFC822 time zone */ private SimpleDateFormat mTimestampZ; @@ -357,7 +361,8 @@ public class MonetPreparedStatement // ResultSetMetaData object once and reuse it for all next calls int rescolcount = 0; for (int i = 0; i < size; i++) { - /* when column[i] == null it is a parameter, when column[i] != null it is a result column of the prepared query */ + /* when column[i] == null it is a parameter, + when column[i] != null it is a result column of the prepared query */ if (column[i] == null) continue; rescolcount++; @@ -380,7 +385,8 @@ public class MonetPreparedStatement // now fill the arrays with only the resultset columns metadata rescolcount = 0; for (int i = 0; i < size; i++) { - /* when column[i] == null it is a parameter, when column[i] != null it is a result column of the prepared query */ + /* when column[i] == null it is a parameter, + when column[i] != null it is a result column of the prepared query */ if (column[i] == null) continue; schemas[rescolcount] = schema[i]; @@ -440,278 +446,44 @@ public class MonetPreparedStatement return rsmd; } - /* helper class for the anonymous class in getParameterMetaData */ - private abstract class pmdw extends MonetWrapper implements ParameterMetaData {} /** * Retrieves the number, types and properties of this * PreparedStatement object's parameters. * * @return a ParameterMetaData object that contains information - * about the number, types and properties of this - * PreparedStatement object's parameters + * about the number, types and properties for each parameter + * marker of this PreparedStatement object * @throws SQLException if a database access error occurs */ @Override public ParameterMetaData getParameterMetaData() throws SQLException { - return new pmdw() { - /** - * Retrieves the number of parameters in the - * PreparedStatement object for which this ParameterMetaData - * object contains information. - * - * @return the number of parameters - */ - @Override - public int getParameterCount() { - return paramCount; - } - - /** - * Retrieves whether null values are allowed in the - * designated parameter. - * - * This is currently always unknown for MonetDB/SQL. - * - * @param param the first parameter is 1, the second is 2, ... - * @return the nullability status of the given parameter; - * one of ParameterMetaData.parameterNoNulls, - * ParameterMetaData.parameterNullable, or - * ParameterMetaData.parameterNullableUnknown - */ - @Override - public int isNullable(final int param) { - return ParameterMetaData.parameterNullableUnknown; - } - - /** - * Retrieves whether values for the designated parameter can - * be signed numbers. - * - * @param param the first parameter is 1, the second is 2, ... - * @return true if so; false otherwise - * @throws SQLException if a database access error occurs - */ - @Override - public boolean isSigned(final int param) throws SQLException { - // we can hardcode this, based on the colum type - switch (getParameterType(param)) { - case Types.TINYINT: - case Types.SMALLINT: - case Types.INTEGER: - case Types.REAL: - case Types.FLOAT: - case Types.DOUBLE: - case Types.DECIMAL: - case Types.NUMERIC: - return true; - case Types.BIGINT: - final String monettype = getParameterTypeName(param); - if (monettype != null && monettype.length() == 3) { - // data of type oid or ptr is not signed - if ("oid".equals(monettype) - || "ptr".equals(monettype)) - return false; - } - return true; - // All other types should return false - // case Types.BOOLEAN: - // case Types.DATE: // can year be negative? - // case Types.TIME: // can time be negative? - // case Types.TIME_WITH_TIMEZONE: - // case Types.TIMESTAMP: // can year be negative? - // case Types.TIMESTAMP_WITH_TIMEZONE: - default: - return false; - } + if (pmd == null) { + // first use, construct the arrays with metadata and a + // ParameterMetaData object once and reuse it for all next calls + final int array_size = paramCount +1; + // create arrays for storing only the parameters meta data + final String[] types = new String[array_size]; + final int[] jdbcTypes = new int[array_size]; + final int[] precisions = new int[array_size]; + final int[] scales = new int[array_size]; + int param = 1; // parameters in JDBC start from 1 + // now fill the arrays with only the parameters metadata + for (int i = 0; i < size; i++) { + /* when column[i] == null it is a parameter, + when column[i] != null it is a result column of the prepared query */ + if (column[i] != null) + continue; + types[param] = monetdbType[i]; + jdbcTypes[param] = javaType[i]; + precisions[param] = digits[i]; + scales[param] = scale[i]; + param++; } - /** - * Retrieves the designated parameter's specified column size. - * The returned value represents the maximum column size for - * the given parameter. - * For numeric data, this is the maximum precision. - * For character data, this is the length in characters. - * For datetime datatypes, this is the length in characters - * of the String representation (assuming the maximum allowed - * precision of the fractional seconds component). - * For binary data, this is the length in bytes. - * For the ROWID datatype, this is the length in bytes. - * 0 is returned for data types where the column size is not applicable. - * - * @param param the first parameter is 1, the second is 2, ... - * @return precision - * @throws SQLException if a database access error occurs - */ - @Override - public int getPrecision(final int param) throws SQLException { - switch (getParameterType(param)) { - case Types.BIGINT: - return 19; - case Types.INTEGER: - return 10; - case Types.SMALLINT: - return 5; - case Types.TINYINT: - return 3; - case Types.REAL: - return 7; - case Types.FLOAT: - case Types.DOUBLE: - return 15; - case Types.DECIMAL: - case Types.NUMERIC: - // these data types have a variable precision (max precision is 38) - try { - return digits[getParamIdx(param)]; - } catch (IndexOutOfBoundsException e) { - throw newSQLInvalidParameterIndexException(param); - } - case Types.CHAR: - case Types.VARCHAR: - case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness - case Types.CLOB: - // these data types have a variable length - try { - return digits[getParamIdx(param)]; - } catch (IndexOutOfBoundsException e) { - throw newSQLInvalidParameterIndexException(param); - } - case Types.BINARY: - case Types.VARBINARY: - case Types.BLOB: - // these data types have a variable length - // It expect number of bytes, not number of hex chars - try { - return digits[getParamIdx(param)]; - } catch (IndexOutOfBoundsException e) { - throw newSQLInvalidParameterIndexException(param); - } - case Types.DATE: - return 10; // 2020-10-08 - case Types.TIME: - return 15; // 21:51:34.399753 - case Types.TIME_WITH_TIMEZONE: - return 21; // 21:51:34.399753+02:00 - case Types.TIMESTAMP: - return 26; // 2020-10-08 21:51:34.399753 - case Types.TIMESTAMP_WITH_TIMEZONE: - return 32; // 2020-10-08 21:51:34.399753+02:00 - case Types.BOOLEAN: - return 1; - default: - // All other types should return 0 - return 0; - } - } - - /** - * Retrieves the designated parameter's number of digits to - * right of the decimal point. - * 0 is returned for data types where the scale is not applicable. - * - * @param param the first parameter is 1, the second is 2, ... - * @return scale - * @throws SQLException if a database access error occurs - */ - @Override - public int getScale(final int param) throws SQLException { - try { - return scale[getParamIdx(param)]; - } catch (IndexOutOfBoundsException e) { - throw newSQLInvalidParameterIndexException(param); - } - } - - /** - * Retrieves the designated parameter's SQL type. - * - * @param param the first parameter is 1, the second is 2, ... - * @return SQL type from java.sql.Types - * @throws SQLException if a database access error occurs - */ - @Override - public int getParameterType(final int param) throws SQLException { - try { - return javaType[getParamIdx(param)]; - } catch (IndexOutOfBoundsException e) { - throw newSQLInvalidParameterIndexException(param); - } - } - - /** - * Retrieves the designated parameter's database-specific - * type name. - * - * @param param the first parameter is 1, the second is 2, ... - * @return type the name used by the database. If the - * parameter type is a user-defined type, then a - * fully-qualified type name is returned. - * @throws SQLException if a database access error occurs - */ - @Override - public String getParameterTypeName(final int param) throws SQLException { - try { - final String monettype = monetdbType[getParamIdx(param)]; - if (monettype.endsWith("_interval")) { - /* convert the interval type names to valid SQL data type names - */ - if ("day_interval".equals(monettype)) - return "interval day"; - if ("month_interval".equals(monettype)) - return "interval month"; - if ("sec_interval".equals(monettype)) - return "interval second"; - } - return monettype; - } catch (IndexOutOfBoundsException e) { - throw newSQLInvalidParameterIndexException(param); - } - } - - /** - * Retrieves the fully-qualified name of the Java class - * whose instances should be passed to the method - * PreparedStatement.setObject. - * - * @param param the first parameter is 1, the second is 2, ... - * @return the fully-qualified name of the class in the Java - * programming language that would be used by the - * method PreparedStatement.setObject to set the - * value in the specified parameter. This is the - * class name used for custom mapping. - * @throws SQLException if a database access error occurs - */ - @Override - public String getParameterClassName(final int param) throws SQLException { - final String MonetDBType = getParameterTypeName(param); - final Map<String,Class<?>> map = getConnection().getTypeMap(); - final Class<?> c; - if (map != null && map.containsKey(MonetDBType)) { - c = (Class)map.get(MonetDBType); - } else { - c = MonetDriver.getClassForType(getParameterType(param)); - } - if (c != null) - return c.getCanonicalName(); - throw new SQLException("column type mapping null: " + MonetDBType, "M0M03"); - } - - /** - * Retrieves the designated parameter's mode. - * For MonetDB/SQL we currently only support INput parameters. - * - * @param param - the first parameter is 1, the second is 2, ... - * @return mode of the parameter; one of - * ParameterMetaData.parameterModeIn, - * ParameterMetaData.parameterModeOut, or - * ParameterMetaData.parameterModeInOut - * ParameterMetaData.parameterModeUnknown. - */ - @Override - public int getParameterMode(final int param) { - return ParameterMetaData.parameterModeIn; - } - }; + pmd = new MonetParameterMetaData((MonetConnection) getConnection(), + paramCount, types, jdbcTypes, precisions, scales); + } + return pmd; } /** @@ -2453,6 +2225,7 @@ public class MonetPreparedStatement } clearParameters(); rsmd = null; + pmd = null; mTimestampZ = null; mTimestamp = null; mTimeZ = null; @@ -2569,16 +2342,4 @@ public class MonetPreparedStatement execStmt.append(')'); return execStmt.toString(); } - - /** - * Small helper method that formats the "Invalid Parameter Index number ..." message - * and creates a new SQLDataException object whose SQLState is set - * to "22010": invalid indicator parameter value. - * - * @param paramIdx the parameter index number - * @return a new created SQLDataException object with SQLState 22010 - */ - private static final SQLDataException newSQLInvalidParameterIndexException(final int paramIdx) { - return new SQLDataException("Invalid Parameter Index number: " + paramIdx, "22010"); - } }
--- a/tests/JDBC_API_Tester.java +++ b/tests/JDBC_API_Tester.java @@ -4419,7 +4419,7 @@ final public class JDBC_API_Tester { ParameterMetaData pmd = pstmt.getParameterMetaData(); checkIsWrapperFor("ParameterMetaData", pmd, jdbc_pkg, "ParameterMetaData"); checkIsWrapperFor("ParameterMetaData", pmd, monetdb_jdbc_pkg, "MonetPreparedStatement"); - checkIsWrapperFor("ParameterMetaData", pmd, monetdb_jdbc_pkg, "MonetPreparedStatement$pmdw"); // it is a private class of MonetPreparedStatement + checkIsWrapperFor("ParameterMetaData", pmd, monetdb_jdbc_pkg, "MonetParameterMetaData"); checkIsWrapperFor("ParameterMetaData", pmd, jdbc_pkg, "Connection"); checkIsWrapperFor("ParameterMetaData", pmd, monetdb_jdbc_pkg, "MonetConnection"); @@ -4467,7 +4467,7 @@ final public class JDBC_API_Tester { "PreparedStatement. isWrapperFor(MonetConnection) returns: false\n" + "ParameterMetaData. isWrapperFor(ParameterMetaData) returns: true Called unwrap(). Returned object is not null, so oke\n" + "ParameterMetaData. isWrapperFor(MonetPreparedStatement) returns: false\n" + - "ParameterMetaData. isWrapperFor(MonetPreparedStatement$pmdw) returns: true Called unwrap(). Returned object is not null, so oke\n" + + "ParameterMetaData. isWrapperFor(MonetParameterMetaData) returns: true Called unwrap(). Returned object is not null, so oke\n" + "ParameterMetaData. isWrapperFor(Connection) returns: false\n" + "ParameterMetaData. isWrapperFor(MonetConnection) returns: false\n" + "PrepStmt ResultSetMetaData. isWrapperFor(ResultSetMetaData) returns: true Called unwrap(). Returned object is not null, so oke\n" +