ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From akuznet...@apache.org
Subject [01/38] ignite git commit: IGNITE-3716: ODBC: Added SQL escape sequence parsing.
Date Wed, 31 Aug 2016 06:32:51 GMT
Repository: ignite
Updated Branches:
  refs/heads/ignite-3443 7de38e733 -> fc0917b53


IGNITE-3716: ODBC: Added SQL escape sequence parsing.


Project: http://git-wip-us.apache.org/repos/asf/ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/4e9e7b8e
Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/4e9e7b8e
Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/4e9e7b8e

Branch: refs/heads/ignite-3443
Commit: 4e9e7b8ee1c990bacdc2d081b706ca315927fdce
Parents: f925873
Author: vozerov-gridgain <vozerov@gridgain.com>
Authored: Wed Aug 24 12:12:00 2016 +0300
Committer: vozerov-gridgain <vozerov@gridgain.com>
Committed: Wed Aug 24 12:12:00 2016 +0300

----------------------------------------------------------------------
 .../processors/odbc/OdbcNioListener.java        |   2 +-
 .../processors/odbc/OdbcRequestHandler.java     |  36 ++-
 .../odbc/escape/OdbcEscapeParseResult.java      |  73 +++++
 .../processors/odbc/escape/OdbcEscapeType.java  |  26 ++
 .../processors/odbc/escape/OdbcEscapeUtils.java | 263 +++++++++++++++++++
 .../odbc/OdbcEscapeSequenceSelfTest.java        | 184 +++++++++++++
 .../ignite/testsuites/IgniteBasicTestSuite.java |   2 +
 7 files changed, 575 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/4e9e7b8e/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcNioListener.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcNioListener.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcNioListener.java
