Mercurial > hg > monetdb-java
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() |