diff src/main/java/org/monetdb/jdbc/MonetConnection.java @ 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 (10 months ago)
parents 778959b2e0a4
children 8c8c423dc619
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 */