diff --git beeline/src/java/org/apache/hive/beeline/Commands.java beeline/src/java/org/apache/hive/beeline/Commands.java index 387861b..ffd1775 100644 --- beeline/src/java/org/apache/hive/beeline/Commands.java +++ beeline/src/java/org/apache/hive/beeline/Commands.java @@ -1128,22 +1128,7 @@ private boolean execute(String line, boolean call, boolean entireLineAsCommand) } line = line.trim(); - List cmdList = new ArrayList(); - if (entireLineAsCommand) { - cmdList.add(line); - } else { - StringBuffer command = new StringBuffer(); - for (String cmdpart: line.split(";")) { - if (cmdpart.endsWith("\\")) { - command.append(cmdpart.substring(0, cmdpart.length() -1)).append(";"); - continue; - } else { - command.append(cmdpart); - } - cmdList.add(command.toString()); - command.setLength(0); - } - } + List cmdList = getCmdList(line, entireLineAsCommand); for (int i = 0; i < cmdList.size(); i++) { String sql = cmdList.get(i).trim(); if (sql.length() != 0) { @@ -1155,6 +1140,79 @@ private boolean execute(String line, boolean call, boolean entireLineAsCommand) 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 semicolons that are placed within + * quotations. It iterates through each character in the line and checks to see if it is a ;, ', + * or " + */ + private List getCmdList(String line, boolean entireLineAsCommand) { + List cmdList = new ArrayList(); + if (entireLineAsCommand) { + cmdList.add(line); + } else { + StringBuffer command = new StringBuffer(); + + boolean hasUnterminatedDoubleQuote = false; + boolean hasUntermindatedSingleQuote = false; + + int lastSemiColonIndex = 0; + char[] lineChars = line.toCharArray(); + + boolean wasPrevEscape = false; + int index = 0; + for (; index < lineChars.length; index++) { + switch (lineChars[index]) { + case '\'': + if (!hasUnterminatedDoubleQuote && !wasPrevEscape) { + hasUntermindatedSingleQuote = !hasUntermindatedSingleQuote; + } + wasPrevEscape = false; + break; + case '\"': + if (!hasUntermindatedSingleQuote && !wasPrevEscape) { + hasUnterminatedDoubleQuote = !hasUnterminatedDoubleQuote; + } + wasPrevEscape = false; + break; + case ';': + if (!hasUnterminatedDoubleQuote && !hasUntermindatedSingleQuote) { + addCmdPart(cmdList, command, line.substring(lastSemiColonIndex, index)); + lastSemiColonIndex = index + 1; + } + wasPrevEscape = false; + break; + case '\\': + wasPrevEscape = true; + break; + default: + wasPrevEscape = false; + break; + } + } + // if the line doesn't end with a ; or if the line is empty, add the cmd part + if (lastSemiColonIndex != index || lineChars.length == 0) { + addCmdPart(cmdList, command, line.substring(lastSemiColonIndex, 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 cmdList, StringBuffer command, String cmdpart) { + if (cmdpart.endsWith("\\")) { + command.append(cmdpart.substring(0, cmdpart.length() - 1)).append(";"); + return; + } else { + command.append(cmdpart); + } + cmdList.add(command.toString()); + command.setLength(0); + } + private Runnable createLogRunnable(Statement statement) { if (statement instanceof HiveStatement) { final HiveStatement hiveStatement = (HiveStatement) statement; diff --git itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeeLineWithArgs.java itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeeLineWithArgs.java index ecfeddb..3b40d71 100644 --- itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeeLineWithArgs.java +++ itests/hive-unit/src/test/java/org/apache/hive/beeline/TestBeeLineWithArgs.java @@ -863,4 +863,25 @@ public void testConnectionWithURLParams() throws Throwable { testScriptFile( SCRIPT_TEXT, EXPECTED_PATTERN, true, argList); } + + /** + * Test that Beeline queries don't treat semicolons inside quotations as query-ending characters. + */ + @Test + public void testQueryNonEscapedSemiColon() throws Throwable { + String SCRIPT_TEXT = "drop table if exists nonEscapedSemiColon;create table nonEscapedSemiColon " + + "(key int) ROW FORMAT DELIMITED FIELDS TERMINATED BY ';';show tables;"; + final String EXPECTED_PATTERN = " nonEscapedSemiColon "; + List argList = getBaseArgs(miniHS2.getBaseJdbcURL()); + testScriptFile(SCRIPT_TEXT, EXPECTED_PATTERN, true, argList); + } + + @Test + public void testSelectQueryWithNonEscapedSemiColon() throws Throwable { + String SCRIPT_TEXT = "select ';', \"';'\", '\";\"', '\\';', ';\\'', '\\\";', ';\\\"' from " + tableName + ";"; + final String EXPECTED_PATTERN = ";\t';'\t\";\"\t';\t;'\t\";\t;\""; + List argList = getBaseArgs(miniHS2.getBaseJdbcURL()); + argList.add("--outputformat=tsv2"); + testScriptFile(SCRIPT_TEXT, EXPECTED_PATTERN, true, argList); + } } diff --git ql/src/java/org/apache/hadoop/hive/ql/exec/DDLTask.java ql/src/java/org/apache/hadoop/hive/ql/exec/DDLTask.java index 2b8d6a7..39ac7d6 100644 --- ql/src/java/org/apache/hadoop/hive/ql/exec/DDLTask.java +++ ql/src/java/org/apache/hadoop/hive/ql/exec/DDLTask.java @@ -4346,7 +4346,7 @@ private String escapeHiveCommand(String str) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < str.length(); i ++) { char c = str.charAt(i); - if (c == '\'' || c == ';') { + if (c == '\'') { sb.append('\\'); } sb.append(c);