Repository: ignite Updated Branches: refs/heads/master 76f26c1e6 -> d98cd3093
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/master Commit: 4e9e7b8ee1c990bacdc2d081b706ca315927fdce Parents: f925873 Author: vozerov-gridgain <[email protected]> Authored: Wed Aug 24 12:12:00 2016 +0300 Committer: vozerov-gridgain <[email protected]> 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);
