IGNITE-3738: ODBC: Fixed escape sequence whitespaces handling. This closes #982.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/e21111f2 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/e21111f2 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/e21111f2 Branch: refs/heads/master Commit: e21111f287039011bc9437c94fb574e61e2ac226 Parents: 0e3a6e2 Author: Andrey V. Mashenkov <[email protected]> Authored: Thu Aug 25 16:26:02 2016 +0300 Committer: vozerov-gridgain <[email protected]> Committed: Thu Aug 25 16:26:02 2016 +0300 ---------------------------------------------------------------------- .../processors/odbc/escape/OdbcEscapeToken.java | 61 +++++++++++ .../processors/odbc/escape/OdbcEscapeType.java | 81 ++++++++++++++- .../processors/odbc/escape/OdbcEscapeUtils.java | 103 +++++++++++++------ .../odbc/OdbcEscapeSequenceSelfTest.java | 15 ++- 4 files changed, 225 insertions(+), 35 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/e21111f2/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeToken.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeToken.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeToken.java new file mode 100644 index 0000000..6bb4f81 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/escape/OdbcEscapeToken.java @@ -0,0 +1,61 @@ +/* + * 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 token. + */ +public class OdbcEscapeToken { + /** Escape sequence type. */ + private final OdbcEscapeType type; + + /** Token length. */ + private final int len; + + /** + * Constructor. + * + * @param type Escape sequence type. + * @param len Token length. + */ + public OdbcEscapeToken(OdbcEscapeType type, int len) { + this.type = type; + this.len = len; + } + + /** + * @return Escape sequence type. + */ + public OdbcEscapeType type() { + return type; + } + + /** + * @return Token length. + */ + public int length() { + return len; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(OdbcEscapeToken.class, this); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/e21111f2/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 index 2df413f..96a2127 100644 --- 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 @@ -22,5 +22,84 @@ package org.apache.ignite.internal.processors.odbc.escape; */ public enum OdbcEscapeType { /** Scalar function. */ - FN + SCALAR_FUNCTION("fn", true, false), + + /** Outer join. */ + OUTER_JOIN("oj", true, false), + + /** Date. */ + DATE("d", true, false), + + /** Timestamp. */ + TIMESTAMP("ts", true, false), + + /** Time. */ + TIME("t", true, false), + + /** GUID. */ + GUID("guid", true, false), + + /** LIKE clause. */ + LIKE("\'", false, true); + + /** Values in convenient order. */ + private static final OdbcEscapeType[] VALS = new OdbcEscapeType[] { + SCALAR_FUNCTION, // Assume that scalar functions are very frequent. + DATE, TIMESTAMP, // Date and timestamp are relatively frequent as well; also TS must go before T. + OUTER_JOIN, // Joins are less frequent, + LIKE, TIME, GUID // LIKE, TIME and GUID are even less frequent. + }; + + /** + * Get values in convenient order, where the most frequent values goes first, and "startsWith" invocation is + * enough to get type (i.e. "ts" goes before "t"). + * + * @return Values. + */ + public static OdbcEscapeType[] sortedValues() { + return VALS; + } + + /** Escape sequence body. */ + private final String body; + + /** Whether token must be delimited from the rest of escape sequence. */ + private final boolean delimited; + + /** Whether empty escape sequence is allowed. */ + private final boolean allowEmpty; + + /** + * Constructor. + * + * @param body Escape sequence body. + * @param delimited Whether token must be delimited from the rest of escape sequence. + * @param allowEmpty Whether empty escape sequence is allowed. + */ + OdbcEscapeType(String body, boolean delimited, boolean allowEmpty) { + this.body = body; + this.delimited = delimited; + this.allowEmpty = allowEmpty; + } + + /** + * @return Escape sequence body. + */ + public String body() { + return body; + } + + /** + * @return Whether token must be delimited from the rest of escape sequence. + */ + public boolean delimited() { + return delimited; + } + + /** + * @return Whether empty escape sequence is allowed. + */ + public boolean allowEmpty() { + return allowEmpty; + } } http://git-wip-us.apache.org/repos/asf/ignite/blob/e21111f2/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 index 4d8ca69..6299c7e 100644 --- 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 @@ -90,18 +90,18 @@ public class OdbcEscapeUtils { if (nested == null) // Found sequence without nesting, process it. - parseRes = parseExpression(text, openPos, curPos - openPos); + parseRes = parseExpression(text, openPos, curPos + 1 - openPos); else { // Special case to process nesting. String res0 = appendNested(text, openPos, curPos + 1, nested); nested = null; - parseRes = parseExpression(res0, 0, res0.length()-1); + parseRes = parseExpression(res0, 0, res0.length()); } if (earlyExit) - return new OdbcEscapeParseResult(startPos, curPos - startPos + 1, parseRes); + return new OdbcEscapeParseResult(startPos, curPos + 1 - startPos, parseRes); else res.append(parseRes); @@ -137,23 +137,21 @@ public class OdbcEscapeUtils { char firstChar = text.charAt(startPos); if (firstChar == '{') { - char lastChar = text.charAt(startPos + len); + char lastChar = text.charAt(startPos + len - 1); 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); + OdbcEscapeToken token = parseToken(text, startPos, len); - switch (typ) { - case FN: - return parseScalarExpression(text, startPos, len); + switch (token.type()) { + case SCALAR_FUNCTION: + return parseScalarExpression(text, startPos, len, token); - default: { - assert false : "Unknown expression type: " + typ; - - return null; - } + default: + throw new IgniteException("Unsupported escape sequence token [text=" + + substring(text, startPos, len) + ", token=" + token.type().body() + ']'); } } else { @@ -161,8 +159,60 @@ public class OdbcEscapeUtils { if (startPos == 0 || text.length() == len) return text; else - return text.substring(startPos, startPos + len); + return substring(text, startPos, len); + } + } + + /** + * Get escape sequence info. + * + * @param text Text. + * @param startPos Start position. + * @return Escape sequence info. + */ + private static OdbcEscapeToken parseToken(String text, int startPos, int len) { + assert validSubstring(text, startPos, len); + assert text.charAt(startPos) == '{'; + + int pos = startPos + 1; + + while (Character.isWhitespace(text.charAt(pos))) + pos++; + + OdbcEscapeType curTyp = null; + boolean empty = false; + + for (OdbcEscapeType typ : OdbcEscapeType.sortedValues()) { + if (text.startsWith(typ.body(), pos)) { + pos += typ.body().length(); + + if (typ == OdbcEscapeType.LIKE) + throw new IgniteException("LIKE escape sequence is not supported yet."); + else { + empty = (startPos + len == pos + 1); + + if (!empty && typ.delimited()) { + char charAfter = text.charAt(pos); + + if (!Character.isWhitespace(charAfter)) + throw new IgniteException("Unexpected escape sequence token: " + + substring(text, startPos, len)); + } + } + + curTyp = typ; + + break; + } } + + if (curTyp == null) + throw new IgniteException("Unsupported escape sequence: " + substring(text, startPos, len)); + + if (empty && !curTyp.allowEmpty()) + throw new IgniteException("Escape sequence cannot be empty: " + substring(text, startPos, len)); + + return new OdbcEscapeToken(curTyp, pos - (startPos + 1)); } /** @@ -171,12 +221,16 @@ public class OdbcEscapeUtils { * @param text Text. * @param startPos Start position. * @param len Length. + * @param token Token. * @return Parsed expression. */ - private static String parseScalarExpression(String text, int startPos, int len) { + private static String parseScalarExpression(String text, int startPos, int len, OdbcEscapeToken token) { assert validSubstring(text, startPos, len); - return substring(text, startPos + 3, len - 3).trim(); + int startPos0 = startPos + 1 /* open brace */ + token.length() /* token. */; + int len0 = len - 1 /* open brace */ - token.length() /* token */ - 1 /* close brace */; + + return substring(text, startPos0, len0).trim(); } /** @@ -212,23 +266,6 @@ public class OdbcEscapeUtils { } /** - * 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. http://git-wip-us.apache.org/repos/asf/ignite/blob/e21111f2/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 index 73fa0f4..d9be6cc 100644 --- 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 @@ -141,7 +141,7 @@ public class OdbcEscapeSequenceSelfTest extends GridCommonAbstractTest { /** * Test non-closed escape sequence. */ - public void testFailedOnInvalidSequence1() { + public void testFailedOnNonClosedEscapeSequence() { checkFail("select {fn func1(field1, {fn func2(field2), field3)} from SomeTable;"); } @@ -153,6 +153,19 @@ public class OdbcEscapeSequenceSelfTest extends GridCommonAbstractTest { } /** + * Test escape sequences with additional whitespace characters + */ + public void testFunctionEscapeSequenceWithWhitespaces() throws Exception { + check("func1()", "{ fn func1()}"); + + check("func1()", "{ fn func1()}"); + + check("func1()", "{ \n fn func1()}"); + + checkFail("{ \n func1()}"); + } + + /** * Check parsing logic. * * @param exp Expected result.