index f720096..e7baaff 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcNioListener.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcNioListener.java
@@ -121,7 +121,7 @@ public class OdbcNioListener extends GridNioServerListenerAdapter<byte[]>
{
 
             OdbcRequestHandler handler = connData.getHandler();
 
-            OdbcResponse resp = handler.handle(req);
+            OdbcResponse resp = handler.handle(reqId, req);
 
             if (log.isDebugEnabled()) {
                 long dur = (System.nanoTime() - startTime) / 1000;

http://git-wip-us.apache.org/repos/asf/ignite/blob/4e9e7b8e/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcRequestHandler.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcRequestHandler.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcRequestHandler.java
index 43a1fa4..ce98720 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcRequestHandler.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/OdbcRequestHandler.java
@@ -18,10 +18,12 @@
 package org.apache.ignite.internal.processors.odbc;
 
 import org.apache.ignite.IgniteCache;
+import org.apache.ignite.IgniteLogger;
 import org.apache.ignite.cache.query.QueryCursor;
 import org.apache.ignite.cache.query.SqlFieldsQuery;
 import org.apache.ignite.internal.GridKernalContext;
 import org.apache.ignite.internal.processors.cache.QueryCursorImpl;
+import org.apache.ignite.internal.processors.odbc.escape.OdbcEscapeUtils;
 import org.apache.ignite.internal.processors.query.GridQueryFieldMetadata;
 import org.apache.ignite.internal.processors.query.GridQueryTypeDescriptor;
 import org.apache.ignite.internal.util.GridSpinBusyLock;
@@ -45,6 +47,9 @@ public class OdbcRequestHandler {
     /** Kernel context. */
     private final GridKernalContext ctx;
 
+    /** Logger. */
+    private final IgniteLogger log;
+
     /** Busy lock. */
     private final GridSpinBusyLock busyLock;
 
@@ -65,15 +70,18 @@ public class OdbcRequestHandler {
         this.ctx = ctx;
         this.busyLock = busyLock;
         this.maxCursors = maxCursors;
+
+        log = ctx.log(OdbcRequestHandler.class);
     }
 
     /**
      * Handle request.
      *
+     * @param reqId Request ID.
      * @param req Request.
      * @return Response.
      */
-    public OdbcResponse handle(OdbcRequest req) {
+    public OdbcResponse handle(long reqId, OdbcRequest req) {
         assert req != null;
 
         if (!busyLock.enterBusy())
@@ -83,22 +91,22 @@ public class OdbcRequestHandler {
         try {
             switch (req.command()) {
                 case HANDSHAKE:
-                    return performHandshake((OdbcHandshakeRequest) req);
+                    return performHandshake(reqId, (OdbcHandshakeRequest)req);
 
                 case EXECUTE_SQL_QUERY:
-                    return executeQuery((OdbcQueryExecuteRequest) req);
+                    return executeQuery(reqId, (OdbcQueryExecuteRequest)req);
 
                 case FETCH_SQL_QUERY:
-                    return fetchQuery((OdbcQueryFetchRequest) req);
+                    return fetchQuery((OdbcQueryFetchRequest)req);
 
                 case CLOSE_SQL_QUERY:
-                    return closeQuery((OdbcQueryCloseRequest) req);
+                    return closeQuery((OdbcQueryCloseRequest)req);
 
                 case GET_COLUMNS_META:
-                    return getColumnsMeta((OdbcQueryGetColumnsMetaRequest) req);
+                    return getColumnsMeta((OdbcQueryGetColumnsMetaRequest)req);
 
                 case GET_TABLES_META:
-                    return getTablesMeta((OdbcQueryGetTablesMetaRequest) req);
+                    return getTablesMeta((OdbcQueryGetTablesMetaRequest)req);
             }
 
             return new OdbcResponse(OdbcResponse.STATUS_FAILED, "Unsupported ODBC request:
" + req);
@@ -111,10 +119,11 @@ public class OdbcRequestHandler {
     /**
      * {@link OdbcHandshakeRequest} command handler.
      *
+     * @param reqId Request ID.
      * @param req Handshake request.
      * @return Response.
      */
-    private OdbcResponse performHandshake(OdbcHandshakeRequest req) {
+    private OdbcResponse performHandshake(long reqId, OdbcHandshakeRequest req) {
         OdbcHandshakeResult res;
 
         if (req.version() == OdbcMessageParser.PROTO_VER)
@@ -133,10 +142,11 @@ public class OdbcRequestHandler {
     /**
      * {@link OdbcQueryExecuteRequest} command handler.
      *
+     * @param reqId Request ID.
      * @param req Execute query request.
      * @return Response.
      */
-    private OdbcResponse executeQuery(OdbcQueryExecuteRequest req) {
+    private OdbcResponse executeQuery(long reqId, OdbcQueryExecuteRequest req) {
         int cursorCnt = qryCursors.size();
 
         if (maxCursors > 0 && cursorCnt >= maxCursors)
@@ -147,7 +157,13 @@ public class OdbcRequestHandler {
         long qryId = QRY_ID_GEN.getAndIncrement();
 
         try {
-            SqlFieldsQuery qry = new SqlFieldsQuery(req.sqlQuery());
+            String sql = OdbcEscapeUtils.parse(req.sqlQuery());
+
+            if (log.isDebugEnabled())
+                log.debug("ODBC query parsed [reqId=" + reqId + ", original=" + req.sqlQuery()
+
+                    ", parsed=" + sql + ']');
+
+            SqlFieldsQuery qry = new SqlFieldsQuery(sql);
 
             qry.setArgs(req.arguments());
 

http://git-wip-us.apache.org/repos/asf/ignite/blob/4e9e7b8e/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeParseResult.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeParseResult.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeParseResult.java
new file mode 100644
index 0000000..cf05651
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeParseResult.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.apache.ignite.internal.util.typedef.internal.S;
+
+/**
+ * ODBC escape sequence parse result.
+ */
+public class OdbcEscapeParseResult {
+    /** Original start position. */
+    private final int originalStart;
+
+    /** Original length. */
+    private final int originalLen;
+
+    /** Resulting text. */
+    private final String res;
+
+    /**
+     * Constructor.
+     *
+     * @param originalStart Original start position.
+     * @param originalLen Original length.
+     * @param res Resulting text.
+     */
+    public OdbcEscapeParseResult(int originalStart, int originalLen, String res) {
+        this.originalStart = originalStart;
+        this.originalLen = originalLen;
+        this.res = res;
+    }
+
+    /**
+     * @return Original start position.
+     */
+    public int originalStart() {
+        return originalStart;
+    }
+
+    /**
+     * @return Original length.
+     */
+    public int originalLength() {
+        return originalLen;
+    }
+
+    /**
+     * @return Resulting text.
+     */
+    public String result() {
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override public String toString() {
+        return S.toString(OdbcEscapeParseResult.class, this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4e9e7b8e/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeType.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeType.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeType.java
new file mode 100644
index 0000000..2df413f
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeType.java
@@ -0,0 +1,26 @@
+/*
+ * 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;
+
+/**
+ * ODBC escape sequence type.
+ */
+public enum OdbcEscapeType {
+    /** Scalar function. */
+    FN
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4e9e7b8e/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeUtils.java
----------------------------------------------------------------------
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeUtils.java
b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeUtils.java
new file mode 100644
index 0000000..4d8ca69
--- /dev/null
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeUtils.java
@@ -0,0 +1,263 @@
+/*
+ * 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.apache.ignite.IgniteException;
+
+import java.util.LinkedList;
+
+/**
+ * ODBC escape sequence parse.
+ */
+public class OdbcEscapeUtils {
+    /**
+     * Parse escape sequence.
+     *
+     * @param text Original text.
+     * @return Result.
+     */
+    public static String parse(String text) {
+        if (text == null)
+            throw new IgniteException("Text cannot be null.");
+
+        return parse0(text.trim(), 0, false).result();
+    }
+
+    /**
+     * Internal parse routine.
+     *
+     * @param text Text.
+     * @param startPos Start position.
+     * @param earlyExit When set to {@code true} we must return as soon as single expression
is parsed.
+     * @return Parse result.
+     */
+    private static OdbcEscapeParseResult parse0(String text, int startPos, boolean earlyExit)
{
+        StringBuilder res = new StringBuilder();
+
+        int curPos = startPos;
+
+        int plainPos = startPos;
+        int openPos = -1;
+
+        LinkedList<OdbcEscapeParseResult> nested = null;
+
+        while (curPos < text.length()) {
+            char curChar = text.charAt(curPos);
+
+            if (curChar == '{') {
+                if (openPos == -1) {
+                    // Top-level opening brace. Append previous portion and remember current
position.
+                    res.append(text, plainPos, curPos);
+
+                    openPos = curPos;
+                }
+                else {
+                    // Nested opening brace -> perform recursion.
+                    OdbcEscapeParseResult nestedRes = parse0(text, curPos, true);
+
+                    if (nested == null)
+                        nested = new LinkedList<>();
+
+                    nested.add(nestedRes);
+
+                    curPos += nestedRes.originalLength() - 1;
+
+                    plainPos = curPos + 1;
+                }
+            }
+            else if (curChar == '}') {
+                if (openPos == -1)
+                    // Close without open -> exception.
+                    throw new IgniteException("Malformed escape sequence " +
+                        "(closing curly brace without opening curly brace): " + text);
+                else {
+                    String parseRes;
+
+                    if (nested == null)
+                        // Found sequence without nesting, process it.
+                        parseRes = parseExpression(text, openPos, curPos - openPos);
+                    else {
+                        // Special case to process nesting.
+                        String res0 = appendNested(text, openPos, curPos + 1, nested);
+
+                        nested = null;
+
+                        parseRes = parseExpression(res0, 0, res0.length()-1);
+                    }
+
+                    if (earlyExit)
+                        return new OdbcEscapeParseResult(startPos, curPos - startPos + 1,
parseRes);
+                    else
+                        res.append(parseRes);
+
+                    openPos = -1;
+
+                    plainPos = curPos + 1;
+                }
+            }
+
+            curPos++;
+        }
+
+        if (openPos != -1)
+            throw new IgniteException("Malformed escape sequence (closing curly brace missing):
" + text);
+
+        if (curPos > plainPos)
+            res.append(text, plainPos, curPos);
+
+        return new OdbcEscapeParseResult(startPos, curPos - startPos + 1, res.toString());
+    }
+
+    /**
+     * Parse concrete expression.
+     *
+     * @param text Text.
+     * @param startPos Start position within text.
+     * @param len Length.
+     * @return Result.
+     */
+    private static String parseExpression(String text, int startPos, int len) {
+        assert validSubstring(text, startPos, len);
+
+        char firstChar = text.charAt(startPos);
+
+        if (firstChar == '{') {
+            char lastChar = text.charAt(startPos + len);
+
+            if (lastChar != '}')
+                throw new IgniteException("Failed to parse escape sequence because it is
not enclosed: " +
+                    substring(text, startPos, len));
+
+            OdbcEscapeType typ = sequenceType(text, startPos, len);
+
+            switch (typ) {
+                case FN:
+                    return parseScalarExpression(text, startPos, len);
+
+                default: {
+                    assert false : "Unknown expression type: " + typ;
+
+                    return null;
+                }
+            }
+        }
+        else {
+            // Nothing to escape, return original string.
+            if (startPos == 0 || text.length() == len)
+                return text;
+            else
+                return text.substring(startPos, startPos + len);
+        }
+    }
+
+    /**
+     * Parse concrete expression.
+     *
+     * @param text Text.
+     * @param startPos Start position.
+     * @param len Length.
+     * @return Parsed expression.
+     */
+    private static String parseScalarExpression(String text, int startPos, int len) {
+        assert validSubstring(text, startPos, len);
+
+        return substring(text, startPos + 3, len - 3).trim();
+    }
+
+    /**
+     * Append nested results.
+     *
+     * @param text Original text.
+     * @param startPos Start position.
+     * @param endPos End position.
+     * @param nestedRess Nested results.
+     * @return Result.
+     */
+    private static String appendNested(String text, int startPos, int endPos,
+        LinkedList<OdbcEscapeParseResult> nestedRess) {
+        StringBuilder res = new StringBuilder();
+
+        int curPos = startPos;
+
+        for (OdbcEscapeParseResult nestedRes : nestedRess) {
+            // Append text between current position and replace.
+            res.append(text, curPos, nestedRes.originalStart());
+
+            // Append replaced text.
+            res.append(nestedRes.result());
+
+            // Advance position.
+            curPos = nestedRes.originalStart() + nestedRes.originalLength();
+        }
+
+        // Append remainder.
+        res.append(text, curPos, endPos);
+
+        return res.toString();
+    }
+
+    /**
+     * Get escape sequence type.
+     *
+     * @param text Text.
+     * @param startPos Start position.
+     * @return Escape sequence type.
+     */
+    private static OdbcEscapeType sequenceType(String text, int startPos, int len) {
+        assert validSubstring(text, startPos, len);
+        assert text.charAt(startPos) == '{';
+
+        if (text.startsWith("fn", startPos + 1))
+            return OdbcEscapeType.FN;
+
+        throw new IgniteException("Unsupported escape sequence: " + text.substring(startPos,
startPos + len));
+    }
+
+    /**
+     * Perform "substring" using start position and length.
+     *
+     * @param text Text.
+     * @param startPos Start position.
+     * @param len Length.
+     * @return Substring.
+     */
+    private static String substring(String text, int startPos, int len) {
+        assert validSubstring(text, startPos, len);
+
+        return text.substring(startPos, startPos + len);
+    }
+
+    /**
+     * Check whether substring is valid.
+     *
+     * @param text Substring.
+     * @param startPos Start position.
+     * @param len Length.
+     * @return {@code True} if valid.
+     */
+    private static boolean validSubstring(String text, int startPos, int len) {
+        return text != null && startPos + len <= text.length();
+    }
+
+    /**
+     * Private constructor.
+     */
+    private OdbcEscapeUtils() {
+        // No-op.
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4e9e7b8e/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java
b/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java
new file mode 100644
index 0000000..73fa0f4
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/odbc/OdbcEscapeSequenceSelfTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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 org.apache.ignite.IgniteException;
+import org.apache.ignite.internal.processors.odbc.escape.OdbcEscapeUtils;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+
+import java.util.concurrent.Callable;
+
+/**
+ * Scalar function escape sequence parser tests.
+ */
+public class OdbcEscapeSequenceSelfTest extends GridCommonAbstractTest {
+    /**
+     * Test simple cases.
+     */
+    public void testSimple() {
+        check(
+            "select * from table;",
+            "select * from table;"
+        );
+
+        check(
+            "test()",
+            "{fn test()}"
+        );
+
+        check(
+            "select test()",
+            "select {fn test()}"
+        );
+
+        check(
+            "select test() from table;",
+            "select {fn test()} from table;"
+        );
+    }
+
+    /**
+     * Test escape sequence series.
+     */
+    public void testSimpleFunction() throws Exception {
+        check(
+            "func(field1) func(field2)",
+            "{fn func(field1)} {fn func(field2)}"
+        );
+
+        check(
+            "select func(field1), func(field2)",
+            "select {fn func(field1)}, {fn func(field2)}"
+        );
+
+        check(
+            "select func(field1), func(field2) from table;",
+            "select {fn func(field1)}, {fn func(field2)} from table;"
+        );
+    }
+
+    /**
+     * Test simple nested escape sequences. Depth = 2.
+     */
+    public void testNestedFunction() throws Exception {
+        check(
+            "func1(field1, func2(field2))",
+            "{fn func1(field1, {fn func2(field2)})}"
+        );
+
+        check(
+            "select func1(field1, func2(field2))",
+            "select {fn func1(field1, {fn func2(field2)})}"
+        );
+
+        check(
+            "select func1(field1, func2(field2), field3) from SomeTable;",
+            "select {fn func1(field1, {fn func2(field2)}, field3)} from SomeTable;"
+        );
+    }
+
+    /**
+     * Test nested escape sequences. Depth > 2.
+     */
+    public void testDeepNestedFunction() {
+        check(
+            "func1(func2(func3(field1)))",
+            "{fn func1({fn func2({fn func3(field1)})})}"
+        );
+
+        check(
+            "func1(func2(func3(func4(field1))))",
+            "{fn func1({fn func2({fn func3({fn func4(field1)})})})}"
+        );
+
+        check(
+            "select func1(field1, func2(func3(field2), field3))",
+            "select {fn func1(field1, {fn func2({fn func3(field2)}, field3)})}"
+        );
+
+        check(
+            "select func1(field1, func2(func3(field2), field3)) from SomeTable;",
+            "select {fn func1(field1, {fn func2({fn func3(field2)}, field3)})} from SomeTable;"
+        );
+    }
+
+    /**
+     * Test series of nested escape sequences.
+     */
+    public void testNestedFunctionMixed() {
+        check(
+            "func1(func2(field1), func3(field2))",
+            "{fn func1({fn func2(field1)}, {fn func3(field2)})}"
+        );
+
+        check(
+            "select func1(func2(field1), func3(field2)) from table;",
+            "select {fn func1({fn func2(field1)}, {fn func3(field2)})} from table;"
+        );
+
+        check(
+            "func1(func2(func3(field1))) func1(func2(field2))",
+            "{fn func1({fn func2({fn func3(field1)})})} {fn func1({fn func2(field2)})}"
+        );
+    }
+
+    /**
+     * Test non-closed escape sequence.
+     */
+    public void testFailedOnInvalidSequence1() {
+        checkFail("select {fn func1(field1, {fn func2(field2), field3)} from SomeTable;");
+    }
+
+    /**
+     * Test closing undeclared escape sequence.
+     */
+    public void testFailedOnClosingNotOpenedSequence() {
+        checkFail("select {fn func1(field1, func2(field2)}, field3)} from SomeTable;");
+    }
+
+    /**
+     * Check parsing logic.
+     *
+     * @param exp Expected result.
+     * @param qry SQL query text.
+     */
+    private void check(String exp, String qry) {
+        String actualRes = OdbcEscapeUtils.parse(qry);
+
+        assertEquals(exp, actualRes);
+    }
+
+    /**
+     * Check that query parsing fails.
+     *
+     * @param qry Query.
+     */
+    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+    private void checkFail(final String qry) {
+        GridTestUtils.assertThrows(null, new Callable<Void>() {
+            @Override public Void call() throws Exception {
+                OdbcEscapeUtils.parse(qry);
+
+                fail("Parsing should fail: " + qry);
+
+                return null;
+            }
+        }, IgniteException.class, null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/4e9e7b8e/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
----------------------------------------------------------------------
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
index d56c29d..6bb2c11 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBasicTestSuite.java
@@ -43,6 +43,7 @@ import org.apache.ignite.internal.processors.closure.GridClosureProcessorSelfTes
 import org.apache.ignite.internal.processors.continuous.GridEventConsumeSelfTest;
 import org.apache.ignite.internal.processors.continuous.GridMessageListenSelfTest;
 import org.apache.ignite.internal.processors.odbc.OdbcProcessorValidationSelfTest;
+import org.apache.ignite.internal.processors.odbc.OdbcEscapeSequenceSelfTest;
 import org.apache.ignite.internal.processors.service.ClosureServiceClientsNodesTest;
 import org.apache.ignite.internal.product.GridProductVersionSelfTest;
 import org.apache.ignite.internal.util.nio.IgniteExceptionInNioWorkerSelfTest;
@@ -128,6 +129,7 @@ public class IgniteBasicTestSuite extends TestSuite {
         suite.addTestSuite(IgniteExceptionInNioWorkerSelfTest.class);
 
         suite.addTestSuite(OdbcProcessorValidationSelfTest.class);
+        suite.addTestSuite(OdbcEscapeSequenceSelfTest.class);
 
         GridTestUtils.addTestIfNeeded(suite, DynamicProxySerializationMultiJvmSelfTest.class,
ignoredTests);
 


Mime
View raw message