Mercurial > hg > monetdb-java
changeset 671:fade6c6960cc
Refactor Handshake options
It was not clear and there were obscure bugs.
author | Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com> |
---|---|
date | Fri, 28 Oct 2022 16:16:00 +0200 (2022-10-28) |
parents | 3034312c1eda |
children | cd6193bc5956 |
files | src/main/java/org/monetdb/jdbc/MonetConnection.java src/main/java/org/monetdb/mcl/net/HandshakeOption.java src/main/java/org/monetdb/mcl/net/HandshakeOptions.java src/main/java/org/monetdb/mcl/net/MapiSocket.java |
diffstat | 4 files changed, 169 insertions(+), 164 deletions(-) [+] |
line wrap: on
line diff
--- a/src/main/java/org/monetdb/jdbc/MonetConnection.java +++ b/src/main/java/org/monetdb/jdbc/MonetConnection.java @@ -36,8 +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.HandshakeOptions; -import org.monetdb.mcl.net.HandshakeOptions.Setting; +import org.monetdb.mcl.net.HandshakeOption; import org.monetdb.mcl.net.MapiSocket; import org.monetdb.mcl.parser.HeaderLineParser; import org.monetdb.mcl.parser.MCLParseException; @@ -166,7 +165,12 @@ public class MonetConnection MonetConnection(final Properties props) throws SQLException, IllegalArgumentException { - // for debug: System.out.println("New connection object. Received properties are: " + props.toString()); + HandshakeOption.AutoCommit autoCommitSetting = new HandshakeOption.AutoCommit(true); + HandshakeOption.ReplySize replySizeSetting = new HandshakeOption.ReplySize(DEF_FETCHSIZE); + HandshakeOption.SizeHeader sizeHeaderSetting = new HandshakeOption.SizeHeader(true); + HandshakeOption.TimeZone timeZoneSetting = new HandshakeOption.TimeZone(0); + + // for debug: System.out.println("New connection object. Received properties are: " + props.toString()); // get supported property values from the props argument. // When a value is found add it to the internal conn_props list for use by getClientInfo(). this.hostname = props.getProperty("host"); @@ -211,10 +215,10 @@ public class MonetConnection conn_props.setProperty("hash", hash); String autocommit_prop = props.getProperty("autocommit"); - boolean initial_autocommit = true; if (autocommit_prop != null) { - initial_autocommit = Boolean.parseBoolean(autocommit_prop); - conn_props.setProperty("autocommit", Boolean.toString(initial_autocommit)); + boolean ac = Boolean.parseBoolean(autocommit_prop); + autoCommitSetting.set(ac); + conn_props.setProperty("autocommit", Boolean.toString(ac)); } final String fetchsize_prop = props.getProperty("fetchsize"); @@ -222,7 +226,7 @@ public class MonetConnection try { int fetchsize = Integer.parseInt(fetchsize_prop); if (fetchsize > 0 || fetchsize == -1) { - this.defaultFetchSize = fetchsize; + replySizeSetting.set(fetchsize); conn_props.setProperty("fetchsize", fetchsize_prop); } else { addWarning("Fetch size must either be positive or -1. Value " + fetchsize + " ignored", "M1M05"); @@ -291,16 +295,18 @@ public class MonetConnection server.setDatabase(database); server.setLanguage(language); + // calculate our time zone offset final Calendar cal = Calendar.getInstance(); int offsetMillis = cal.get(Calendar.ZONE_OFFSET) + cal.get(Calendar.DST_OFFSET); int offsetSeconds = offsetMillis / 1000; - final HandshakeOptions handshakeOptions = new HandshakeOptions(); - handshakeOptions.set(Setting.AutoCommit, initial_autocommit ? 1 : 0); - handshakeOptions.set(Setting.TimeZone, offsetSeconds); - handshakeOptions.set(Setting.ReplySize, defaultFetchSize); -// handshakeOptions.set(Setting.SizeHeader, 1); - server.setHandshakeOptions(handshakeOptions); - autoCommit = initial_autocommit; + timeZoneSetting.set(offsetSeconds); + + server.setHandshakeOptions(new HandshakeOption[] { + autoCommitSetting, + replySizeSetting, + sizeHeaderSetting, + timeZoneSetting, + }); // we're debugging here... uhm, should be off in real life if (debug) { @@ -381,50 +387,21 @@ public class MonetConnection lang = LANG_UNKNOWN; } - // The reply size is checked before every query and adjusted if - // necessary. Update our current belief of what the server is set to. - if (handshakeOptions.wasSentInHandshake(HandshakeOptions.Setting.ReplySize)) { - this.curReplySize = handshakeOptions.get(HandshakeOptions.Setting.ReplySize); + // Now take care of any handshake options not handled during the handshake + if (replySizeSetting.isSent()) { + this.curReplySize = replySizeSetting.get(); } - - for (Setting setting : new Setting[] { Setting.SizeHeader }) { - if (handshakeOptions.mustSend(setting)) { - Integer value = handshakeOptions.get(setting); // guaranteed by mustSend to be non-null - String command = String.format("%s %d", setting.getXCommand(), value); - sendControlCommand(command); - } - } - - // the following initialisers are only valid when the language is SQL... + this.defaultFetchSize = replySizeSetting.get(); if (lang == LANG_SQL) { - if (handshakeOptions.mustSend(Setting.AutoCommit)) { - setAutoCommit(handshakeOptions.get(Setting.AutoCommit) != 0); + if (autoCommitSetting.mustSend(autoCommit)) { + setAutoCommit(autoCommitSetting.get()); } - - // set our time zone on the server, if we haven't already - if (handshakeOptions.mustSend(Setting.TimeZone)) { - final StringBuilder tz = new StringBuilder(64); - tz.append("SET TIME ZONE INTERVAL '"); - int offsetMinutes = handshakeOptions.get(Setting.TimeZone) / 60; - if (offsetMinutes < 0) { - tz.append('-'); - offsetMinutes = -offsetMinutes; // make it positive - } else { - tz.append('+'); - } - int offsetHours = offsetMinutes / 60; - if (offsetHours < 10) - tz.append('0'); - tz.append(offsetHours).append(':'); - offsetMinutes -= offsetHours * 60; - if (offsetMinutes < 10) - tz.append('0'); - tz.append(offsetMinutes).append("' HOUR TO MINUTE"); - sendIndependentCommand(tz.toString()); + if (sizeHeaderSetting.mustSend(false)) { + sendControlCommand("sizeheader 1"); } - - // set sizeheader to 1 to enable sending "typesizes" info by the server (see mapi_set_size_header() in mapi.c) - sendControlCommand("sizeheader 1"); + if (timeZoneSetting.mustSend(0)) { + setTimezone(timeZoneSetting.get()); + } } // we're absolutely not closed, since we're brand new @@ -1739,6 +1716,27 @@ public class MonetConnection return downloadHandler; } + public void setTimezone(int offsetSeconds) throws SQLException { + final StringBuilder tz = new StringBuilder(64); + tz.append("SET TIME ZONE INTERVAL '"); + int offsetMinutes = offsetSeconds / 60; + if (offsetMinutes < 0) { + tz.append('-'); + offsetMinutes = -offsetMinutes; // make it positive + } else { + tz.append('+'); + } + int offsetHours = offsetMinutes / 60; + if (offsetHours < 10) + tz.append('0'); + tz.append(offsetHours).append(':'); + offsetMinutes -= offsetHours * 60; + if (offsetMinutes < 10) + tz.append('0'); + tz.append(offsetMinutes).append("' HOUR TO MINUTE"); + sendIndependentCommand(tz.toString()); + } + /** * Local helper method to test whether the Connection object is closed * When closed it throws an SQLException
new file mode 100644 --- /dev/null +++ b/src/main/java/org/monetdb/mcl/net/HandshakeOption.java @@ -0,0 +1,100 @@ +package org.monetdb.mcl.net; + +public abstract class HandshakeOption<T> { + protected final int level; + protected final String handshakeField; + boolean sent = false; + T desiredValue; + + protected HandshakeOption(int level, String handshakeField, T desiredValue) { + if (desiredValue == null) { + throw new IllegalArgumentException("initial value must not be null"); + } + this.level = level; + this.handshakeField = handshakeField; + this.desiredValue = desiredValue; + } + + public void set(T newValue) { + if (newValue == null) { + throw new IllegalArgumentException("new value must not be null"); + } + desiredValue = newValue; + } + + public T get() { + return desiredValue; + } + + public int getLevel() { + return level; + } + + public String getFieldName() { + return handshakeField; + } + + public boolean isSent() { + return sent; + } + + public void setSent(boolean b) { + sent = b; + } + + public boolean mustSend(T currentValue) { + if (sent) + return false; + if (currentValue.equals(desiredValue)) + return false; + return true; + } + + abstract long numericValue(); + + protected static class BooleanOption extends HandshakeOption<Boolean> { + protected BooleanOption(int level, String name, Boolean initialValue) { + super(level, name, initialValue); + } + + @Override + long numericValue() { + return desiredValue ? 1 : 0; + } + } + + public static class AutoCommit extends BooleanOption { + public AutoCommit(boolean autoCommit) { + super(1, "auto_commit", autoCommit); + } + } + + public static class ReplySize extends HandshakeOption<Integer> { + public ReplySize(int size) { + super(2, "reply_size", size); + } + + @Override + long numericValue() { + return desiredValue; + } + } + + public static class SizeHeader extends BooleanOption { + public SizeHeader(boolean sendHeader) { + super(3, "size_header", sendHeader); + set(sendHeader); + } + } + + public static class TimeZone extends HandshakeOption<Integer> { + public TimeZone(int offset) { + super(5, "time_zone", offset); + } + + @Override + long numericValue() { + return desiredValue; + } + } +}
deleted file mode 100644 --- a/src/main/java/org/monetdb/mcl/net/HandshakeOptions.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - * - * Copyright 1997 - July 2008 CWI, August 2008 - 2022 MonetDB B.V. - */ - -package org.monetdb.mcl.net; - -import java.util.HashMap; -import java.util.Map; - -/** Keep track of MAPI handshake options. - * - * Recent server versions (from 2021) 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 values themselves, and also of whether or not they should still be sent. - */ -final public class HandshakeOptions { - HashMap<Setting,Integer> options = new HashMap<>(); - int handshakeLevel = 0; - - public void set(Setting setting, int value) { - options.put(setting, value); - } - - public Integer get(Setting setting) { - return options.get(setting); - } - - public boolean wasSentInHandshake(Setting setting) { - return setting.isSupported(this.handshakeLevel); - } - - public boolean mustSend(Setting setting) { - if (wasSentInHandshake(setting)) { - return false; - } - Integer value = options.get(setting); - return value != null && value != setting.defaultValue; - } - - public String formatHandshakeResponse(int serverLevel) { - StringBuilder opts = new StringBuilder(100); - - for (Map.Entry<Setting, Integer> entry: options.entrySet()) { - Setting setting = entry.getKey(); - Integer value = entry.getValue(); - if (setting.isSupported(serverLevel)) { - if (opts.length() > 0) { - opts.append(","); - } - opts.append(setting.field); - opts.append("="); - opts.append(value); - } - } - - this.handshakeLevel = serverLevel; - - return opts.toString(); - } - - public enum Setting { - AutoCommit("auto_commit", 1, 1), - ReplySize("reply_size", 2, 100), - SizeHeader("size_header", "sizeheader", 3, 0), - // ColumnarProtocol("columnar_protocol", 4), - TimeZone("time_zone", 5, 0), - ; - - private final int level; - private final String field; - private final String xcommand; - private final int defaultValue; - - Setting(String field, int level, int defaultValue) { - this(field, field, level, defaultValue); - } - - Setting(String field, String xcommand, int level, int defaultValue) { - this.field = field; - this.xcommand = xcommand; - this.level = level; - this.defaultValue = defaultValue; - } - - public boolean isSupported(int serverLevel) { - return this.level < serverLevel; - } - - public String getXCommand() { - return xcommand; - } - - public Integer getDefaultValue() { - return defaultValue; - } - } -}
--- a/src/main/java/org/monetdb/mcl/net/MapiSocket.java +++ b/src/main/java/org/monetdb/mcl/net/MapiSocket.java @@ -125,7 +125,7 @@ public class MapiSocket { /* cannot (yet private final byte[] blklen = new byte[2]; /** Options that can be sent during the auth handshake if the server supports it */ - private HandshakeOptions handshakeOptions; + private HandshakeOption[] handshakeOptions; /** * Constructs a new MapiSocket. @@ -562,7 +562,21 @@ public class MapiSocket { /* cannot (yet } catch (NumberFormatException e) { throw new MCLParseException("Invalid handshake level: " + chaltok[6]); } - response += handshakeOptions.formatHandshakeResponse(level); + boolean first = true; + for (HandshakeOption opt: handshakeOptions) { + if (opt.getLevel() < level) { + // server supports it + if (first) { + first = false; + } else { + response += ","; + } + String key = opt.getFieldName(); + long value = opt.numericValue(); + response += opt.getFieldName() + "=" + value; + opt.setSent(true); + } + } break; } } @@ -718,14 +732,10 @@ public class MapiSocket { /* cannot (yet log.flush(); } - public void setHandshakeOptions(HandshakeOptions handshakeOptions) { + public void setHandshakeOptions(HandshakeOption[] handshakeOptions) { this.handshakeOptions = handshakeOptions; } - public HandshakeOptions getHandshakeOptions() { - return handshakeOptions; - } - /** * For internal use *