Mercurial > hg > monetdb-java
changeset 905:a52bc2dcdb8c
Implement ClientInfo API
And move ClientInfo out out MapiSocket
author | Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com> |
---|---|
date | Mon, 17 Jun 2024 15:54:45 +0200 (9 months ago) |
parents | 2d880f90be2a |
children | 8c8c423dc619 |
files | src/main/java/org/monetdb/jdbc/MonetConnection.java src/main/java/org/monetdb/mcl/net/ClientInfo.java src/main/java/org/monetdb/mcl/net/MapiSocket.java src/main/java/org/monetdb/mcl/net/Target.java |
diffstat | 4 files changed, 183 insertions(+), 54 deletions(-) [+] |
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetConnection.java +++ b/src/main/java/org/monetdb/jdbc/MonetConnection.java @@ -21,6 +21,7 @@ import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.PreparedStatement; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; @@ -35,6 +36,7 @@ import java.util.concurrent.Executor; import org.monetdb.mcl.io.BufferedMCLReader; import org.monetdb.mcl.io.BufferedMCLWriter; import org.monetdb.mcl.io.LineType; +import org.monetdb.mcl.net.ClientInfo; import org.monetdb.mcl.net.MapiSocket; import org.monetdb.mcl.net.Target; import org.monetdb.mcl.parser.HeaderLineParser; @@ -118,6 +120,11 @@ public class MonetConnection /** A template to apply to each command (like pre and post fixes), filled in constructor */ private final String[] commandTempl = new String[2]; // pre, post + /** A mapping of ClientInfo property names such as 'ClientHostname' to columns of the + * sessions table, such as 'hostname'. + */ + private HashMap<String,String> clientInfoAttributeNames = null; + /** the SQL language */ private static final int LANG_SQL = 0; /** the MAL language (officially *NOT* supported) */ @@ -224,9 +231,16 @@ public class MonetConnection throw sqle; } - // send any clientinfo - if (server.hasClientInfo()) { - sendControlCommand("clientinfo " + server.getClientInfo().format()); + if (server.canClientInfo() && target.sendClientInfo()) { + ClientInfo info = new ClientInfo(); + info.setDefaults(); + String clientApplication = target.getClientApplication(); + String clientRemark = target.getClientRemark(); + if (!clientApplication.isEmpty()) + info.set("ApplicationName", clientApplication); + if (!clientRemark.isEmpty()) + info.set("ClientRemark", clientRemark); + sendClientInfo(info); } // Now take care of any options not handled during the handshake @@ -1270,8 +1284,16 @@ public class MonetConnection */ @Override public String getClientInfo(final String name) throws SQLException { - // MonetDB doesn't support any Client Info Properties yet - return null; + String attrName = getClientInfoAttributeNames().get(name); + if (attrName == null) + return null; + String query = "SELECT " + attrName + " FROM sys.sessions WHERE sessionid = current_sessionid()"; + try (Statement st = createStatement(); ResultSet rs = st.executeQuery(query)) { + if (rs.next()) + return rs.getString(1); + else + return null; + } } /** @@ -1289,7 +1311,53 @@ public class MonetConnection @Override public Properties getClientInfo() throws SQLException { // MonetDB doesn't support any Client Info Properties yet - return new Properties(); + Properties props = new Properties(); + + if (server.canClientInfo()) { + StringBuilder builder = new StringBuilder("SELECT "); + String sep = ""; + for (Entry<String, String> entry: getClientInfoAttributeNames().entrySet()) { + String jdbcName = entry.getKey(); + String attrName = entry.getValue(); + builder.append(sep); + sep = ", "; + builder.append(attrName); + builder.append(" AS \""); + builder.append(jdbcName); + builder.append("\""); + } + builder.append(" FROM sys.sessions WHERE sessionid = current_sessionid()"); + + try ( + Statement st = createStatement(); + ResultSet rs = st.executeQuery(builder.toString()) + ) { + if (rs.next()) { + ResultSetMetaData md = rs.getMetaData(); + for (int i = 1; i <= md.getColumnCount(); i++) { + String key = md.getColumnName(i); + String value = rs.getString(i); + props.setProperty(key, value != null ? value : ""); + } + } + } + } + return props; + } + + private HashMap<String,String> getClientInfoAttributeNames() throws SQLException { + if (clientInfoAttributeNames == null) { + HashMap<String, String> map = new HashMap<>(); + try (Statement st = createStatement(); ResultSet rs = st.executeQuery("SELECT prop, session_attr FROM sys.clientinfo_properties")) { + while (rs.next()) { + String jdbcName = rs.getString(1); + String attrName = rs.getString(2); + map.put(jdbcName, attrName); + } + } + clientInfoAttributeNames = map; + } + return clientInfoAttributeNames; } /** @@ -1330,8 +1398,16 @@ public class MonetConnection */ @Override public void setClientInfo(final String name, final String value) throws SQLClientInfoException { - // MonetDB doesn't support any Client Info Properties yet - addWarning("setClientInfo: client info property name not recognized", "01M07"); + ClientInfo info = new ClientInfo(); + try { + info.set(name, value, getClientInfoAttributeNames().keySet()); + sendClientInfo(info); + SQLWarning warn = info.warnings(); + if (warn != null) + addWarning(warn); + } catch (SQLException e) { + throw info.wrapException(e); + } } /** @@ -1359,13 +1435,29 @@ public class MonetConnection */ @Override public void setClientInfo(final Properties props) throws SQLClientInfoException { - if (props != null) { - for (Entry<Object, Object> entry : props.entrySet()) { - setClientInfo(entry.getKey().toString(), entry.getValue().toString()); + ClientInfo info = new ClientInfo(); + try { + for (String name: props.stringPropertyNames()) { + String value = props.getProperty(name); + info.set(name, value, getClientInfoAttributeNames().keySet()); } + sendClientInfo(info); + SQLWarning warn = info.warnings(); + if (warn != null) + addWarning(warn); + } catch (SQLClientInfoException e) { + throw e; + } catch (SQLException e) { + throw info.wrapException(e); } } + private void sendClientInfo(ClientInfo info) throws SQLException { + String formatted = info.format(); + if (!formatted.isEmpty()) + sendControlCommand("clientinfo " + formatted); + } + //== Java 1.7 methods (JDBC 4.1) /** @@ -2052,16 +2144,28 @@ public class MonetConnection * warning will be the first, otherwise this warning will get * appended to the current warning. * + * @param warning The warning to add + */ + private final void addWarning(SQLWarning warning) { + if (warnings == null) { + warnings = warning; + } else { + warnings.setNextWarning(warning); + } + } + + /** + * Adds a warning to the pile of warnings this Connection object has. + * If there were no warnings (or clearWarnings was called) this + * warning will be the first, otherwise this warning will get + * appended to the current warning. + * * @param reason the warning message * @param sqlstate the SQLState code (5 characters) */ private final void addWarning(final String reason, final String sqlstate) { - final SQLWarning warng = new SQLWarning(reason, sqlstate); - if (warnings == null) { - warnings = warng; - } else { - warnings.setNextWarning(warng); - } + final SQLWarning warning = new SQLWarning(reason, sqlstate); + addWarning(warning); } /** the default number of rows that are (attempted to) read at once */
--- a/src/main/java/org/monetdb/mcl/net/ClientInfo.java +++ b/src/main/java/org/monetdb/mcl/net/ClientInfo.java @@ -9,10 +9,17 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.sql.ClientInfoStatus; import java.sql.SQLClientInfoException; -import java.util.Collections; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.Set; +/** + * Manage ClientInfo properties to track, and help generating a + * @{link SQLClientInfoException} if there is a failure + */ public class ClientInfo { private static final String defaultHostname = findHostname(); @@ -23,9 +30,13 @@ public class ClientInfo { private static final String defaultPid = findPid(); private final Properties props; + private HashMap<String, ClientInfoStatus> problems = null; public ClientInfo() { props = new Properties(); + } + + public void setDefaults() { props.setProperty("ClientHostname", defaultHostname); props.setProperty("ClientLibrary", defaultClientLibrary); props.setProperty("ClientPid", defaultPid); @@ -88,24 +99,56 @@ public class ClientInfo { } public Properties get() { - Properties ret = new Properties(); - ret.putAll(props); - return ret; + return props; } - public boolean set(String name, String value) throws SQLClientInfoException { + public HashMap<String,ClientInfoStatus> getProblems() { + return problems; + } + + public void set(String name, String value, Set<String> known) throws SQLClientInfoException { if (value == null) value = ""; - if (value.contains("\n")) { - Map<String, ClientInfoStatus> map = Collections.singletonMap(name, ClientInfoStatus.REASON_VALUE_INVALID); - throw new SQLClientInfoException(map); - } - if (props.containsKey(name)) { + + if (known != null && !known.contains(name)) { + addProblem(name, ClientInfoStatus.REASON_UNKNOWN_PROPERTY); + } else if (value.contains("\n")) { + addProblem(name, ClientInfoStatus.REASON_VALUE_INVALID); + throw new SQLClientInfoException("Invalid value for Client Info property '" + name + "'", "01M07", problems); + } else { props.setProperty(name, value); - return true; - } else { - return false; } } + public void set(String name, String value) throws SQLClientInfoException { + set(name, value, null); + } + + private void addProblem(String name, ClientInfoStatus status) { + if (problems == null) + problems = new HashMap<>(); + ClientInfoStatus old = problems.get(name); + if (old == null || status.compareTo(old) > 0) + problems.put(name, status); + } + + public SQLClientInfoException wrapException(SQLException e) { + return new SQLClientInfoException(problems, e); + } + + public SQLWarning warnings() { + SQLWarning ret = null; + if (problems == null) + return null; + for (Map.Entry<String, ClientInfoStatus> entry: problems.entrySet()) { + if (!entry.getValue().equals(ClientInfoStatus.REASON_UNKNOWN_PROPERTY)) + continue; + SQLWarning warning = new SQLWarning("unknown client info property: " + entry.getKey(), "01M07"); + if (ret == null) + ret = warning; + else + ret.setNextWarning(warning); + } + return ret; + } }
--- a/src/main/java/org/monetdb/mcl/net/MapiSocket.java +++ b/src/main/java/org/monetdb/mcl/net/MapiSocket.java @@ -25,7 +25,6 @@ import java.net.*; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.sql.SQLClientInfoException; import java.util.*; import javax.net.ssl.SSLException; @@ -118,7 +117,7 @@ public final class MapiSocket { private BufferedMCLWriter writer; /** protocol version of the connection */ private int version; - private ClientInfo clientInfo; + private boolean supportsClientInfo; /** Whether we should follow redirects. * Not sure why this needs to be separate @@ -499,20 +498,8 @@ public final class MapiSocket { String optionsPart = parts.length > 6 ? parts[6] : null; // String binaryPart = parts.length > 7 ? parts[7] : null; - if (parts.length > 9 && target.isClientInfo()) { - clientInfo = new ClientInfo(); - try { - String clientApplication = target.getClientApplication(); - String clientRemark = target.getClientRemark(); - if (!clientApplication.isEmpty()) - clientInfo.set("ApplicationName", clientApplication); - if (!clientRemark.isEmpty()) - clientInfo.set("ClientRemark", clientRemark); - } catch (SQLClientInfoException e) { - String keys = String.join(", ", e.getFailedProperties().keySet()); - throw new MCLException("Could not set ClientInfo properties: " + keys, e); - } - } + if (parts.length > 9) + supportsClientInfo = true; String userResponse; String password = target.getPassword(); @@ -788,15 +775,10 @@ public final class MapiSocket { return target.isDebug(); } - public boolean hasClientInfo() { - return clientInfo != null; + public boolean canClientInfo() { + return supportsClientInfo; } - public ClientInfo getClientInfo() { - if (clientInfo == null) - clientInfo = new ClientInfo(); - return clientInfo; - } /** * Inner class that is used to write data on a normal stream as a