Changeset: 95cf74c7f51c for MonetDB URL: http://dev.monetdb.org/hg/MonetDB?cmd=changeset;node=95cf74c7f51c Modified Files: java/ChangeLog.Jun2016 java/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java java/tests/Test_Rmetadata.java sql/jdbc/tests/Tests/Test_Rmetadata.stable.out Branch: Jun2016 Log Message:
Fixed resource leak in ResultSetMetaData. It created and cached a ResultSet object for each column but never closed the ResultSet objects. diffs (truncated from 496 to 300 lines): diff --git a/java/ChangeLog.Jun2016 b/java/ChangeLog.Jun2016 --- a/java/ChangeLog.Jun2016 +++ b/java/ChangeLog.Jun2016 @@ -1,6 +1,10 @@ # ChangeLog file for java # This file is updated with Maddlog +* Thu Apr 21 2016 Martin van Dinther <martin.van.dint...@monetdbsolutions.com> +- Fixed resource leak in ResultSetMetaData. It created and cached a ResultSet + object for each column but never closed the ResultSet objects. + * Thu Mar 31 2016 Martin van Dinther <martin.van.dint...@monetdbsolutions.com> - Corrected DatabaseMetaData methods which accept a catalog filter argument. Those methods will now filter the results on the specified catalog name, diff --git a/java/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java b/java/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java --- a/java/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java +++ b/java/src/main/java/nl/cwi/monetdb/jdbc/MonetResultSet.java @@ -21,6 +21,7 @@ import java.net.URL; import java.sql.Array; import java.sql.Blob; import java.sql.Clob; +import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.Ref; @@ -683,7 +684,7 @@ public class MonetResultSet extends Mone * @throws SQLException if there is no such column */ @Override - public boolean getBoolean(int columnIndex) throws SQLException{ + public boolean getBoolean(int columnIndex) throws SQLException { switch (getJavaType(types[columnIndex - 1])) { case Types.TINYINT: case Types.SMALLINT: @@ -1081,11 +1082,63 @@ public class MonetResultSet extends Mone public ResultSetMetaData getMetaData() { // return inner class which implements the ResultSetMetaData interface return new rsmdw() { - // for the more expensive methods, we provide a simple cache - // for the most expensive part; getting the ResultSet which - // contains the data + // for the more expensive methods (getPrecision(), getScale(), isNullable()), we provide a simple cache + // caches to store precision, scale and isNullable values from getColumns() + private boolean[] _is_fetched = new boolean[columns.length +1]; + private int[] _precision = new int[columns.length +1]; + private int[] _scale = new int[columns.length +1]; + private int[] _isNullable = new int[columns.length +1]; + private Connection conn = null; private DatabaseMetaData dbmd = null; - private ResultSet[] colrs = new ResultSet[columns.length]; + + /** + * A private method to fetch the precision, scale and isNuallble value for a fully qualified column. + * As md.getColumns() is an expensive method we call it only once per column + * and cache the precision, scale and isNullable values in the above array chaches. + * Also we only call md.getColumns() when we have a non empty schema name and table name and column name. + */ + private void fetchColumnInfo(int column) throws SQLException + { + if (column <= 0 || column > columns.length) + throw new SQLException("No such column " + column, "M1M05"); + + _is_fetched[column] = true; + _precision[column] = 0; + _scale[column] = 0; + _isNullable[column] = columnNullableUnknown; + + // we can only call dbmd.getColumns() when we have a specific schema name and table name and column name + String schName = getSchemaName(column); + if (schName != null && !"".equals(schName)) { + String tblName = getTableName(column); + if (tblName != null && !"".equals(tblName)) { + String colName = getColumnName(column); + if (colName != null && !"".equals(colName)) { + if (conn == null) { + // first time, get a Connection object and cache it for all next columns + conn = getStatement().getConnection(); + } + if (conn != null && dbmd == null) { + // first time, get a MetaData object and cache it for all next columns + dbmd = conn.getMetaData(); + } + if (dbmd != null) { + // for precision, scale and isNullable we query the information from data dictionary + ResultSet colInfo = dbmd.getColumns(null, schName, tblName, colName); + if (colInfo != null) { + // we expect exactly one row in the resultset + if (colInfo.next()) { + _precision[column] = colInfo.getInt(7); // col 7 is "COLUMN_SIZE" + _scale[column] = colInfo.getInt(9); // col 9 is "DECIMAL_DIGITS" + _isNullable[column] = colInfo.getInt(11); // col 11 is "NULLABLE" + } + colInfo.close(); // close the resultset to release resources + } + } + } + } + } + } /** * Returns the number of columns in this ResultSet object. @@ -1098,8 +1151,7 @@ public class MonetResultSet extends Mone } /** - * Indicates whether the designated column is automatically - * numbered, thus read-only. + * Indicates whether the designated column is automatically numbered. * * @param column the first column is 1, the second is 2, ... * @return true if so; false otherwise @@ -1118,15 +1170,21 @@ public class MonetResultSet extends Mone } /** - * Indicates whether a column's case matters. This holds for all - * columns in MonetDB resultsets since the mapping is done case - * insensitive, therefore this method will always return false. + * Indicates whether a column's case matters. * * @param column the first column is 1, the second is 2, ... - * @returns false + * @returns true for all character string columns else false */ @Override - public boolean isCaseSensitive(int column) { + public boolean isCaseSensitive(int column) throws SQLException { + switch (getColumnType(column)) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.CLOB: + return true; + } + return false; } @@ -1290,19 +1348,62 @@ public class MonetResultSet extends Mone */ @Override public int getPrecision(int column) throws SQLException { - int precision = 0; - try { - ResultSet col = getColumnResultSet(column); - - // the result has either zero or one results, as the - // schema, table and column should be unique... - if (col.next()) - precision = col.getInt("COLUMN_SIZE"); - } catch (NullPointerException npe) { - /* do nothing */ + if (_is_fetched[column] != true) { + fetchColumnInfo(column); } - - return precision; + if (_precision[column] == 0) { + // apparently no precision could be fetched + // use columnDisplaySize() value for variable length data types + switch (getColumnType(column)) { + case Types.CHAR: + case Types.VARCHAR: + case Types.LONGVARCHAR: // MonetDB doesn't use type LONGVARCHAR, it's here for completeness + case Types.CLOB: + case Types.BLOB: + case Types.NUMERIC: + case Types.DECIMAL: + _precision[column] = getColumnDisplaySize(column); + break; + case Types.TINYINT: + _precision[column] = 3; + break; + case Types.SMALLINT: + _precision[column] = 5; + break; + case Types.INTEGER: + _precision[column] = 10; + break; + case Types.BIGINT: + _precision[column] = 19; + break; + case Types.REAL: + _precision[column] = 7; + break; + case Types.FLOAT: + case Types.DOUBLE: + _precision[column] = 15; + break; + case Types.BIT: // MonetDB doesn't use type BIT, it's here for completeness + _precision[column] = 1; + break; + case Types.BOOLEAN: + _precision[column] = 5; + break; + case Types.DATE: + _precision[column] = 10; + break; + case Types.TIME: + _precision[column] = 8; + break; + case Types.TIMESTAMP: + _precision[column] = 19; + break; + default: + _precision[column] = 30; + break; + } + } + return _precision[column]; } /** @@ -1317,19 +1418,10 @@ public class MonetResultSet extends Mone */ @Override public int getScale(int column) throws SQLException { - int scale = 0; - try { - ResultSet col = getColumnResultSet(column); - - // the result has either zero or one results, as the - // schema, table and column should be unique... - if (col.next()) - scale = col.getInt("DECIMAL_DIGITS"); - } catch (NullPointerException npe) { - /* do nothing */ + if (_is_fetched[column] != true) { + fetchColumnInfo(column); } - - return scale; + return _scale[column]; } /** @@ -1339,30 +1431,20 @@ public class MonetResultSet extends Mone * an SQL query. * * @param column the first column is 1, the second is 2, ... - * @return scale + * @return the nullability status of the given column; one of columnNoNulls, columnNullable or columnNullableUnknown * @throws SQLException if a database access error occurs */ @Override public int isNullable(int column) throws SQLException { - int ret = columnNullableUnknown; - try { - ResultSet col = getColumnResultSet(column); - - // the result has either zero or one results, as the - // schema, table and column should be unique... - if (col.next()) - ret = col.getInt("NULLABLE"); - } catch (NullPointerException npe) { - /* do nothing */ + if (_is_fetched[column] != true) { + fetchColumnInfo(column); } - - return ret; + return _isNullable[column]; } /** * Gets the designated column's table's catalog name. - * Because MonetDB handles only one catalog (dbfarm) at a - * time, the current one is the one we deal with here. + * MonetDB does not support the catalog naming concept as in: catalog.schema.table naming scheme * * @param column the first column is 1, the second is 2, ... * @return the name of the catalog for the table in which the given @@ -1370,11 +1452,8 @@ public class MonetResultSet extends Mone */ @Override public String getCatalogName(int column) throws SQLException { - if (getTableName(column) != "") { - return getStatement().getConnection().getCatalog(); - } else { - return ""; - } + return null; // MonetDB does NOT support catalogs + } /** @@ -1432,17 +1511,22 @@ public class MonetResultSet extends Mone @Override public String getColumnClassName(int column) throws SQLException { try { - Class type; - Map map = getStatement().getConnection().getTypeMap(); - if (map.containsKey(types[column - 1])) { - type = (Class)map.get(types[column - 1]); - } else { - type = getClassForType(getJavaType(types[column - 1])); + if (conn == null) { + // first time, get a Connection object and cache it for all next columns + conn = getStatement().getConnection(); _______________________________________________ checkin-list mailing list checkin-list@monetdb.org https://www.monetdb.org/mailman/listinfo/checkin-list