diff src/main/java/org/monetdb/jdbc/MonetResultSet.java @ 416:b3c876a0d61f

Improved performance of ResultSetMetaData methods getSchemaName(), getTableName(), getPrecision(), getScale(), isNullable() and isAutoIncrement(). Previously getSchemaName() and getTableName() would extract the schema name or the table name from a single string containing both, separated by a dot. Now this is done once at a higher level (ResultSetResponse) and the values can be accessed directly. The methods getPrecision(), getScale(), isNullable() and isAutoIncrement() used to call fetchColumnInfo() which created a MonetDatabaseMetaData object and next call MonetDatabaseMetaData.getColumns() method. However this getColumns() method queries and returns much more info than really needed for the methods in ResultSetMetaData. It is a costly (and slow) method. Hence it is now replaced with a smaller and faster custom query (based on the query from getColumns()) which runs much faster. Also the creation of a MonetDatabaseMetaData object is no longer needed and has been removed.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Wed, 20 Jan 2021 23:54:03 +0100 (2021-01-20)
parents 50e43af49d47
children 6558ab0d2547
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetResultSet.java
+++ b/src/main/java/org/monetdb/jdbc/MonetResultSet.java
@@ -61,7 +61,7 @@ import java.util.TimeZone;
  *
  * @author Fabian Groffen
  * @author Martin van Dinther
- * @version 0.9
+ * @version 1.0
  */
 public class MonetResultSet
 	extends MonetWrapper
@@ -127,7 +127,6 @@ public class MonetResultSet
 		concurrency = header.getRSConcur();
 		/* the fetchSize used for this result set is the header's cacheSize */
 		fetchSize = header.getCacheSize();
