changeset 422:8368cbc670bf

Send reply size and time zone during initial handshake
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Tue, 02 Feb 2021 13:50:13 +0100 (2021-02-02)
parents 163b784aa93b
children de2ef68b672f
files src/main/java/org/monetdb/jdbc/MonetConnection.java src/main/java/org/monetdb/mcl/net/HandshakeOptions.java src/main/java/org/monetdb/mcl/net/MapiSocket.java
diffstat 3 files changed, 161 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetConnection.java
+++ b/src/main/java/org/monetdb/jdbc/MonetConnection.java
@@ -35,6 +35,7 @@ import java.util.concurrent.Executor;
 
 import org.monetdb.mcl.io.BufferedMCLReader;
 import org.monetdb.mcl.io.BufferedMCLWriter;
+import org.monetdb.mcl.net.HandshakeOptions;
 import org.monetdb.mcl.net.MapiSocket;
 import org.monetdb.mcl.parser.HeaderLineParser;
 import org.monetdb.mcl.parser.MCLParseException;
@@ -257,6 +258,14 @@ public class MonetConnection
 			server.setDatabase(database);
 		server.setLanguage(language);
 
+		HandshakeOptions handshakeOptions = new HandshakeOptions();
+		final Calendar cal = Calendar.getInstance();
+		int offsetMillis = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
+		int offsetSeconds = offsetMillis / 1000;
+		handshakeOptions.setTimeZone(offsetSeconds);
+		handshakeOptions.setReplySize(DEF_FETCHSIZE);
+		server.setHandshakeOptions(handshakeOptions);
+
 		// we're debugging here... uhm, should be off in real life
 		if (debug) {
 			try {
@@ -336,20 +345,27 @@ public class MonetConnection
 			lang = LANG_UNKNOWN;
 		}
 
+		if (!handshakeOptions.mustSendReplySize()) {
+			// Initially, it had to be sent. If it no more needs to be sent now,
+			// it must have been sent during the auth challenge/response.
+			// Record the value it was set to.
+			this.curReplySize = handshakeOptions.getReplySize();
+		}
+
 		// the following initialisers are only valid when the language is SQL...
 		if (lang == LANG_SQL) {
 			// enable auto commit
 			setAutoCommit(true);
 
-			// set our time zone on the server
-			final Calendar cal = Calendar.getInstance();
-			int offset = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET);
-			offset /= (60 * 1000); // milliseconds to minutes
-			String tz = offset < 0 ? "-" : "+";
-			tz += (Math.abs(offset) / 60 < 10 ? "0" : "") + (Math.abs(offset) / 60) + ":";
-			offset -= (offset / 60) * 60;
-			tz += (offset < 10 ? "0" : "") + offset;
-			sendIndependentCommand("SET TIME ZONE INTERVAL '" + tz + "' HOUR TO MINUTE");
+			// set our time zone on the server, if we haven't already
+			if (handshakeOptions.mustSendTimeZone()) {
+				int offsetMinutes = handshakeOptions.getTimeZone() / 60;
+				String tz = offsetMinutes < 0 ? "-" : "+";
+				tz += (Math.abs(offsetMinutes) / 60 < 10 ? "0" : "") + (Math.abs(offsetMinutes) / 60) + ":";
+				offsetMinutes -= (offsetMinutes / 60) * 60;
+				tz += (offsetMinutes < 10 ? "0" : "") + offsetMinutes;
+				sendIndependentCommand("SET TIME ZONE INTERVAL '" + tz + "' HOUR TO MINUTE");
+			}
 		}
 
 		// we're absolutely not closed, since we're brand new
