comparison src/main/java/nl/cwi/monetdb/jdbc/MonetConnection.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 0b01771d5f8a
children aa17dfa45a8a
comparison
equal deleted inserted replaced
331:6ed8f5b1f9ed 332:e8962bdaa206
62 * The current state of this connection is that it nearly implements the 62 * The current state of this connection is that it nearly implements the
63 * whole Connection interface. 63 * whole Connection interface.
64 * 64 *
65 * @author Fabian Groffen 65 * @author Fabian Groffen
66 * @author Martin van Dinther 66 * @author Martin van Dinther
67 * @version 1.4 67 * @version 1.5
68 */ 68 */
69 public class MonetConnection 69 public class MonetConnection
70 extends MonetWrapper 70 extends MonetWrapper
71 implements Connection, AutoCloseable 71 implements Connection, AutoCloseable
72 { 72 {
119 119
120 /** The number of results we receive from the server at once */ 120 /** The number of results we receive from the server at once */
121 private int curReplySize = -1; // the server by default uses -1 (all) 121 private int curReplySize = -1; // the server by default uses -1 (all)
122 122
123 /** A template to apply to each query (like pre and post fixes), filled in constructor */ 123 /** A template to apply to each query (like pre and post fixes), filled in constructor */
124 public final String[] queryTempl = new String[3]; // pre, post, sep 124 // note: it is made public to the package as it is used from MonetStatement
125 final String[] queryTempl = new String[3]; // pre, post, sep
125 126
126 /** A template to apply to each command (like pre and post fixes), filled in constructor */ 127 /** A template to apply to each command (like pre and post fixes), filled in constructor */
127 private final String[] commandTempl = new String[3]; // pre, post, sep 128 private final String[] commandTempl = new String[3]; // pre, post, sep
128 129
129 /** the SQL language */ 130 /** the SQL language */
137 138
138 /** Whether or not BLOB is mapped to Types.VARBINARY instead of Types.BLOB within this connection */ 139 /** Whether or not BLOB is mapped to Types.VARBINARY instead of Types.BLOB within this connection */
139 private boolean treatBlobAsVarBinary = false; 140 private boolean treatBlobAsVarBinary = false;
140 /** Whether or not CLOB is mapped to Types.VARCHAR instead of Types.CLOB within this connection */ 141 /** Whether or not CLOB is mapped to Types.VARCHAR instead of Types.CLOB within this connection */
141 private boolean treatClobAsVarChar = false; 142 private boolean treatClobAsVarChar = false;
142
143 // Internal cache for determining if system table sys.privilege_codes (new as of Jul2017 release) exists on server
144 private boolean queriedPrivilege_codesTable = false;
145 private boolean hasPrivilege_codesTable = false;
146
147 // Internal cache for determining if system table sys.comments (new as of Mar2018 release) exists on server
148 private boolean queriedCommentsTable = false;
149 private boolean hasCommentsTable = false;
150 143
151 /** The last set query timeout on the server as used by Statement, PreparedStatement and CallableStatement */ 144 /** The last set query timeout on the server as used by Statement, PreparedStatement and CallableStatement */
152 protected int lastSetQueryTimeout = 0; // 0 means no timeout, which is the default on the server 145 protected int lastSetQueryTimeout = 0; // 0 means no timeout, which is the default on the server
153 146
154 147
324 queryTempl[0] = "s"; // pre 317 queryTempl[0] = "s"; // pre
325 queryTempl[1] = "\n;"; // post 318 queryTempl[1] = "\n;"; // post
326 queryTempl[2] = "\n;\n"; // separator 319 queryTempl[2] = "\n;\n"; // separator
327 320
328 commandTempl[0] = "X"; // pre 321 commandTempl[0] = "X"; // pre
329 commandTempl[1] = null; // post 322 commandTempl[1] = ""; // post
330 commandTempl[2] = "\nX"; // separator 323 commandTempl[2] = "\nX"; // separator
331 } else if ("mal".equals(language)) { 324 } else if ("mal".equals(language)) {
332 lang = LANG_MAL; 325 lang = LANG_MAL;
333 326
334 queryTempl[0] = null; 327 queryTempl[0] = ""; // pre
335 queryTempl[1] = ";\n"; 328 queryTempl[1] = ";\n"; // post
336 queryTempl[2] = ";\n"; 329 queryTempl[2] = ";\n"; // separator
337 330
338 commandTempl[0] = null; // pre 331 commandTempl[0] = ""; // pre
339 commandTempl[1] = null; // post 332 commandTempl[1] = ""; // post
340 commandTempl[2] = null; // separator 333 commandTempl[2] = ""; // separator
341 } else { 334 } else {
342 lang = LANG_UNKNOWN; 335 lang = LANG_UNKNOWN;
343 } 336 }
344 337
345 // the following initialisers are only valid when the language is SQL... 338 // the following initialisers are only valid when the language is SQL...
1626 } catch (SocketException e) { 1619 } catch (SocketException e) {
1627 throw new SQLNonTransientConnectionException(e.getMessage(), "08000"); 1620 throw new SQLNonTransientConnectionException(e.getMessage(), "08000");
1628 } 1621 }
1629 } 1622 }
1630 1623
1631
1632 //== end methods of interface java.sql.Connection 1624 //== end methods of interface java.sql.Connection
1633 1625
1634
1635 /**
1636 * @return the MonetDB JDBC Connection URL (without user name and password).
1637 * Defined as public because it is called from: MonetDatabaseMetaData.java getURL()
1638 */
1639 String getJDBCURL() {
1640 return MonetDriver.MONETURL + hostname + ":" + port + "/" + database + (lang == LANG_MAL ? "?language=mal" : "");
1641 }
1642 1626
1643 /** 1627 /**
1644 * @return whether the JDBC BLOB type should be mapped to VARBINARY type. 1628 * @return whether the JDBC BLOB type should be mapped to VARBINARY type.
1645 * This allows generic JDBC programs to fetch Blob data via getBytes() 1629 * This allows generic JDBC programs to fetch Blob data via getBytes()
1646 * instead of getBlob() and Blob.getBinaryStream() to reduce overhead. 1630 * instead of getBlob() and Blob.getBinaryStream() to reduce overhead.
1631 * It is called from: MonetResultSet and MonetPreparedStatement
1647 */ 1632 */
1648 boolean mapBlobAsVarBinary() { 1633 boolean mapBlobAsVarBinary() {
1649 return treatBlobAsVarBinary; 1634 return treatBlobAsVarBinary;
1650 } 1635 }
1651 1636
1652 /** 1637 /**
1653 * @return whether the JDBC CLOB type should be mapped to VARCHAR type. 1638 * @return whether the JDBC CLOB type should be mapped to VARCHAR type.
1654 * This allows generic JDBC programs to fetch Clob data via getString() 1639 * This allows generic JDBC programs to fetch Clob data via getString()
1655 * instead of getClob() and Clob.getCharacterStream() to reduce overhead. 1640 * instead of getClob() and Clob.getCharacterStream() to reduce overhead.
1641 * It is called from: MonetResultSet and MonetPreparedStatement
1656 */ 1642 */
1657 boolean mapClobAsVarChar() { 1643 boolean mapClobAsVarChar() {
1658 return treatClobAsVarChar; 1644 return treatClobAsVarChar;
1659 } 1645 }
1660 1646
1661 /** 1647 /**
1662 * Internal utility method to query the server to find out if it has 1648 * @return the MonetDB JDBC Connection URL (without user name and password).
1649 * It is called from: getURL()in MonetDatabaseMetaData
1650 */
1651 String getJDBCURL() {
1652 final StringBuilder sb = new StringBuilder(128);
1653 sb.append(MonetDriver.MONETURL).append(hostname)
1654 .append(':').append(port)
1655 .append('/').append(database);
1656 if (lang == LANG_MAL)
1657 sb.append("?language=mal");
1658 return sb.toString();
1659 }
1660
1661 // Internal cache for 3 static mserver environment values, so they aren't queried from mserver again and again
1662 private String env_current_user = null;
1663 private String env_monet_version = null;
1664 private String env_max_clients = null;
1665
1666 /**
1667 * Utility method to fetch 3 mserver environment values combined in one query for efficiency.
1668 * We currently fetch the env values of: current_user, monet_version and max_clients.
1669 * We cache them such that we do not need to query the server again and again.
1670 */
1671 private synchronized void getEnvValues() throws SQLException {
1672 Statement st = null;
1673 ResultSet rs = null;
1674 try {
1675 st = createStatement();
1676 if (st != null) {
1677 rs = st.executeQuery(
1678 "SELECT \"name\", \"value\" FROM \"sys\".\"env\"()" +
1679 " WHERE \"name\" IN ('monet_version', 'max_clients')" +
1680 " UNION SELECT 'current_user' as \"name\", current_user as \"value\"");
1681 if (rs != null) {
1682 while (rs.next()) {
1683 final String prop = rs.getString(1);
1684 final String value = rs.getString(2);
1685 if ("current_user".equals(prop)) {
1686 env_current_user = value;
1687 } else
1688 if ("monet_version".equals(prop)) {
1689 env_monet_version = value;
1690 } else
1691 if ("max_clients".equals(prop)) {
1692 env_max_clients = value;
1693 }
1694 }
1695 }
1696 }
1697 /* do not catch SQLException here, as we need to know it when it fails */
1698 } finally {
1699 closeResultsetStatement(rs, st);
1700 }
1701 // for debug: System.out.println("Read: env_current_user: " + env_current_user + " env_monet_version: " + env_monet_version + " env_max_clients: " + env_max_clients);
1702 }
1703
1704 /**
1705 * @return the current User Name.
1706 * It is called from: MonetDatabaseMetaData
1707 */
1708 String getUserName() throws SQLException {
1709 if (env_current_user == null)
1710 getEnvValues();
1711 return env_current_user;
1712 }
1713
1714 /**
1715 * @return the MonetDB Database Server version string.
1716 * It is called from: MonetDatabaseMetaData
1717 */
1718 String getDatabaseProductVersion() throws SQLException {
1719 if (env_monet_version == null)
1720 getEnvValues();
1721 // always return a valid String to prevent NPE in getTables() and getTableTypes()
1722 if (env_monet_version != null)
1723 return env_monet_version;
1724 return "";
1725 }
1726
1727 /**
1728 * @return the MonetDB Database Server major version number.
1729 * It is called from: MonetDatabaseMetaData
1730 */
1731 int getDatabaseMajorVersion() throws SQLException {
1732 if (env_monet_version == null)
1733 getEnvValues();
1734 if (env_monet_version != null) {
1735 try {
1736 // from version string such as 11.33.9 extract number: 11
1737 final int start = env_monet_version.indexOf('.');
1738 return Integer.parseInt((start >= 0) ? env_monet_version.substring(0, start) : env_monet_version);
1739 } catch (NumberFormatException nfe) {
1740 // ignore
1741 }
1742 }
1743 return 0;
1744 }
1745
1746 /**
1747 * @return the MonetDB Database Server minor version number.
1748 * It is called from: MonetDatabaseMetaData
1749 */
1750 int getDatabaseMinorVersion() throws SQLException {
1751 if (env_monet_version == null)
1752 getEnvValues();
1753 if (env_monet_version != null) {
1754 try {
1755 // from version string such as 11.33.9 extract number: 33
1756 int start = env_monet_version.indexOf('.');
1757 if (start >= 0) {
1758 start++;
1759 final int end = env_monet_version.indexOf('.', start);
1760 return Integer.parseInt((end > 0) ? env_monet_version.substring(start, end) : env_monet_version.substring(start));
1761 }
1762 } catch (NumberFormatException nfe) {
1763 // ignore
1764 }
1765 }
1766 return 0;
1767 }
1768
1769 /**
1770 * @return the maximum number of active connections possible at one time;
1771 * a result of zero means that there is no limit or the limit is not known
1772 * It is called from: MonetDatabaseMetaData
1773 */
1774 int getMaxConnections() throws SQLException {
1775 if (env_max_clients == null)
1776 getEnvValues();
1777 if (env_max_clients != null) {
1778 try {
1779 return Integer.parseInt(env_max_clients);
1780 } catch (NumberFormatException nfe) {
1781 /* ignore */
1782 }
1783 }
1784 return 0;
1785 }
1786
1787
1788 // Internal cache for determining if system table sys.privilege_codes (new as of Jul2017 release) exists on connected server
1789 private boolean queriedPrivilege_codesTable = false;
1790 private boolean hasPrivilege_codesTable = false;
1791 /**
1792 * Utility method to query the server to find out if it has
1793 * the system table sys.privilege_codes (which is new as of Jul2017 release).
1794 * The result is cached and reused, so that we only test the query once per connection.
1795 * This method is used by methods from MonetDatabaseMetaData.
1796 */
1797 boolean privilege_codesTableExists() {
1798 if (!queriedPrivilege_codesTable) {
1799 hasPrivilege_codesTable = existsSysTable("privilege_codes");
1800 queriedPrivilege_codesTable = true; // set flag, so the querying is done only at first invocation.
1801 }
1802 return hasPrivilege_codesTable;
1803 }
1804
1805 // Internal cache for determining if system table sys.comments (new as of Mar2018 release) exists on connected server
1806 private boolean queriedCommentsTable = false;
1807 private boolean hasCommentsTable = false;
1808 /**
1809 * Utility method to query the server to find out if it has
1663 * the system table sys.comments (which is new as of Mar2018 release). 1810 * the system table sys.comments (which is new as of Mar2018 release).
1664 * The result is cached and reused, so that we only test the query once per connection. 1811 * The result is cached and reused, so that we only test the query once per connection.
1665 * This method is used by methods from MonetDatabaseMetaData. 1812 * This method is used by methods from MonetDatabaseMetaData.
1666 */ 1813 */
1667 boolean commentsTableExists() { 1814 boolean commentsTableExists() {
1670 queriedCommentsTable = true; // set flag, so the querying is done only at first invocation. 1817 queriedCommentsTable = true; // set flag, so the querying is done only at first invocation.
1671 } 1818 }
1672 return hasCommentsTable; 1819 return hasCommentsTable;
1673 } 1820 }
1674 1821
1675 /** 1822
1676 * Internal utility method to query the server to find out if it has 1823 /**
1677 * the system table sys.privilege_codes (which is new as of Jul2017 release). 1824 * Internal utility method to query the server to find out if it has a specific system table sys.<tablename>.
1678 * The result is cached and reused, so that we only test the query once per connection.
1679 * This method is used by methods from MonetDatabaseMetaData.
1680 */
1681 boolean privilege_codesTableExists() {
1682 if (!queriedPrivilege_codesTable) {
1683 hasPrivilege_codesTable = existsSysTable("privilege_codes");
1684 queriedPrivilege_codesTable = true; // set flag, so the querying is done only at first invocation.
1685 }
1686 return hasPrivilege_codesTable;
1687 }
1688
1689 /**
1690 * Internal utility method to query the server to find out if it has the system table sys.<tablename>.
1691 */ 1825 */
1692 private boolean existsSysTable(final String tablename) { 1826 private boolean existsSysTable(final String tablename) {
1693 boolean exists = false; 1827 boolean exists = false;
1694 Statement stmt = null; 1828 Statement stmt = null;
1695 ResultSet rs = null; 1829 ResultSet rs = null;
1706 } catch (SQLException se) { 1840 } catch (SQLException se) {
1707 /* ignore */ 1841 /* ignore */
1708 } finally { 1842 } finally {
1709 closeResultsetStatement(rs, stmt); 1843 closeResultsetStatement(rs, stmt);
1710 } 1844 }
1711 // for debug: System.out.println("testTableExists(" + tablename + ") returns: " + exists); 1845 // for debug: System.out.println("testTableExists(" + tablename + ") returns: " + exists);
1712 return exists; 1846 return exists;
1713 } 1847 }
1714 1848
1715 /** 1849 /**
1716 * Closes a ResultSet and/or Statement object without throwing any SQLExceptions 1850 * Closes a ResultSet and/or Statement object without throwing any SQLExceptions
1778 } 1912 }
1779 1913
1780 /** 1914 /**
1781 * Sends the given string to MonetDB as command/query using commandTempl or queryTempl 1915 * Sends the given string to MonetDB as command/query using commandTempl or queryTempl
1782 * Making sure there is a prompt after the command is sent. All possible 1916 * Making sure there is a prompt after the command is sent. All possible
1783 * returned information is discarded. Encountered errors are reported. 1917 * returned information is discarded. Encountered errors are reported.
1784 * 1918 *
1785 * @param command the exact string to send to MonetDB 1919 * @param command the exact string to send to MonetDB
1786 * @param usequeryTempl send the command using a queryTempl? else it is send using commandTempl 1920 * @param usequeryTempl send the command using queryTempl or else using commandTempl
1787 * @throws SQLException if an IO exception or a database error occurs 1921 * @throws SQLException if an IO exception or a database error occurs
1788 */ 1922 */
1789 private void sendCommand(final String command, final boolean usequeryTempl) throws SQLException { 1923 private void sendCommand(final String command, final boolean usequeryTempl) throws SQLException {
1790 final String cmd = usequeryTempl
1791 ? (queryTempl[0] == null ? "" : queryTempl[0]) + command + (queryTempl[1] == null ? "" : queryTempl[1])
1792 : (commandTempl[0] == null ? "" : commandTempl[0]) + command + (commandTempl[1] == null ? "" : commandTempl[1]);
1793
1794 synchronized (server) { 1924 synchronized (server) {
1795 try { 1925 try {
1796 out.writeLine(cmd); 1926 out.writeLine(usequeryTempl ? (queryTempl[0] + command + queryTempl[1])
1927 : (commandTempl[0] + command + commandTempl[1]) );
1797 final String error = in.waitForPrompt(); 1928 final String error = in.waitForPrompt();
1798 if (error != null) 1929 if (error != null)
1799 throw new SQLException(error.substring(6), error.substring(0, 5)); 1930 throw new SQLException(error.substring(6), error.substring(0, 5));
1800 } catch (SocketTimeoutException e) { 1931 } catch (SocketTimeoutException e) {
1801 close(); // JDBC 4.1 semantics, abort() 1932 close(); // JDBC 4.1 semantics, abort()