Scalar function escape sequence support prototype implemented. Quotation support added.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/ed40518d Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/ed40518d Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/ed40518d Branch: refs/heads/ignite-3716 Commit: ed40518dc00bca6da5890555f9dbf23942bdeeb2 Parents: 98a2125 Author: Andrey V. Mashenkov <[email protected]> Authored: Mon Aug 22 18:12:31 2016 +0300 Committer: Andrey V. Mashenkov <[email protected]> Committed: Mon Aug 22 18:12:31 2016 +0300 ---------------------------------------------------------------------- .../processors/odbc/escape/EscapeSQLParser.java | 63 +++++++++++------- .../OdbcScalarFunctionEscapeSequenceTest.java | 70 ++++++++++++++++++-- 2 files changed, 106 insertions(+), 27 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/ed40518d/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSQLParser.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSQLParser.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSQLParser.java index 4630e7d..a995b58 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSQLParser.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSQLParser.java @@ -11,25 +11,26 @@ public class EscapeSQLParser { /** * Process ODBC escape sequences in sql query. + * * @param sql - sql query text * @return - processed sql query text */ public String parse(String sql) { - T2<Integer, String> value = parseInternal(sql, 0, false); + T2<Integer, String> val = parseInternal(sql, 0, false); - return value.get2(); + return val.get2(); } /** * Parse escape sequence using appropriate parser. * Supports: - * - Scalar function escape sequence. Example: "{fn func(arg1, arg2)}" + * - Scalar function escape sequence. Example: "{fn func(arg1, arg2)}" * * @param sql - sql query text * @param startPosition - parser start position * @return pair of end of processed sequence position and parse result */ - T2<Integer, String> parseEscapeSqeuence(String sql, int startPosition) { + private T2<Integer, String> parseEscapeSqeuence(String sql, int startPosition) { assert sql.charAt(startPosition) == '{'; int pos = sql.indexOf(' ', startPosition + 1); @@ -50,59 +51,75 @@ public class EscapeSQLParser { /** * Process ODBC escape sequences in sql query. + * * @param sql - sql query text * @param startPosition - parser start position - * @param insideEscapeSequence - inside escape sequence flag + * @param insideEscapeSeq - inside escape sequence flag * @return pair of end of processed sequence position and parse result */ - @NotNull private T2<Integer, String> parseInternal(String sql, int startPosition, boolean insideEscapeSequence) { + @NotNull private T2<Integer, String> parseInternal(String sql, int startPosition, boolean insideEscapeSeq) { StringBuffer sb = null; - int end = -1; - int offset = startPosition; + int off = startPosition; + int seqEndPos = -1; + + char quoted = 0; for (int i = startPosition; i < sql.length(); i++) { + char ch = sql.charAt(i); + + if ((ch == '\'' || ch == '"') && (i == 0 || sql.charAt(i - 1) != '\\')) { + if (quoted == 0) + quoted = ch; + else if (quoted == ch) + quoted = 0; + continue; + } + + if (quoted > 0) + continue; + // Current escape sequence ends up. - if (sql.charAt(i) == '}') { - end = i; + if (ch == '}') { + seqEndPos = i; break; } // Inner escape sequence starts - else if (sql.charAt(i) == '{') { + else if (ch == '{') { T2<Integer, String> res = parseEscapeSqeuence(sql, i); if (sb == null) sb = new StringBuffer(); - sb.append(sql, offset, i); + sb.append(sql, off, i); sb.append(res.get2()); i = res.get1(); - assert sql.charAt(i) == '}'; + assert sql.charAt(res.get1()) == '}'; - offset = res.get1() + 1; + off = res.get1() + 1; } } - if (insideEscapeSequence && end == -1) + if (insideEscapeSeq && seqEndPos == -1) // Closing bracket character '}' was not found in inner escape sequence throw new IllegalStateException("Escape sequence started at position (" + startPosition + ") is not closed: " + sql); - else if (!insideEscapeSequence && end != -1) + else if (!insideEscapeSeq && seqEndPos != -1) // Closing bracket character '}' was found out of escape sequence - throw new IllegalStateException("Unexpected end of escaped sequence found at position (" + startPosition + ") is not closed: " + sql); + throw new IllegalStateException("Unexpected seqEndPos of escaped sequence found at position (" + startPosition + ") is not closed: " + sql); - if (end == -1) - end = sql.length(); + if (seqEndPos == -1) + seqEndPos = sql.length(); // Add tail to the result if (sb == null) - return new T2<>(end, sql.substring(startPosition, end)); - else if (offset < end) - sb.append(sql, offset, end); + return new T2<>(seqEndPos, sql.substring(startPosition, seqEndPos)); + else if (off < seqEndPos) + sb.append(sql, off, seqEndPos); - return new T2<>(end, sb.toString()); + return new T2<>(seqEndPos, sb.toString()); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/ed40518d/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcScalarFunctionEscapeSequenceTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcScalarFunctionEscapeSequenceTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcScalarFunctionEscapeSequenceTest.java index 7d60b42..c7f0d36 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcScalarFunctionEscapeSequenceTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcScalarFunctionEscapeSequenceTest.java @@ -1,10 +1,16 @@ package org.apache.ignite.internal.processors.odbc; +import java.util.concurrent.Callable; import junit.framework.TestCase; import org.apache.ignite.internal.processors.odbc.escape.EscapeSQLParser; +import org.apache.ignite.testframework.GridTestUtils; +/** + * + */ public class OdbcScalarFunctionEscapeSequenceTest extends TestCase { + /** */ public void testTrivialFunction() throws Exception { String sqlQry = "select {fn func(field1)} {fn func(field2)} from table;"; String expRes = "select func(field1) func(field2) from table;"; @@ -26,15 +32,16 @@ public class OdbcScalarFunctionEscapeSequenceTest extends TestCase { assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); } + /** */ public void testNestedFunction() throws Exception { String sqlQry = "select {fn func1(field1, {fn func2(field2)}, field3)} from SomeTable;"; String expRes = "select func1(field1, func2(field2), field3) from SomeTable;"; EscapeSQLParser parser = new EscapeSQLParser(); - String actualResult = parser.parse(sqlQry); + String actualRes = parser.parse(sqlQry); - assertEquals("Scalar function escape sequence parsing fails", expRes, actualResult); + assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); sqlQry = "select {fn func1(field1, {fn func2(field2)}, field3)}"; @@ -42,8 +49,63 @@ public class OdbcScalarFunctionEscapeSequenceTest extends TestCase { parser = new EscapeSQLParser(); - actualResult = parser.parse(sqlQry); + actualRes = parser.parse(sqlQry); + + assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); + } + + /** */ + public void testEscapedCharsInFunction() throws Exception { + String sqlQry = "select {fn func1(field1, {fn func2(\"}\")}, field3)} from SomeTable;"; + String expRes = "select func1(field1, func2(\"}\"), field3) from SomeTable;"; + + EscapeSQLParser parser = new EscapeSQLParser(); + + String actualRes = parser.parse(sqlQry); + + assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); + + sqlQry = "select {fn func1(field1, '\\'{fn f(arg)}', field3)} from SomeTable;"; + expRes = "select func1(field1, '\\\'{fn f(arg)}', field3) from SomeTable;"; + + parser = new EscapeSQLParser(); + + actualRes = parser.parse(sqlQry); + + assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); + } + + /** */ + public void testFailedNotClosedSequence() throws Exception { + GridTestUtils.assertThrows(null, new Callable<Void>() { + @Override public Void call() throws Exception { + String sqlQry = "select {fn func1(field1, {fn func2(field2), field3)} from SomeTable;"; + + EscapeSQLParser parser = new EscapeSQLParser(); + + parser.parse(sqlQry); + + fail("Scalar function escape sequence parsing should failed"); + + return null; + } + }, IllegalStateException.class, null); + } + + /** */ + public void testFailedNotOpenedSequenceCloses() throws Exception { + GridTestUtils.assertThrows(null, new Callable<Void>() { + @Override public Void call() throws Exception { + String sqlQry = "select {fn func1(field1, func2(field2)}, field3)} from SomeTable;"; + + EscapeSQLParser parser = new EscapeSQLParser(); + + parser.parse(sqlQry); + + fail("Scalar function escape sequence parsing should failed"); - assertEquals("Scalar function escape sequence parsing fails", expRes, actualResult); + return null; + } + }, IllegalStateException.class, null); } }