-
 		columns = header.getNames();
 		types = header.getTypes();
 		if (columns == null || types == null) {
@@ -1247,16 +1246,17 @@ public class MonetResultSet
 	public ResultSetMetaData getMetaData() throws SQLException {
 		// return inner class which implements the ResultSetMetaData interface
 		return new rsmdw() {
+			private final String[] schemas = (header != null) ? header.getSchemaNames() : null;
+			private final String[] tables = (header != null) ? header.getTableNames() : null;
+			private final MonetConnection conn = (MonetConnection)getStatement().getConnection();
 			// for the more expensive methods (getPrecision(), getScale(), isNullable(), isAutoIncrement()), we
-			// use caches to store precision, scale and isNullable values from getColumns() combined per fully qualified column.
+			// use caches to store precision, scale and isNullable values from getColumnInfo() combined per fully qualified column.
 			private final int array_size = columns.length + 1;  // add 1 as in JDBC columns start from 1 (array from 0).
 			private final boolean[] _is_fetched = new boolean[array_size];
 			private final int[] _precision	= new int[array_size];
 			private final int[] _scale	= new int[array_size];
 			private final int[] _isNullable	= new int[array_size];
 			private final boolean[] _isAutoincrement = new boolean[array_size];
-			private final Connection conn = getStatement().getConnection();
-			private DatabaseMetaData dbmd = null;	// it will be assigned at first need and reused for other columns
 
 			/**
 			 * A private utility method to check validity of column index number
@@ -1269,9 +1269,9 @@ public class MonetResultSet
 
 			/**
 			 * A private method to fetch the precision, scale, isNullable and isAutoincrement value for a fully qualified column.
-			 * As md.getColumns() is an expensive method we call it only once per column and store
+			 * As getColumnInfo() is an expensive method we call it only once per column and store
 			 * the precision, scale, isNullable and isAutoincrement values in the above array caches.
-			 * Also we only call md.getColumns() when we have a non empty schema name and table name and column name.
+			 * Also we only call getColumnInfo() when we have a non empty schema name and table name and column name.
 			 */
 			private final void fetchColumnInfo(final int column) throws SQLException
 			{
@@ -1283,14 +1283,7 @@ public class MonetResultSet
 				_isNullable[column] = columnNullableUnknown;
 				_isAutoincrement[column] = false;
 
-				if (dbmd == null) {
-					// first time usage
-					dbmd = conn.getMetaData();
-					if (dbmd == null)
-						return;
-				}
-
-				// we will only call dbmd.getColumns() when we have a specific schema name and table name and column name
+				// we will only call getColumnInfo() when we have a specific schema name, table name and column name
 				final String schName = getSchemaName(column);
 				if (schName != null && !schName.isEmpty()) {
 					final String tblName = getTableName(column);
@@ -1298,16 +1291,14 @@ public class MonetResultSet
 						final String colName = getColumnName(column);
 						if (colName != null && !colName.isEmpty()) {
 							// for precision, scale, isNullable and isAutoincrement we query the information from data dictionary
-							final ResultSet colInfo = dbmd.getColumns(null, schName, tblName, colName);
+							final ResultSet colInfo = getColumnInfo(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"
-									final String strVal = colInfo.getString(23);  // col 23 is "IS_AUTOINCREMENT"
-									if (strVal != null && "YES".equals(strVal))
-										_isAutoincrement[column] = true;
+									_precision[column] = colInfo.getInt(1);  // col 1 (was 7) is "COLUMN_SIZE"
+									_scale[column] = colInfo.getInt(2);  // col 2 (was 9) is "DECIMAL_DIGITS"
+									_isNullable[column] = colInfo.getInt(3);  // col 3 (was 11) is "NULLABLE"
+									_isAutoincrement[column] = colInfo.getBoolean(4);  // col 4 (was 23) is "IS_AUTOINCREMENT"
 								}
 								colInfo.close();  // close the resultset to release resources
 							}
@@ -1316,6 +1307,44 @@ public class MonetResultSet
 				}
 			}
 
+			/* private simplified copy of MonetDatabaseMetaData.getColumns() method to fetch only 4 needed attributes of a specific column */
+			private final ResultSet getColumnInfo(final String schemaName, final String tableName, final String columnName) throws SQLException
+			{
+				final StringBuilder query = new StringBuilder(700);
+				query.append("SELECT " +
+					"c.\"type_digits\" AS \"COLUMN_SIZE\", " +
+					"c.\"type_scale\" AS \"DECIMAL_DIGITS\", " +
+					"cast(CASE c.\"null\" WHEN true THEN ").append(ResultSetMetaData.columnNullable)
+						.append(" WHEN false THEN ").append(ResultSetMetaData.columnNoNulls)
+						.append(" ELSE ").append(columnNullableUnknown)
+						.append(" END AS int) AS \"NULLABLE\", ").append(
+					"cast(CASE WHEN c.\"default\" IS NOT NULL AND c.\"default\" LIKE 'next value for %' THEN true ELSE false END AS boolean) AS \"IS_AUTOINCREMENT\" " +
+					// ", s.\"name\" AS \"TABLE_SCHEM\", t.\"name\" AS \"TABLE_NAME\", c.\"name\" AS \"COLUMN_NAME\" " +
+				"FROM \"sys\".\"columns\" c " +
+				"JOIN \"sys\".\"tables\" t ON c.\"table_id\" = t.\"id\" " +
+				"JOIN \"sys\".\"schemas\" s ON t.\"schema_id\" = s.\"id\" ");
+
+				query.append("WHERE s.\"name\" = '").append(conn.escapeSpecialChars(schemaName)).append("'");
+				query.append(" AND t.\"name\" = '").append(conn.escapeSpecialChars(tableName)).append("'");
+				query.append(" AND c.\"name\" = '").append(conn.escapeSpecialChars(columnName)).append("'");
+				// query.append(" ORDER BY \"TABLE_SCHEM\", \"TABLE_NAME\"");
+
+				ResultSet rs = null;
+				final Statement stmt = conn.createStatement();
+				if (stmt != null) {
+					// for debug: System.out.println("SQL (len " + query.length() + "): " + query.toString());
+					rs = stmt.executeQuery(query.toString());
+					if (rs != null) {
+						/* we want the statement object to be closed also when the resultset is closed by the caller */
+						stmt.closeOnCompletion();
+					} else {
+						/* failed to produce a resultset, so release resources for created statement object now */
+						stmt.close();
+					}
+				}
+				return rs;
+			}
+
 			/**
 			 * Returns the number of columns in this ResultSet object.
 			 *
@@ -1495,14 +1524,9 @@ public class MonetResultSet
 			@Override
 			public String getSchemaName(final int column) throws SQLException {
 				checkColumnIndexValidity(column);
-				if (header != null) {
-					// figure the name out
+				if (schemas != null) {
 					try {
-						final String schema = header.getTableNames()[column - 1];
-						if (schema != null) {
-							final int dot = schema.indexOf('.');
-							return (dot >= 0) ? schema.substring(0, dot) : "";
-						}
+						return schemas[column - 1];
 					} catch (IndexOutOfBoundsException e) {
 						throw MonetResultSet.newSQLInvalidColumnIndexException(column);
 					}
@@ -1519,14 +1543,9 @@ public class MonetResultSet
 			@Override
 			public String getTableName(final int column) throws SQLException {
 				checkColumnIndexValidity(column);
-				if (header != null) {
-					// figure the name out
+				if (tables != null) {
 					try {
-						final String table = header.getTableNames()[column - 1];
-						if (table != null) {
-							final int dot = table.indexOf('.');
-							return (dot >= 0) ? table.substring(dot + 1) : table;
-						}
+						return tables[column - 1];
 					} catch (IndexOutOfBoundsException e) {
 						throw MonetResultSet.newSQLInvalidColumnIndexException(column);
 					}