new file mode 100644
--- /dev/null
+++ b/src/main/java/org/monetdb/mcl/net/HandshakeOptions.java
@@ -0,0 +1,98 @@
+package org.monetdb.mcl.net;
+
+/** Keep track of MAPI handshake options.
+ *
+ * Recent server versions allow you to send configuration information during
+ * the authentication handshake so no additional round trips are necessary
+ * when that has completed.
+ *
+ * This class keeps track of the options to send, and whether they have already
+ * been sent.
+ */
+public class HandshakeOptions {
+
+	// public Boolean autoCommit;
+	int replySize;
+	// public Integer replySize;
+	// public Integer ColumnarProtocol;
+	int timeZone;
+
+	boolean mustSendReplySize;
+	boolean mustSendTimeZone;
+
+	public int getReplySize() {
+		return replySize;
+	}
+
+	public void setReplySize(int replySize) {
+		this.replySize = replySize;
+		this.mustSendReplySize = true;
+	}
+
+	public boolean mustSendReplySize() {
+		return mustSendReplySize;
+	}
+
+	public void mustSendReplySize(boolean mustSendReplySize) {
+		this.mustSendReplySize = mustSendReplySize;
+	}
+
+	public int getTimeZone() {
+		return timeZone;
+	}
+
+	public void setTimeZone(int timeZone) {
+		this.timeZone = timeZone;
+		this.mustSendTimeZone = true;
+	}
+
+	public boolean mustSendTimeZone() {
+		return mustSendTimeZone;
+	}
+
+	public void mustSendTimeZone(boolean mustSendTimeZone) {
+		this.mustSendTimeZone = mustSendTimeZone;
+	}
+
+	public String formatResponse(int serverLevel) {
+		StringBuilder opts = new StringBuilder(100);
+		if (mustSendReplySize()) {
+			formatOption(opts, Level.ReplySize, serverLevel, replySize);
+			mustSendReplySize(false);
+		}
+		if (mustSendTimeZone()) {
+			formatOption(opts, Level.TimeZone, serverLevel, timeZone);
+			mustSendTimeZone(false);
+		}
+
+		return opts.toString();
+	}
+
+	private void formatOption(StringBuilder opts, Level level, int serverLevel, int value) {
+		if (!level.isSupported(serverLevel))
+			return;
+		if (opts.length() > 0) {
+			opts.append(",");
+		}
+		opts.append(level.field);
+		opts.append("=");
+		opts.append(value);
+	}
+
+	public enum Level {
+		ReplySize("reply_size", 2),
+		TimeZone("time_zone", 5);
+
+		private final int level;
+		private final String field;
+
+		Level(String field, int level) {
+			this.field = field;
+			this.level = level;
+		}
+
+		public boolean isSupported(int serverLevel) {
+			return this.level < serverLevel;
+		}
+	}
+}
--- a/src/main/java/org/monetdb/mcl/net/MapiSocket.java
+++ b/src/main/java/org/monetdb/mcl/net/MapiSocket.java
@@ -122,6 +122,9 @@ public class MapiSocket {	/* cannot (yet
 	/** A short in two bytes for holding the block size in bytes */
 	private final byte[] blklen = new byte[2];
 
+	/** Options that can be sent during the auth handshake if the server supports it */
+	private HandshakeOptions handshakeOptions;
+
 	/**
 	 * Constructs a new MapiSocket.
 	 */
@@ -533,11 +536,33 @@ public class MapiSocket {	/* cannot (yet
 				}
 
 				// compose and return response
-				return "BIG:"	// JVM byte-order is big-endian
-					+ username + ":"
-					+ pwhash + ":"
-					+ language + ":"
-					+ (database == null ? "" : database) + ":";
+				String response = "BIG:"    // JVM byte-order is big-endian
+						+ username + ":"
+						+ pwhash + ":"
+						+ language + ":"
+						+ (database == null ? "" : database) + ":";
+				if (chaltok.length > 6) {
+					// this ':' delimits the FILETRANS field, currently empty because we don't support it.
+					response += ":";
+
+					// if supported, send handshake options
+					for (String part : chaltok[6].split(",")) {
+						if (part.startsWith("sql=") && handshakeOptions != null) {
+							int level;
+							try {
+								level = Integer.parseInt(chaltok[6].substring(4));
+							} catch (NumberFormatException e) {
+								throw new MCLParseException("Invalid handshake level: " + chaltok[6]);
+							}
+							response += handshakeOptions.formatResponse(level);
+							break;
+						}
+					}
+					// this ':' delimits the handshake options field.
+					response += ":";
+
+				}
+				return response;
 			default:
 				throw new MCLException("Unsupported protocol version: " + version);
 		}
@@ -687,6 +712,14 @@ public class MapiSocket {	/* cannot (yet
 			log.flush();
 	}
 
+	public void setHandshakeOptions(HandshakeOptions handshakeOptions) {
+		this.handshakeOptions = handshakeOptions;
+	}
+
+	public HandshakeOptions getHandshakeOptions() {
+		return handshakeOptions;
+	}
+
 	/**
 	 * Inner class that is used to write data on a normal stream as a
 	 * blocked stream.  A call to the flush() method will write a