diff --git a/beeline/src/java/org/apache/hive/beeline/BeeLine.java b/beeline/src/java/org/apache/hive/beeline/BeeLine.java index 5e6e9ba..807d22f 100644 --- a/beeline/src/java/org/apache/hive/beeline/BeeLine.java +++ b/beeline/src/java/org/apache/hive/beeline/BeeLine.java @@ -297,6 +297,12 @@ .withDescription("the JDBC URL to connect to") .create('u')); + // -r + options.addOption(OptionBuilder + .withLongOpt("reconnect") + .withDescription("Reconnect to last saved connect url (in conjunction with !save)") + .create('r')); + // -n options.addOption(OptionBuilder .hasArg() @@ -760,6 +766,11 @@ int initArgs(String[] args) { String comForDebug = constructCmd(url, user, pass, driver, true); debug("issuing: " + comForDebug); dispatch(com); + } else if (cl.hasOption("reconnect")) { + String com = constructCmd(BeeLineOpts.LAST_CONNECTED_URL,user, pass, driver, false); + String comForDebug = constructCmd(BeeLineOpts.LAST_CONNECTED_URL,user, pass, driver, true); + debug("issuing: " + comForDebug); + dispatch(com); } // now load properties files diff --git a/beeline/src/java/org/apache/hive/beeline/BeeLineOpts.java b/beeline/src/java/org/apache/hive/beeline/BeeLineOpts.java index 7a6ee5f..b8c2cf2 100644 --- a/beeline/src/java/org/apache/hive/beeline/BeeLineOpts.java +++ b/beeline/src/java/org/apache/hive/beeline/BeeLineOpts.java @@ -28,6 +28,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Arrays; @@ -36,6 +38,7 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.TreeSet; import jline.Terminal; @@ -51,10 +54,12 @@ public static final String DEFAULT_ISOLATION_LEVEL = "TRANSACTION_REPEATABLE_READ"; public static final String PROPERTY_PREFIX = "beeline."; + public static final String SAVED_VAR_PREFIX = PROPERTY_PREFIX + "vars."; public static final String PROPERTY_NAME_EXIT = PROPERTY_PREFIX + "system.exit"; public static final String DEFAULT_NULL_STRING = "NULL"; public static final char DEFAULT_DELIMITER_FOR_DSV = '|'; + public static String LAST_CONNECTED_URL = "lastConnectedUrl"; private final BeeLine beeLine; private boolean autosave = false; @@ -102,6 +107,20 @@ private Map hiveConfVariables = new HashMap(); private boolean helpAsked; + // New place to save arbitrary named variables using !set + private Map savedVars = new HashMap(); + + private TreeSet cachedPropertyNameSet = null; + + @Retention(RetentionPolicy.RUNTIME) + public @interface Ignore { + // marker annotations for functions that Reflector should ignore / pretend it does not exist + + // NOTE: BeeLineOpts uses Reflector in an extensive way to call getters and setters on itself + // If you want to add any getters or setters to this class, but not have it interfere with + // saved variables in beeline.properties, careful use of this marker is needed. + } + public BeeLineOpts(BeeLine beeLine, Properties props) { this.beeLine = beeLine; if (terminal.getWidth() > 0) { @@ -177,21 +196,33 @@ public void save(OutputStream out) throws IOException { String[] propertyNames() throws IllegalAccessException, InvocationTargetException { - TreeSet names = new TreeSet(); + Set names = propertyNamesSet(); // make sure we initialize if necessary + return names.toArray(new String[names.size()]); + } - // get all the values from getXXX methods - Method[] m = getClass().getDeclaredMethods(); - for (int i = 0; m != null && i < m.length; i++) { - if (!(m[i].getName().startsWith("get"))) { - continue; + Set propertyNamesSet() + throws IllegalAccessException, InvocationTargetException { + if (cachedPropertyNameSet == null){ + TreeSet names = new TreeSet(); + + // get all the values from getXXX methods + Method[] m = getClass().getDeclaredMethods(); + for (int i = 0; m != null && i < m.length; i++) { + if (!(m[i].getName().startsWith("get"))) { + continue; + } + if (m[i].getAnnotation(Ignore.class) != null){ + continue; // not actually a getter + } + if (m[i].getParameterTypes().length != 0) { + continue; + } + String propName = m[i].getName().substring(3).toLowerCase(); + names.add(propName); } - if (m[i].getParameterTypes().length != 0) { - continue; - } - String propName = m[i].getName().substring(3).toLowerCase(); - names.add(propName); + cachedPropertyNameSet = names; } - return names.toArray(new String[names.size()]); + return cachedPropertyNameSet; } @@ -206,6 +237,11 @@ public Properties toProperties() props.setProperty(PROPERTY_PREFIX + names[i], o == null ? "" : o.toString()); } + for (Map.Entry savedVar : savedVars.entrySet()){ + Object o = savedVar.getValue(); + props.setProperty(SAVED_VAR_PREFIX + savedVar.getKey(), + o == null? "" : o.toString()); + } beeLine.debug("properties: " + props.toString()); return props; } @@ -247,7 +283,10 @@ public void loadProperties(Properties props) { // fix for sf.net bug 879422 continue; } - if (key.startsWith(PROPERTY_PREFIX)) { + if (key.startsWith(SAVED_VAR_PREFIX)){ + getSavedVars().put(key.substring(SAVED_VAR_PREFIX.length()), + props.getProperty(key)); + } else if (key.startsWith(PROPERTY_PREFIX)) { set(key.substring(PROPERTY_PREFIX.length()), props.getProperty(key)); } @@ -260,7 +299,12 @@ public void set(String key, String value) { public boolean set(String key, String value, boolean quiet) { try { - beeLine.getReflector().invoke(this, "set" + key, new Object[] {value}); + if (propertyNamesSet().contains(key)){ + beeLine.getReflector().invoke(this, "set" + key, new Object[] {value}); + } else { + // This is an arbitrary named-var, not a beeline property itself. + getSavedVars().put(key,value); + } return true; } catch (Exception e) { if (!quiet) { @@ -496,6 +540,7 @@ public int getMaxHeight() { return maxHeight; } + @Ignore public File getPropertiesFile() { return rcFile; } @@ -520,6 +565,7 @@ public void setAllowMultiLineCommand(boolean allowMultiLineCommand) { * Use getNullString() to get the null string to be used. * @return true if null representation should be an empty string */ + @Ignore public boolean getNullEmptyString() { return nullEmptyString; } @@ -567,5 +613,14 @@ public void setHelpAsked(boolean helpAsked) { public boolean isHelpAsked() { return helpAsked; } + + @Ignore + public synchronized Map getSavedVars() { + // Handles all the beeline.vars.* saved variables + if (savedVars == null){ + savedVars = new HashMap(); + } + return savedVars; + } } diff --git a/beeline/src/java/org/apache/hive/beeline/Commands.java b/beeline/src/java/org/apache/hive/beeline/Commands.java index 0178333..7e3d33b 100644 --- a/beeline/src/java/org/apache/hive/beeline/Commands.java +++ b/beeline/src/java/org/apache/hive/beeline/Commands.java @@ -36,6 +36,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.net.URLClassLoader; import java.sql.CallableStatement; @@ -53,6 +55,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeSet; @@ -310,7 +313,19 @@ public boolean dropall(String line) { public boolean reconnect(String line) { if (beeLine.getDatabaseConnection() == null || beeLine.getDatabaseConnection().getUrl() == null) { - return beeLine.error(beeLine.loc("no-current-connection")); + // First, let's try connecting using the last successful url - if that fails, then we error out. + String lastConnectedUrl = beeLine.getOpts().getSavedVars().get(BeeLineOpts.LAST_CONNECTED_URL); + if (lastConnectedUrl != null){ + Properties props = new Properties(); + props.setProperty("url",lastConnectedUrl); + try { + return connect(props); + } catch (IOException e) { + return beeLine.error(e); + } + } else { + return beeLine.error(beeLine.loc("no-current-connection")); + } } beeLine.info(beeLine.loc("reconnecting", beeLine.getDatabaseConnection().getUrl())); try { @@ -1299,7 +1314,34 @@ public boolean connect(String line) throws Exception { Properties props = new Properties(); if (url != null) { - props.setProperty("url", url); + + boolean useSavedUrl = false; + // If the url passed to us is a valid url with a protocol, we use it as-is + // Otherwise, we assume it is a named var, and try to load it from the saved named vars + try { + URI tryParse = new URI(url); + if (tryParse.getScheme() == null){ + useSavedUrl = true; + } + } catch (URISyntaxException e){ + useSavedUrl = true; + } + + if (useSavedUrl){ + // Use url param indirectly - as the name of a var that contains the url + Map savedVars = beeLine.getOpts().getSavedVars(); + if ((savedVars != null) && savedVars.containsKey(url)){ + props.setProperty("url",savedVars.get(url)); + } else{ + // This will most-likely lead to an error, since it didn't match + // a url pattern, and isn't a saved var either. We'll retain base + // behaviour and let it fail out as it would normally. + props.setProperty("url",url); + } + } else { + // Use url param directly + props.setProperty("url", url); + } } if (driver != null) { props.setProperty("driver", driver); @@ -1398,6 +1440,7 @@ public boolean connect(Properties props) throws IOException { beeLine.runInit(); beeLine.setCompletions(); + beeLine.getOpts().getSavedVars().put(BeeLineOpts.LAST_CONNECTED_URL, url); return true; } catch (SQLException sqle) { beeLine.getDatabaseConnections().remove();