Scalar function escape sequence support prototype implemented.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/902b45ba Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/902b45ba Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/902b45ba Branch: refs/heads/ignite-3716 Commit: 902b45ba0a85a8b66b7f40d3865b7aaa3a367a30 Parents: 4c96007 Author: Andrey V. Mashenkov <[email protected]> Authored: Mon Aug 22 20:27:34 2016 +0300 Committer: Andrey V. Mashenkov <[email protected]> Committed: Mon Aug 22 20:27:34 2016 +0300 ---------------------------------------------------------------------- .../odbc/escape/EscapeSequenceParseResult.java | 46 +++++++ .../odbc/escape/EscapeSequenceParser.java | 32 +++++ .../odbc/escape/OdbcEscapeSequenceParser.java | 129 ------------------- .../odbc/escape/OdbcEscapeSequenceUtils.java | 98 ++++++++++++++ .../ScalarFunctionEscapeSequenceParser.java | 73 +++++++++++ .../OdbcScalarFunctionEscapeSequenceTest.java | 88 +++++++------ 6 files changed, 292 insertions(+), 174 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/902b45ba/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSequenceParseResult.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSequenceParseResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSequenceParseResult.java new file mode 100644 index 0000000..1039028 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSequenceParseResult.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.odbc.escape; + +/** + * Uses in parsing Odbc escape sequences + */ +class EscapeSequenceParseResult { + + /** Sequence last character position in source sql query string. */ + private int endPosition; + + /** Escape sequence calculated value. */ + private String value; + + /** */ + public EscapeSequenceParseResult(int endPosition, String value) { + this.endPosition = endPosition; + this.value = value; + } + + /** */ + public int endPosition() { + return endPosition; + } + + /** */ + public String value() { + return value; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/902b45ba/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSequenceParser.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSequenceParser.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSequenceParser.java new file mode 100644 index 0000000..26bbff6 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/EscapeSequenceParser.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.odbc.escape; + +/** */ +interface EscapeSequenceParser { + + /** + * Process ODBC escape sequences in sql query. + * + * @param sql - sql query string + * @param start - start position in given sql string + * @return escape sequence parse result + */ + EscapeSequenceParseResult parse(String sql, int start); + +} http://git-wip-us.apache.org/repos/asf/ignite/blob/902b45ba/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 deleted file mode 100644 index 911aede..0000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceParser.java +++ /dev/null @@ -1,129 +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 OdbcEscapeSequenceParser { - - /** - * Process ODBC escape sequences in sql query. - * - * @param sql - sql query text - * @return - processed sql query text - */ - // TODO: Set static. - public String parse(String sql) { - // TODO: The whole string could be escape sequence. Need to check it here, not simply pass false. - T2<Integer, String> val = parse0(sql, 0, false); - - return val.get2(); - } - - /** - * 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 - */ - // TODO: Do not use T2, create separate class instead. - @NotNull private T2<Integer, String> parse0(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); - - // TODO: This should be implemented as separate parsers. - 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 = processEscapeSequence(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()); - } - - /** - * 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> processEscapeSequence(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 parse0(sql, pos + 1, true); - - default: - throw new IllegalStateException("Unsupported escape sequence found at (" + startPosition + "): " + sql); - } - } -} http://git-wip-us.apache.org/repos/asf/ignite/blob/902b45ba/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceUtils.java new file mode 100644 index 0000000..da44914 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeSequenceUtils.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.odbc.escape; + +/** + * Process ODBC escape sequences in sql query. + * See ODBC escape sequence syntax. + */ +public class OdbcEscapeSequenceUtils { + + /** Scalar function escape sequence parser */ + private static EscapeSequenceParser FN = new ScalarFunctionEscapeSequenceParser(); + + /** + * Process ODBC escape sequences in sql query. + * + * @param sql - sql query text + * @return - processed sql query text + */ + public static String parse(String sql) { + return parse0(sql, 0); + } + + /** + * Process ODBC escape sequences in sql query. + * + * @param sql - sql query text + * @return - processed sql query text + */ + private static String parse0(String sql, int startPos) { + StringBuffer res = null; + + int off = startPos; + + for (int i = startPos; i < sql.length(); i++) { + char ch = sql.charAt(i); + + // Current escape sequence ends up. + if (ch == '}') + throw new IllegalStateException("Unexpected end of escaped sequence found at (" + i + "): " + sql); + // Inner escape sequence starts + else if (ch == '{') { + EscapeSequenceParseResult escapeRes = processEscapeSequence(sql, i); + + if (res == null) + res = new StringBuffer(); + + res.append(sql, off, i); + res.append(escapeRes.value()); + + i = escapeRes.endPosition(); + + assert sql.charAt(escapeRes.endPosition()) == '}'; + + off = escapeRes.endPosition() + 1; + } + } + + if (res == null) + return sql; + else if (off < sql.length()) + res.append(sql, off, sql.length()); + + return res.toString(); + } + + /** + * Parse escape sequence using appropriate parser. + * + * @param sql - sql query text + * @param startPosition - parser start position + * @return pair of end of processed sequence position and parse result + */ + static EscapeSequenceParseResult processEscapeSequence(String sql, int startPosition) { + assert sql.charAt(startPosition) == '{'; + + // New escape sequence parsers should be added here. + if (sql.startsWith("fn ", startPosition + 1)) + return FN.parse(sql, startPosition + 4); + + throw new IllegalStateException("Unsupported escape sequence found at (" + startPosition + "): " + sql); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/902b45ba/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/ScalarFunctionEscapeSequenceParser.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/ScalarFunctionEscapeSequenceParser.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/ScalarFunctionEscapeSequenceParser.java new file mode 100644 index 0000000..9b2a09c --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/ScalarFunctionEscapeSequenceParser.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.odbc.escape; + +import org.jetbrains.annotations.NotNull; + +/** + * Scalar function escape sequence parser. + * Escape sequence example: "{fn func(arg1, arg2)}" + */ +class ScalarFunctionEscapeSequenceParser implements EscapeSequenceParser { + + /** {@inheritDoc} */ + @NotNull public EscapeSequenceParseResult parse(String sql, int start) { + int off = start; + int end = -1; + + StringBuffer sb = null; + + for (int i = start; i < sql.length(); i++) { + char ch = sql.charAt(i); + + // Current escape sequence ends up. + if (ch == '}') { + end = i; + + break; + } + // Inner escape sequence starts + else if (ch == '{') { + EscapeSequenceParseResult res = OdbcEscapeSequenceUtils.processEscapeSequence(sql, i); + + if (sb == null) + sb = new StringBuffer(); + + sb.append(sql, off, i); + sb.append(res.value()); + + i = res.endPosition(); + + assert sql.charAt(res.endPosition()) == '}'; + + off = res.endPosition() + 1; + } + } + + if (end == -1) + // Closing bracket character '}' was not found in escape sequence + throw new IllegalStateException("Escape sequence started at position (" + start + ") is not closed: " + sql); + + if (sb != null && off < end) + sb.append(sql, off, end); + + String res = (sb != null) ? sb.toString() : sql.substring(start, end); + + return new EscapeSequenceParseResult(end, res); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/902b45ba/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 9c6cf7f..bab9feb 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,76 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.apache.ignite.internal.processors.odbc; import java.util.concurrent.Callable; import junit.framework.TestCase; -import org.apache.ignite.internal.processors.odbc.escape.OdbcEscapeSequenceParser; +import org.apache.ignite.internal.processors.odbc.escape.OdbcEscapeSequenceUtils; 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;"; - - OdbcEscapeSequenceParser parser = new OdbcEscapeSequenceParser(); + public void testTrivial() throws Exception { + String sqlQry = "{fn test()}"; + String expRes = "test()"; - String actualRes = parser.parse(sqlQry); + String actualRes = OdbcEscapeSequenceUtils.parse(sqlQry); assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); + sqlQry = "select * from table;"; + expRes = "select * from table;"; - sqlQry = "select {fn func(field1)} {fn func(field2)}"; - expRes = "select func(field1) func(field2)"; - - parser = new OdbcEscapeSequenceParser(); - - actualRes = parser.parse(sqlQry); + actualRes = OdbcEscapeSequenceUtils.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;"; - OdbcEscapeSequenceParser parser = new OdbcEscapeSequenceParser(); + /** */ + public void testSimpleFunction() throws Exception { + String sqlQry = "select {fn func(field1)} {fn func(field2)} from table;"; + String expRes = "select func(field1) func(field2) from table;"; - String actualRes = parser.parse(sqlQry); + String actualRes = OdbcEscapeSequenceUtils.parse(sqlQry); assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); + sqlQry = "select {fn func(field1)} {fn func(field2)}"; + expRes = "select func(field1) func(field2)"; - sqlQry = "select {fn func1(field1, {fn func2(field2)}, field3)}"; - expRes = "select func1(field1, func2(field2), field3)"; - - parser = new OdbcEscapeSequenceParser(); - - actualRes = parser.parse(sqlQry); + actualRes = OdbcEscapeSequenceUtils.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(); + 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;"; - String actualRes = parser.parse(sqlQry); + String actualRes = OdbcEscapeSequenceUtils.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(); + sqlQry = "select {fn func1(field1, {fn func2(field2)}, field3)}"; + expRes = "select func1(field1, func2(field2), field3)"; - actualRes = parser.parse(sqlQry); + actualRes = OdbcEscapeSequenceUtils.parse(sqlQry); assertEquals("Scalar function escape sequence parsing fails", expRes, actualRes); } @@ -81,9 +83,7 @@ public class OdbcScalarFunctionEscapeSequenceTest extends TestCase { @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); + String actualRes = OdbcEscapeSequenceUtils.parse(sqlQry); fail("Scalar function escape sequence parsing should failed"); @@ -98,9 +98,7 @@ public class OdbcScalarFunctionEscapeSequenceTest extends TestCase { @Override public Void call() throws Exception { String sqlQry = "select {fn func1(field1, func2(field2)}, field3)} from SomeTable;"; - OdbcEscapeSequenceParser parser = new OdbcEscapeSequenceParser(); - - parser.parse(sqlQry); + OdbcEscapeSequenceUtils.parse(sqlQry); fail("Scalar function escape sequence parsing should failed");
