Skip to content

Commit

Permalink
[SQLLINE-237] Allow several SQL statements on the same line, separate…
Browse files Browse the repository at this point in the history
…d by ';'

Use '!set incremental false' inside tests, to avoid failures if user's
sqlline.properties has incremental=true.

Add a function, 'String flush(StringBuilder)', to return and clear a
StringBuilder's contents.
  • Loading branch information
snuyanzin authored and julianhyde committed May 25, 2019
1 parent a689205 commit b8ace84
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 47 deletions.
78 changes: 51 additions & 27 deletions src/main/java/sqlline/Commands.java
Expand Up @@ -929,32 +929,53 @@ private void execute(String line, boolean call, DispatchCallback callback) {
return;
}

if (line.trim().endsWith(";")) {
line = line.trim();
line = line.substring(0, line.length() - 1);
}

if (!sqlLine.assertConnection()) {
callback.setToFailure();
return;
}

String sql = line;

if (sql.startsWith(SqlLine.COMMAND_PREFIX)) {
sql = sql.substring(1);
String fullLine = line;
if (fullLine.startsWith(SqlLine.COMMAND_PREFIX)) {
fullLine = fullLine.substring(1);
}

String prefix = call ? "call" : "sql";
final String prefix = call ? "call" : "sql";
if (fullLine.startsWith(prefix)) {
fullLine = fullLine.substring(prefix.length());
}

if (sql.startsWith(prefix)) {
sql = sql.substring(prefix.length());
final StringBuilder sql2execute = new StringBuilder();
for (String sqlItem : fullLine.split(";")) {
sql2execute.append(sqlItem).append(";");
if (sqlLine.isOneLineComment(sql2execute.toString())
|| isSqlContinuationRequired(sql2execute.toString())) {
continue;
}
final String sql = skipLast(flush(sql2execute));
executeSingleQuery(sql, call, callback);
}
if (!callback.isFailure()) {
callback.setToSuccess();
}
}

/** Returns the contents of a {@link StringBuilder} and clears the builder. */
static String flush(StringBuilder buf) {
final String s = buf.toString();
buf.setLength(0);
return s;
}

/** Returns a string with the last character removed. */
private static String skipLast(String s) {
return s.substring(0, s.length() - 1);
}

private void executeSingleQuery(String sql, boolean call,
DispatchCallback callback) {
// batch statements?
if (sqlLine.getBatch() != null) {
sqlLine.getBatch().add(sql);
callback.setToSuccess();
return;
}

Expand All @@ -967,7 +988,8 @@ private void execute(String line, boolean call, DispatchCallback callback) {
if (sqlLine.getOpts().getCompiledConfirmPattern().matcher(sql).find()
&& sqlLine.getOpts().getConfirm()) {
final String question = sqlLine.loc("really-perform-action");
final int userResponse = getUserAnswer(question, 'y', 'n', 'Y', 'N');
final int userResponse =
getUserAnswer(question, 'y', 'n', 'Y', 'N');
if (userResponse != 'y') {
sqlLine.error(sqlLine.loc("abort-action"));
callback.setToFailure();
Expand All @@ -982,7 +1004,6 @@ private void execute(String line, boolean call, DispatchCallback callback) {
stmnt = sqlLine.createStatement();
callback.trackSqlQuery(stmnt);
hasResults = stmnt.execute(sql);
callback.setToSuccess();
}

sqlLine.showWarnings();
Expand Down Expand Up @@ -1017,11 +1038,9 @@ private void execute(String line, boolean call, DispatchCallback callback) {
} catch (Exception e) {
callback.setToFailure();
sqlLine.error(e);
return;
}

sqlLine.showWarnings();
callback.setToSuccess();
}

public void quit(String line, DispatchCallback callback) {
Expand Down Expand Up @@ -1478,7 +1497,6 @@ public void run(String line, DispatchCallback callback) {
List<String> cmds = new LinkedList<>();

try {
final Parser parser = sqlLine.getLineReader().getParser();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(expand(filename)), StandardCharsets.UTF_8))) {
Expand All @@ -1494,16 +1512,9 @@ public void run(String line, DispatchCallback callback) {
cmd.append(" \n");
cmd.append(scriptLine);

try {
needsContinuation = false;
parser.parse(cmd.toString(), cmd.length(),
Parser.ParseContext.ACCEPT_LINE);
} catch (EOFError e) {
needsContinuation = true;
}
needsContinuation = isSqlContinuationRequired(cmd.toString());
if (!needsContinuation && !cmd.toString().trim().isEmpty()) {
cmds.add(maybeTrim(cmd.toString()));
cmd.setLength(0);
cmds.add(maybeTrim(flush(cmd)));
}
}

Expand Down Expand Up @@ -1833,6 +1844,19 @@ static Map<String, String> asMap(Properties properties) {
return (Map) properties;
}

private boolean isSqlContinuationRequired(String sql) {
if (sqlLine.getLineReader() == null) {
return false;
}
try {
sqlLine.getLineReader().getParser().parse(sql, sql.length(),
Parser.ParseContext.ACCEPT_LINE);
} catch (EOFError e) {
return true;
}
return false;
}

/**
* Callback that masks input while reading a password.
*/
Expand Down
26 changes: 18 additions & 8 deletions src/main/java/sqlline/SqlLine.java
Expand Up @@ -666,7 +666,7 @@ void dispatch(String line, DispatchCallback callback) {
return;
}

if (isComment(line)) {
if (isOneLineComment(line)) {
callback.setStatus(DispatchCallback.Status.SUCCESS);
return;
}
Expand Down Expand Up @@ -740,7 +740,21 @@ boolean isHelpRequest(String line) {
* @param line the line to be tested
* @return true if a comment
*/
boolean isComment(String line, boolean trim) {
boolean isOneLineComment(String line, boolean trim) {
String[] inputLines = line.split("\n");
for (String inputLine: inputLines) {
if (!isComment(inputLine, trim)) {
return false;
}
}
return true;
}

boolean isOneLineComment(String line) {
return isOneLineComment(line, true);
}

private boolean isComment(String line, boolean trim) {
final String trimmedLine = trim ? line.trim() : line;
for (String comment: getDialect().getSqlLineOneLineComments()) {
if (trimmedLine.startsWith(comment)) {
Expand All @@ -750,10 +764,6 @@ boolean isComment(String line, boolean trim) {
return false;
}

boolean isComment(String line) {
return isComment(line, true);
}

/**
* Print the specified message to the console
*
Expand Down Expand Up @@ -1394,8 +1404,8 @@ String[] split(String line, int assertLen, String usage) {
* @return the wrapped string
*/
String wrap(String toWrap, int len, int start) {
StringBuilder buff = new StringBuilder();
StringBuilder line = new StringBuilder();
final StringBuilder buff = new StringBuilder();
final StringBuilder line = new StringBuilder();

char[] head = new char[start];
Arrays.fill(head, ' ');
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/sqlline/SqlLineHighlighter.java
Expand Up @@ -57,7 +57,7 @@ public SqlLineHighlighter(SqlLine sqlLine) {
final boolean isCommandPresent =
trimmed.startsWith(SqlLine.COMMAND_PREFIX);
final boolean isComment =
!isCommandPresent && sqlLine.isComment(trimmed, false);
!isCommandPresent && sqlLine.isOneLineComment(trimmed, false);
final boolean isSql = !isComment
&& isSqlQuery(trimmed, isCommandPresent);

Expand Down
21 changes: 14 additions & 7 deletions src/main/java/sqlline/SqlLineParser.java
Expand Up @@ -13,11 +13,14 @@

import java.util.LinkedList;
import java.util.List;
import java.util.Objects;

import org.jline.reader.EOFError;
import org.jline.reader.ParsedLine;
import org.jline.reader.impl.DefaultParser;

import static sqlline.Commands.flush;

/**
* SqlLineParser implements multi-line
* for sql, !sql, !all while it's not ended with a non-commented ';'.
Expand Down Expand Up @@ -153,8 +156,7 @@ public ParsedLine parse(final String line, final int cursor,
// In a quote block
if (line.charAt(quoteStart) == currentChar && !isEscaped(line, i)) {
// End the block; arg could be empty, but that's fine
words.add(current.toString());
current.setLength(0);
words.add(flush(current));
quoteStart = -1;
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart + 1;
Expand All @@ -173,8 +175,7 @@ public ParsedLine parse(final String line, final int cursor,
} else if (multiLineCommentStart >= 0) {
if (currentChar == '/' && line.charAt(i - 1) == '*') {
// End the block; arg could be empty, but that's fine
words.add(current.toString());
current.setLength(0);
words.add(flush(current));
multiLineCommentStart = -1;
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart + 1;
Expand Down Expand Up @@ -272,6 +273,13 @@ public ParsedLine parse(final String line, final int cursor,
}
}

if (line.endsWith("\n")
&& sqlLine.getLineReader() != null
&& !Objects.equals(line,
sqlLine.getLineReader().getBuffer().toString())) {
throw new EOFError(-1, -1, "Line continues",
getPaddedPrompt(""));
}
String openingQuote = quoteStart >= 0
? line.substring(quoteStart, quoteStart + 1) : null;
return new ArgumentList(line, words, wordIndex, wordCursor,
Expand Down Expand Up @@ -321,8 +329,7 @@ private int getRawWordLength(
int rawWordStart,
int i) {
if (current.length() > 0) {
words.add(current.toString());
current.setLength(0); // reset the arg
words.add(flush(current));
if (rawWordCursor >= 0 && rawWordLength < 0) {
rawWordLength = i - rawWordStart;
}
Expand All @@ -333,7 +340,7 @@ private int getRawWordLength(
static boolean isSql(SqlLine sqlLine, String line, ParseContext context) {
String trimmedLine = trimLeadingSpacesIfPossible(line, context);
return !trimmedLine.isEmpty()
&& !sqlLine.isComment(trimmedLine, false)
&& !sqlLine.isOneLineComment(trimmedLine, false)
&& (trimmedLine.charAt(0) != '!'
|| trimmedLine.regionMatches(0, "!sql", 0, "!sql".length())
|| trimmedLine.regionMatches(0, "!all", 0, "!all".length()));
Expand Down
47 changes: 43 additions & 4 deletions src/test/java/sqlline/SqlLineArgsTest.java
Expand Up @@ -193,7 +193,7 @@ public void testMultilineScriptWithComments() {

final String script2Text =
"!set incremental true\n"
+ "--comment \n values (';\n' /* comment */, '\"'"
+ "--comment \n --comment2 \nvalues (';\n' /* comment */, '\"'"
+ "/*multiline;\n ;\n comment*/)\n -- ; \n; -- comment";

checkScriptFile(script2Text, true,
Expand Down Expand Up @@ -289,6 +289,45 @@ public void testMultilineScriptFileWithMultilineQuotedStrings() {
allOf(containsString("multiline2;"), containsString("; string2")));
}

@Test
public void testScriptWithMultilineStatementsInARow() {
final String scriptText = "!set incremental false\n"
+ "--comment\n\n"
+ "values 1;values 2;";
checkScriptFile(scriptText, true,
equalTo(SqlLine.Status.OK),
allOf(
containsString("+----+\n"
+ "| C1 |\n"
+ "+----+\n"
+ "| 1 |\n"
+ "+----+"),
containsString("+----+\n"
+ "| C1 |\n"
+ "+----+\n"
+ "| 2 |\n"
+ "+----+")));
}

@Test
public void testScriptWithMultilineStatementsAndCommentsInARow() {
final String scriptText = "!set incremental false\n"
+ "--comment;;\n\n"
+ "select * from (values ';') t (\";\");/*;select 1;*/values 2;";
checkScriptFile(scriptText, true,
equalTo(SqlLine.Status.OK),
allOf(
containsString("+---+\n"
+ "| ; |\n"
+ "+---+\n"
+ "| ; |\n"
+ "+---+"),
containsString("+----+\n"
+ "| C1 |\n"
+ "+----+\n"
+ "| 2 |\n"
+ "+----+")));
}
/**
* Tests sql with H2 specific one-line comment '//'
*/
Expand Down Expand Up @@ -386,7 +425,7 @@ public void testScriptWithOutput() {
@Test
public void testNull() {
final String script = "!set incremental true\n"
+ "values (1, cast(null as integer), cast(null as varchar(3));\n";
+ "values (1, cast(null as integer), cast(null as varchar(3)));\n";
checkScriptFile(script, false,
equalTo(SqlLine.Status.OK),
containsString(
Expand All @@ -413,7 +452,7 @@ public void testScan() {
public void testTableOutputNullWithoutHeader() {
final String script = "!set showHeader false\n"
+ "!set incremental true\n"
+ "values (1, cast(null as integer), cast(null as varchar(3));\n";
+ "values (1, cast(null as integer), cast(null as varchar(3)));\n";
checkScriptFile(script, false,
equalTo(SqlLine.Status.OK),
containsString("| 1 | null | |\n"));
Expand All @@ -426,7 +465,7 @@ public void testTableOutputNullWithoutHeader() {
public void testCsvNullWithoutHeader() {
final String script = "!set showHeader false\n"
+ "!set outputformat csv\n"
+ "values (1, cast(null as integer), cast(null as varchar(3));\n";
+ "values (1, cast(null as integer), cast(null as varchar(3)));\n";
checkScriptFile(script, false,
equalTo(SqlLine.Status.OK),
containsString("'1','null',''\n"));
Expand Down
9 changes: 9 additions & 0 deletions src/test/java/sqlline/SqlLineHighlighterTest.java
Expand Up @@ -505,6 +505,15 @@ public void testComplexStrings() {
expectedStyle.defaults.set(line.indexOf(" dual"), line.length());
checkLineAgainstAllHighlighters(line, expectedStyle);

//one line comment first
line = "-- \nselect 1;";
expectedStyle = new ExpectedHighlightStyle(line.length());
expectedStyle.comments.set(0, line.indexOf("select"));
expectedStyle.keywords.set(line.indexOf("select"), line.indexOf(" 1"));
expectedStyle.defaults.set(line.indexOf(" 1"), line.indexOf("1;"));
expectedStyle.numbers.set(line.indexOf("1"));
expectedStyle.defaults.set(line.length() - 1);
checkLineAgainstAllHighlighters(line, expectedStyle);
}

/**
Expand Down

0 comments on commit b8ace84

Please sign in to comment.