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/06df2492 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/06df2492 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/06df2492 Branch: refs/heads/ignite-3716 Commit: 06df24921b3e36ef14813688e52e93baba77b4f1 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:18:51 2016 +0300 ---------------------------------------------------------------------- .../processors/odbc/escape/EscapeSQLParser.java | 108 ---------------- .../odbc/escape/OdbcEscapeSequenceParser.java | 126 +++++++++++++++++++ .../OdbcScalarFunctionEscapeSequenceTest.java | 80 ++++++++++-- 3 files changed, 197 insertions(+), 117 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/06df2492/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 deleted file mode 100644 index 4630e7d..0000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSQLParser.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.apache.ignite.internal.processors.odbc.escape; - -import org.apache.ignite.internal.util.typedef.T2; -import org.jetbrains.annotations.NotNull; - -/** - * Process ODBC escape sequences in sql query. - * See ODBC escape sequence syntax. - */ -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); - - return value.get2(); - } - - /** - * Parse escape sequence using appropriate parser. - * Supports: - * - 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) { - assert sql.charAt(startPosition) == '{'; - - int pos = sql.indexOf(' ', startPosition + 1); - - if (pos == -1) - throw new IllegalStateException("Escape sequence parsing error at (" + startPosition + "): " + sql); - - String esType = sql.substring(startPosition + 1, pos); - - switch (esType) { - case "fn": - return parseInternal(sql, pos + 1, true); - - default: - throw new IllegalStateException("Unsupported escape sequence found at (" + startPosition + "): " + sql); - } - } - - /** - * Process ODBC escape sequences in sql query. - * @param sql - sql query text - * @param startPosition - parser start position - * @param insideEscapeSequence - 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) { - StringBuffer sb = null; - - int end = -1; - int offset = startPosition; - - for (int i = startPosition; i < sql.length(); i++) { - // Current escape sequence ends up. - if (sql.charAt(i) == '}') { - end = i; - - break; - } - // Inner escape sequence starts - else if (sql.charAt(i) == '{') { - T2<Integer, String> res = parseEscapeSqeuence(sql, i); - - if (sb == null) - sb = new StringBuffer(); - - sb.append(sql, offset, i); - sb.append(res.get2()); - - i = res.get1(); - - assert sql.charAt(i) == '}'; - - offset = res.get1() + 1; - } - } - - if (insideEscapeSequence && end == -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) - // 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); - - if (end == -1) - end = 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<>(end, sb.toString()); - } - -} http://git-wip-us.apache.org/repos/asf/ignite/blob/06df2492/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceParser.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceParser.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceParser.java new file mode 100644 index 0000000..cfaa7c0 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceParser.java @@ -0,0 +1,126 @@ +package org.apache.ignite.internal.processors.odbc.escape; + +import org.apache.ignite.internal.util.typedef.T2; +import org.jetbrains.annotations.NotNull; + +/** + * Process ODBC escape sequences in sql query. + * See ODBC escape sequence syntax. + */ +public class OdbcEscapeSequenceParser { + + /** + * 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> val = parseInternal(sql, 0, false); + + return val.get2(); + } + + /** + * Parse escape sequence using appropriate parser. + * Supports: + * - 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 + */ + private T2<Integer, String> parseEscapeSqeuence(String sql, int startPosition) { + assert sql.charAt(startPosition) == '{'; + + int pos = sql.indexOf(' ', startPosition + 1); + + if (pos == -1) + throw new IllegalStateException("Escape sequence parsing error at (" + startPosition + "): " + sql); + + String esType = sql.substring(startPosition + 1, pos); + + switch (esType) { + case "fn": + return parseInternal(sql, pos + 1, true); + + default: + throw new IllegalStateException("Unsupported escape sequence found at (" + startPosition + "): " + sql); + } + } + + /** + * Process ODBC escape sequences in sql query. + * + * @param sql - sql query text + * @param startPosition - parser start position + * @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 insideEscapeSeq) { + StringBuffer sb = null; + + 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 (ch == '}') { + seqEndPos = i; + + break; + } + // Inner escape sequence starts + else if (ch == '{') { + T2<Integer, String> res = parseEscapeSqeuence(sql, i); + + if (sb == null) + sb = new StringBuffer(); + + sb.append(sql, off, i); + sb.append(res.get2()); + + i = res.get1(); + + assert sql.charAt(res.get1()) == '}'; + + off = res.get1() + 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 (!insideEscapeSeq && seqEndPos != -1) + // Closing bracket character '}' was found out of escape sequence + throw new IllegalStateException("Unexpected seqEndPos of escaped sequence found at position (" + startPosition + ") is not closed: " + sql); + + if (seqEndPos == -1) + seqEndPos = sql.length(); + + // Add tail to the result + if (sb == null) + return new T2<>(seqEndPos, sql.substring(startPosition, seqEndPos)); + else if (off < seqEndPos) + sb.append(sql, off, seqEndPos); + + return new T2<>(seqEndPos, sb.toString()); + } + +} http://git-wip-us.apache.org/repos/asf/ignite/blob/06df2492/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..9c6cf7f 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,15 +1,21 @@ 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.internal.processors.odbc.escape.OdbcEscapeSequenceParser; +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;"; - EscapeSQLParser parser = new EscapeSQLParser(); + OdbcEscapeSequenceParser parser = new OdbcEscapeSequenceParser(); String actualRes = parser.parse(sqlQry); @@ -19,31 +25,87 @@ public class OdbcScalarFunctionEscapeSequenceTest extends TestCase { sqlQry = "select {fn func(field1)} {fn func(field2)}"; expRes = "select func(field1) func(field2)"; - parser = new EscapeSQLParser(); + parser = new OdbcEscapeSequenceParser(); actualRes = parser.parse(sqlQry); 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(); + OdbcEscapeSequenceParser parser = new OdbcEscapeSequenceParser(); - 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)}"; expRes = "select func1(field1, func2(field2), field3)"; - parser = new EscapeSQLParser(); + parser = new OdbcEscapeSequenceParser(); + + actualRes = parser.parse(sqlQry); + + assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); + } + + /** */ + public void testEscapeSequenceWithQuotation() throws Exception { + String sqlQry = "select {fn func1(field1, {fn func2(\"}\")}, field3)} from SomeTable;"; + String expRes = "select func1(field1, func2(\"}\"), field3) from SomeTable;"; + + OdbcEscapeSequenceParser parser = new OdbcEscapeSequenceParser(); + + 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 OdbcEscapeSequenceParser(); + + actualRes = parser.parse(sqlQry); + + assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); + } + + /** */ + public void testFailedOnNotClosedSequence() 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;"; + + OdbcEscapeSequenceParser parser = new OdbcEscapeSequenceParser(); + + parser.parse(sqlQry); + + fail("Scalar function escape sequence parsing should failed"); + + return null; + } + }, IllegalStateException.class, null); + } + + /** */ + public void testFailedOnClosingNotOpenedSequence() 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;"; + + OdbcEscapeSequenceParser parser = new OdbcEscapeSequenceParser(); + + parser.parse(sqlQry); - actualResult = 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); } }
