# HG changeset patch # Parent f767e162e34edfadf893dfdb00a6a2c7534ae44d # User Matthias Bläsing Quoting in SQLSplitter was not correctly recognized -- this adapts the changes in SQLLexer into Database Core diff --git a/db.core/src/org/netbeans/modules/db/sql/execute/SQLExecuteHelper.java b/db.core/src/org/netbeans/modules/db/sql/execute/SQLExecuteHelper.java --- a/db.core/src/org/netbeans/modules/db/sql/execute/SQLExecuteHelper.java +++ b/db.core/src/org/netbeans/modules/db/sql/execute/SQLExecuteHelper.java @@ -221,14 +221,14 @@ private static final int STATE_MAYBE_BLOCK_COMMENT = 3; private static final int STATE_BLOCK_COMMENT = 4; private static final int STATE_MAYBE_END_BLOCK_COMMENT = 5; - private static final int STATE_STRING = 6; + private static final int STATE_QUOTED = 6; - private String sql; - private int sqlLength; - private boolean useHashComments; + private final String sql; + private final int sqlLength; + private final boolean useHashComments; - private StringBuffer statement = new StringBuffer(); - private List statements = new ArrayList(); + private final StringBuilder statement = new StringBuilder(); + private final List statements = new ArrayList(); private int pos = 0; private int line = -1; @@ -263,6 +263,7 @@ private void parse() { checkDelimiterStatement(); + int startQuote = -1; while (pos < sqlLength) { char ch = sql.charAt(pos); @@ -294,8 +295,9 @@ if (statement.length() == 0 || !Character.isLetterOrDigit(statement.charAt(statement.length() - 1))) { state = STATE_LINE_COMMENT; } - } else if (ch == '\'') { - state = STATE_STRING; + } else if (isStartQuote(ch)) { + startQuote = ch; + state = STATE_QUOTED; } break; @@ -344,9 +346,24 @@ } break; - case STATE_STRING: - if (ch == '\'') { - state = STATE_MEANINGFUL_TEXT; + case STATE_QUOTED: + int lookAhead = -1; + if((pos + 1) < sqlLength) { + lookAhead = sql.charAt(pos + 1); + } + if (isEndQuote(startQuote, ch)) { + if (lookAhead >= 0 && isEndQuote(startQuote, lookAhead)) { + statement.append(ch); + statement.append((char) lookAhead); + pos += 2; + // the end offset is the character after the last non-whitespace character + if (state == STATE_QUOTED || !Character.isWhitespace(ch)) { + endOffset = pos + 1; + } + continue; + } else { + state = STATE_MEANINGFUL_TEXT; + } } break; @@ -354,7 +371,7 @@ assert false; } - if (state == STATE_MEANINGFUL_TEXT || state == STATE_STRING) { + if (state == STATE_MEANINGFUL_TEXT || state == STATE_QUOTED) { // don't append leading whitespace if (statement.length() > 0 || !Character.isWhitespace(ch)) { // remember the position of the first appended char @@ -372,7 +389,7 @@ } statement.append(ch); // the end offset is the character after the last non-whitespace character - if (state == STATE_STRING || !Character.isWhitespace(ch)) { + if (state == STATE_QUOTED || !Character.isWhitespace(ch)) { endOffset = pos + 1; } } @@ -383,7 +400,45 @@ rawEndOffset = pos; addStatement(); } + + // The methods to detect quoting chars are copied from SQLLexer from the + // org.netbeans.modules.db.sql.editor module + // Copied to not introduce another dependency + private static boolean isStartQuote(int start) { + return isStartStringQuoteChar(start) || isStartIdentifierQuoteChar(start); + } + private static boolean isEndQuote(int start, int end) { + return isEndIdentifierQuoteChar(start, end) || isEndStringQuoteChar(start, end); + } + + private static boolean isStartStringQuoteChar(int start) { + return start == '\''; // SQL-99 string + } + + private static boolean isStartIdentifierQuoteChar(int start) { + return start == '\"' || // SQL-99 + start == '`' || // MySQL + start == '['; // MS SQL Server + } + + private static int getMatchingQuote(int start) { + switch (start) { + case '[': + return ']'; + default: + return start; + } + } + + private static boolean isEndIdentifierQuoteChar(int start, int end) { + return isStartIdentifierQuoteChar(start) && end == getMatchingQuote(start); + } + + private static boolean isEndStringQuoteChar(int start, int end) { + return isStartStringQuoteChar(start) && end == getMatchingQuote(start); + } + /** * See if the user wants to use a different delimiter for splitting * up statements. This is useful if, for example, their SQL contains diff --git a/db.core/test/unit/src/org/netbeans/modules/db/sql/execute/SQLExecuteHelperTest.java b/db.core/test/unit/src/org/netbeans/modules/db/sql/execute/SQLExecuteHelperTest.java --- a/db.core/test/unit/src/org/netbeans/modules/db/sql/execute/SQLExecuteHelperTest.java +++ b/db.core/test/unit/src/org/netbeans/modules/db/sql/execute/SQLExecuteHelperTest.java @@ -139,6 +139,28 @@ assertSplit(sql, new StatementInfo("select foo", 0, 32, 5, 0, 42, 44)); } + public void testQuoteHandling() { + assertSplit("select * from b where a = ';';" // Normal String quoting + + "select * from b where \";\" = 'a';" // SQL Identifier quoting + + "select * from b where [;] = 'a';" // MSSQL Identifier Quoting + + "select * from b where `;` = 'a';" // MySQL Identifier Quoting + // The follwing statemens are variants of the four previous + // statements with identifier escaping + + "select * from b where a = ''';';" + + "select * from b where \";\"\"\" = 'a';" + + "select * from b where []];] = 'a';" + + "select * from b where `;``` = 'a';" + , "select * from b where a = ';'" + , "select * from b where \";\" = 'a'" + , "select * from b where [;] = 'a'" + , "select * from b where `;` = 'a'" + , "select * from b where a = ''';'" + , "select * from b where \";\"\"\" = 'a'" + , "select * from b where []];] = 'a'" + , "select * from b where `;``` = 'a'" + ); + } + private static void assertSplit(String script, String... expected) { assertSplit(script, true, expected); }