comparison src/main/java/nl/cwi/monetdb/jdbc/MonetDatabaseMetaData.java @ 332:e8962bdaa206

Moved utility methods and associated caches from DatabaseMetaData to Connection object. This allows the cached values to be shared across multiple DatabaseMetaData objects and thereby reduce the number of queries sent to the server and the number of caches. For instance SQuirreL instantiates many DatabaseMetaData objects and each time the caches needed to be refilled by getting values from the server. This is now optimized. Also made sure that in MonetConnection the internal queryTempl[] and commandTempl[] array entries never contain null. This eleviates the need to test on null when composing the marked-up command string which is sent to the server via mapi.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 19 Sep 2019 16:40:33 +0200 (2019-09-19)
parents 6ed8f5b1f9ed
children 98018e38994b
comparison
equal deleted inserted replaced
331:6ed8f5b1f9ed 332:e8962bdaa206
20 /** 20 /**
21 * A DatabaseMetaData object suitable for the MonetDB database. 21 * A DatabaseMetaData object suitable for the MonetDB database.
22 * 22 *
23 * @author Fabian Groffen 23 * @author Fabian Groffen
24 * @author Martin van Dinther 24 * @author Martin van Dinther
25 * @version 0.8 25 * @version 0.9
26 */ 26 */
27 public class MonetDatabaseMetaData 27 public class MonetDatabaseMetaData
28 extends MonetWrapper 28 extends MonetWrapper
29 implements DatabaseMetaData 29 implements DatabaseMetaData
30 { 30 {
31 private final Connection con; 31 private final MonetConnection con;
32 32
33 // Internal cache for 3 server environment values 33 public MonetDatabaseMetaData(final MonetConnection parent) {
34 private String env_current_user;
35 private String env_monet_version;
36 private String env_max_clients;
37
38 public MonetDatabaseMetaData(final Connection parent) {
39 con = parent; 34 con = parent;
40 }
41
42 /**
43 * Utility method to fetch some server environment values combined in one query for efficiency.
44 * We currently fetch the env values of: current_user, monet_version and max_clients.
45 * We cache them locally such that we do not need to query the server again and again.
46 */
47 private synchronized void getEnvValues() throws SQLException {
48 Statement st = null;
49 ResultSet rs = null;
50 try {
51 st = con.createStatement();
52 rs = st.executeQuery(
53 "SELECT \"name\", \"value\" FROM \"sys\".\"env\"()" +
54 " WHERE \"name\" IN ('monet_version', 'max_clients')" +
55 " UNION SELECT 'current_user' as \"name\", current_user as \"value\"");
56 if (rs != null) {
57 while (rs.next()) {
58 String prop = rs.getString("name");
59 String value = rs.getString("value");
60 if ("current_user".equals(prop)) {
61 env_current_user = value;
62 } else
63 if ("monet_version".equals(prop)) {
64 env_monet_version = value;
65 } else
66 if ("max_clients".equals(prop)) {
67 env_max_clients = value;
68 }
69 }
70 }
71 /* do not catch SQLException here, as we want to know it when it fails */
72 } finally {
73 MonetConnection.closeResultsetStatement(rs, st);
74 }
75 // for debug: System.out.println("Read: env_current_user: " + env_current_user + " env_monet_version: " + env_monet_version + " env_max_clients: " + env_max_clients);
76 }
77
78 /**
79 * Internal utility method to create a Statement object, execute a query and return the ResulSet object which allows scrolling.
80 * As the Statement object is created internally (the caller does not see it and thus can not close it),
81 * we set it to close (and free server resources) when the ResultSet object is closed by the caller.
82 */
83 private ResultSet executeMetaDataQuery(final String query) throws SQLException {
84 final Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
85 ResultSet rs = null;
86 if (stmt != null) {
87 // for debug: System.out.println("SQL (len " + query.length() + "): " + query);
88 rs = stmt.executeQuery(query);
89 if (rs != null) {
90 /* we want the statement object to be closed also when the resultset is closed by the caller */
91 stmt.closeOnCompletion();
92 } else {
93 /* failed to produce a resultset, so release resources for created statement object now */
94 stmt.close();
95 }
96 }
97 return rs;
98 } 35 }
99 36
100 /** 37 /**
101 * Retrieves whether the current user can call all the procedures 38 * Retrieves whether the current user can call all the procedures
102 * returned by the method getProcedures. 39 * returned by the method getProcedures.
127 * @return a reconstructed connection string 64 * @return a reconstructed connection string
128 * @throws SQLException if a database access error occurs 65 * @throws SQLException if a database access error occurs
129 */ 66 */
130 @Override 67 @Override
131 public String getURL() throws SQLException { 68 public String getURL() throws SQLException {
132 return ((MonetConnection)con).getJDBCURL(); 69 return con.getJDBCURL();
133 } 70 }
134 71
135 /** 72 /**
136 * What is our user name as known to the database? 73 * What is our user name as known to the database?
137 * 74 *
138 * @return sql user 75 * @return sql user
139 * @throws SQLException if a database access error occurs 76 * @throws SQLException if a database access error occurs
140 */ 77 */
141 @Override 78 @Override
142 public String getUserName() throws SQLException { 79 public String getUserName() throws SQLException {
143 if (env_current_user == null) 80 return con.getUserName();
144 getEnvValues();
145 return env_current_user;
146 } 81 }
147 82
148 /** 83 /**
149 * Is the database in read-only mode? 84 * Is the database in read-only mode?
150 * 85 *
221 * @return the mserver5 version number string 156 * @return the mserver5 version number string
222 * @throws SQLException if a database access error occurs 157 * @throws SQLException if a database access error occurs
223 */ 158 */
224 @Override 159 @Override
225 public String getDatabaseProductVersion() throws SQLException { 160 public String getDatabaseProductVersion() throws SQLException {
226 if (env_monet_version == null) 161 return con.getDatabaseProductVersion();
227 getEnvValues();
228 if (env_monet_version != null)
229 return env_monet_version;
230 // always return a valid String to prevent NPE in getTables() and getTableTypes()
231 return "";
232 } 162 }
233 163
234 /** 164 /**
235 * What is the name of this JDBC driver? 165 * What is the name of this JDBC driver?
236 * 166 *
1416 public int getMaxColumnsInTable() { 1346 public int getMaxColumnsInTable() {
1417 return 100*1000; // soft limit it to 100 thousand 1347 return 100*1000; // soft limit it to 100 thousand
1418 } 1348 }
1419 1349
1420 /** 1350 /**
1421 * How many active connections can we have at a time to this 1351 * Retrieves the maximum number of concurrent connections to this database that are possible.
1422 * database? Well, since it depends on Mserver, which just listens 1352 *
1423 * for new connections and creates a new thread for each connection, 1353 * @return the maximum number of active connections possible at one time; a result of zero means that there is no limit or the limit is not known
1424 * this number can be very high, and theoretically till the system
1425 * runs out of resources. However, knowing MonetDB is knowing that you
1426 * should handle it a little bit with care, so I give a very minimalistic
1427 * number here.
1428 *
1429 * @return the maximum number of connections
1430 */ 1354 */
1431 @Override 1355 @Override
1432 public int getMaxConnections() throws SQLException { 1356 public int getMaxConnections() throws SQLException {
1433 if (env_max_clients == null) 1357 return con.getMaxConnections();
1434 getEnvValues();
1435
1436 int max_clients = 16;
1437 if (env_max_clients != null) {
1438 try {
1439 max_clients = Integer.parseInt(env_max_clients);
1440 } catch (NumberFormatException nfe) { /* ignore */ }
1441 }
1442 return max_clients;
1443 } 1358 }
1444 1359
1445 /** 1360 /**
1446 * What is the maximum cursor name length 1361 * What is the maximum cursor name length
1447 * Actually we do not do named cursors, so I keep the value small as 1362 * Actually we do not do named cursors, so I keep the value small as
1711 final String catalog, 1626 final String catalog,
1712 final String schemaPattern, 1627 final String schemaPattern,
1713 final String procedureNamePattern 1628 final String procedureNamePattern
1714 ) throws SQLException 1629 ) throws SQLException
1715 { 1630 {
1716 final boolean useCommentsTable = ((MonetConnection)con).commentsTableExists(); 1631 final boolean useCommentsTable = con.commentsTableExists();
1717 final StringBuilder query = new StringBuilder(980); 1632 final StringBuilder query = new StringBuilder(980);
1718 query.append("SELECT cast(null as char(1)) AS \"PROCEDURE_CAT\", " + 1633 query.append("SELECT cast(null as char(1)) AS \"PROCEDURE_CAT\", " +
1719 "s.\"name\" AS \"PROCEDURE_SCHEM\", " + 1634 "s.\"name\" AS \"PROCEDURE_SCHEM\", " +
1720 "f.\"name\" AS \"PROCEDURE_NAME\", " + 1635 "f.\"name\" AS \"PROCEDURE_NAME\", " +
1721 "cast(null as char(1)) AS \"Field4\", " + 1636 "cast(null as char(1)) AS \"Field4\", " +
1926 // as of Jul2015 release we also have a new table: sys.table_types with names for the new table types 1841 // as of Jul2015 release we also have a new table: sys.table_types with names for the new table types
1927 // for correct behavior we need to know if the server is using the old (pre Jul2015) or new sys.tables.type values 1842 // for correct behavior we need to know if the server is using the old (pre Jul2015) or new sys.tables.type values
1928 final boolean preJul2015 = ("11.19.15".compareTo(getDatabaseProductVersion()) >= 0); 1843 final boolean preJul2015 = ("11.19.15".compareTo(getDatabaseProductVersion()) >= 0);
1929 // for debug: System.out.println("getDatabaseProductVersion() is " + getDatabaseProductVersion() + " preJul2015 is " + preJul2015); 1844 // for debug: System.out.println("getDatabaseProductVersion() is " + getDatabaseProductVersion() + " preJul2015 is " + preJul2015);
1930 1845
1931 final boolean useCommentsTable = ((MonetConnection)con).commentsTableExists(); 1846 final boolean useCommentsTable = con.commentsTableExists();
1932 final StringBuilder query = new StringBuilder(1600); 1847 final StringBuilder query = new StringBuilder(1600);
1933 if (preJul2015 && types != null && types.length > 0) { 1848 if (preJul2015 && types != null && types.length > 0) {
1934 // we need to filter on the constructed "TABLE_TYPE" expression, this is only possible when we use a subquery in the FROM 1849 // we need to filter on the constructed "TABLE_TYPE" expression, this is only possible when we use a subquery in the FROM
1935 query.append("SELECT * FROM ("); 1850 query.append("SELECT * FROM (");
1936 } 1851 }
2175 final String schemaPattern, 2090 final String schemaPattern,
2176 final String tableNamePattern, 2091 final String tableNamePattern,
2177 final String columnNamePattern 2092 final String columnNamePattern
2178 ) throws SQLException 2093 ) throws SQLException
2179 { 2094 {
2180 final boolean useCommentsTable = ((MonetConnection)con).commentsTableExists(); 2095 final boolean useCommentsTable = con.commentsTableExists();
2181 final StringBuilder query = new StringBuilder(2450); 2096 final StringBuilder query = new StringBuilder(2450);
2182 query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " + 2097 query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " +
2183 "s.\"name\" AS \"TABLE_SCHEM\", " + 2098 "s.\"name\" AS \"TABLE_SCHEM\", " +
2184 "t.\"name\" AS \"TABLE_NAME\", " + 2099 "t.\"name\" AS \"TABLE_NAME\", " +
2185 "c.\"name\" AS \"COLUMN_NAME\", " + 2100 "c.\"name\" AS \"COLUMN_NAME\", " +
2272 final String schemaPattern, 2187 final String schemaPattern,
2273 final String tableNamePattern, 2188 final String tableNamePattern,
2274 final String columnNamePattern 2189 final String columnNamePattern
2275 ) throws SQLException 2190 ) throws SQLException
2276 { 2191 {
2277 final boolean usePrivilege_codesTable = ((MonetConnection)con).privilege_codesTableExists(); 2192 final boolean usePrivilege_codesTable = con.privilege_codesTableExists();
2278 final StringBuilder query = new StringBuilder(1100); 2193 final StringBuilder query = new StringBuilder(1100);
2279 query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " + 2194 query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " +
2280 "s.\"name\" AS \"TABLE_SCHEM\", " + 2195 "s.\"name\" AS \"TABLE_SCHEM\", " +
2281 "t.\"name\" AS \"TABLE_NAME\", " + 2196 "t.\"name\" AS \"TABLE_NAME\", " +
2282 "c.\"name\" AS \"COLUMN_NAME\", " + 2197 "c.\"name\" AS \"COLUMN_NAME\", " +
2363 final String catalog, 2278 final String catalog,
2364 final String schemaPattern, 2279 final String schemaPattern,
2365 final String tableNamePattern 2280 final String tableNamePattern
2366 ) throws SQLException 2281 ) throws SQLException
2367 { 2282 {
2368 final boolean usePrivilege_codesTable = ((MonetConnection)con).privilege_codesTableExists(); 2283 final boolean usePrivilege_codesTable = con.privilege_codesTableExists();
2369 final StringBuilder query = new StringBuilder(1000); 2284 final StringBuilder query = new StringBuilder(1000);
2370 query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " + 2285 query.append("SELECT cast(null as char(1)) AS \"TABLE_CAT\", " +
2371 "s.\"name\" AS \"TABLE_SCHEM\", " + 2286 "s.\"name\" AS \"TABLE_SCHEM\", " +
2372 "t.\"name\" AS \"TABLE_NAME\", " + 2287 "t.\"name\" AS \"TABLE_NAME\", " +
2373 "grantors.\"name\" AS \"GRANTOR\", " + 2288 "grantors.\"name\" AS \"GRANTOR\", " +
3617 * @return the underlying database's major version 3532 * @return the underlying database's major version
3618 * @throws SQLException if a database access error occurs 3533 * @throws SQLException if a database access error occurs
3619 */ 3534 */
3620 @Override 3535 @Override
3621 public int getDatabaseMajorVersion() throws SQLException { 3536 public int getDatabaseMajorVersion() throws SQLException {
3622 if (env_monet_version == null) 3537 return con.getDatabaseMajorVersion();
3623 getEnvValues();
3624 int major = 0;
3625 if (env_monet_version != null) {
3626 try {
3627 int start = env_monet_version.indexOf('.');
3628 major = Integer.parseInt((start >= 0) ? env_monet_version.substring(0, start) : env_monet_version);
3629 } catch (NumberFormatException e) {
3630 // ignore
3631 }
3632 }
3633 return major;
3634 } 3538 }
3635 3539
3636 /** 3540 /**
3637 * Retrieves the minor version number of the underlying database. 3541 * Retrieves the minor version number of the underlying database.
3638 * 3542 *
3639 * @return underlying database's minor version 3543 * @return underlying database's minor version
3640 * @throws SQLException if a database access error occurs 3544 * @throws SQLException if a database access error occurs
3641 */ 3545 */
3642 @Override 3546 @Override
3643 public int getDatabaseMinorVersion() throws SQLException { 3547 public int getDatabaseMinorVersion() throws SQLException {
3644 if (env_monet_version == null) 3548 return con.getDatabaseMinorVersion();
3645 getEnvValues();
3646 int minor = 0;
3647 if (env_monet_version != null) {
3648 try {
3649 int start = env_monet_version.indexOf('.');
3650 if (start >= 0) {
3651 start++;
3652 int end = env_monet_version.indexOf('.', start);
3653 minor = Integer.parseInt((end > 0) ? env_monet_version.substring(start, end) : env_monet_version.substring(start));
3654 }
3655 } catch (NumberFormatException e) {
3656 // ignore
3657 }
3658 }
3659 return minor;
3660 } 3549 }
3661 3550
3662 /** 3551 /**
3663 * Retrieves the major JDBC version number for this driver. 3552 * Retrieves the major JDBC version number for this driver.
3664 * 3553 *
3866 final String catalog, 3755 final String catalog,
3867 final String schemaPattern, 3756 final String schemaPattern,
3868 final String functionNamePattern 3757 final String functionNamePattern
3869 ) throws SQLException 3758 ) throws SQLException
3870 { 3759 {
3871 final boolean useCommentsTable = ((MonetConnection)con).commentsTableExists(); 3760 final boolean useCommentsTable = con.commentsTableExists();
3872 final StringBuilder query = new StringBuilder(800); 3761 final StringBuilder query = new StringBuilder(800);
3873 query.append("SELECT cast(null as char(1)) AS \"FUNCTION_CAT\", " + 3762 query.append("SELECT cast(null as char(1)) AS \"FUNCTION_CAT\", " +
3874 "s.\"name\" AS \"FUNCTION_SCHEM\", " + 3763 "s.\"name\" AS \"FUNCTION_SCHEM\", " +
3875 "f.\"name\" AS \"FUNCTION_NAME\", ") 3764 "f.\"name\" AS \"FUNCTION_NAME\", ")
3876 .append(useCommentsTable ? "COALESCE(cm.\"remark\", cast(f.\"func\" as varchar(9999)))" : "cast(f.\"func\" as varchar(9999))").append(" AS \"REMARKS\", " + 3765 .append(useCommentsTable ? "COALESCE(cm.\"remark\", cast(f.\"func\" as varchar(9999)))" : "cast(f.\"func\" as varchar(9999))").append(" AS \"REMARKS\", " +
4136 } 4025 }
4137 4026
4138 //== end methods interface DatabaseMetaData 4027 //== end methods interface DatabaseMetaData
4139 4028
4140 4029
4141 //== this is a helper method which does not belong to the interface 4030 //== internal helper methods which do not belong to the JDBC interface
4031
4032 /**
4033 * Internal utility method to create a Statement object, execute a query and return the ResulSet object which allows scrolling.
4034 * As the Statement object is created internally (the caller does not see it and thus can not close it),
4035 * we set it to close (and free server resources) when the ResultSet object is closed by the caller.
4036 */
4037 private ResultSet executeMetaDataQuery(final String query) throws SQLException {
4038 final Statement stmt = con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
4039 ResultSet rs = null;
4040 if (stmt != null) {
4041 // for debug: System.out.println("SQL (len " + query.length() + "): " + query);
4042 rs = stmt.executeQuery(query);
4043 if (rs != null) {
4044 /* we want the statement object to be closed also when the resultset is closed by the caller */
4045 stmt.closeOnCompletion();
4046 } else {
4047 /* failed to produce a resultset, so release resources for created statement object now */
4048 stmt.close();
4049 }
4050 }
4051 return rs;
4052 }
4142 4053
4143 /** 4054 /**
4144 * Returns a SQL match part string where depending on the input value we 4055 * Returns a SQL match part string where depending on the input value we
4145 * compose an exact match (use =) or match with wildcards (use LIKE) or IS NULL 4056 * compose an exact match (use =) or match with wildcards (use LIKE) or IS NULL
4146 * 4057 *