Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 0A3AC200D61 for ; Mon, 13 Nov 2017 10:52:00 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 08DCA160BF3; Mon, 13 Nov 2017 09:52:00 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 69B1B160C06 for ; Mon, 13 Nov 2017 10:51:57 +0100 (CET) Received: (qmail 41206 invoked by uid 500); 13 Nov 2017 09:51:56 -0000 Mailing-List: contact commits-help@ignite.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ignite.apache.org Delivered-To: mailing list commits@ignite.apache.org Received: (qmail 41196 invoked by uid 99); 13 Nov 2017 09:51:56 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 13 Nov 2017 09:51:56 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id DF127DFE15; Mon, 13 Nov 2017 09:51:55 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: sboikov@apache.org To: commits@ignite.apache.org Date: Mon, 13 Nov 2017 09:51:57 -0000 Message-Id: <9e5b5c2903d54a7b8c928a1669f322fe@git.apache.org> In-Reply-To: <7fe468c3a1934aaeaa8a26f0e695af20@git.apache.org> References: <7fe468c3a1934aaeaa8a26f0e695af20@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [03/28] ignite git commit: IGNITE-6480: SQL: implemented base parser/lexer and CREATE INDEX command support. This closes #3001. archived-at: Mon, 13 Nov 2017 09:52:00 -0000 IGNITE-6480: SQL: implemented base parser/lexer and CREATE INDEX command support. This closes #3001. Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/145c59dd Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/145c59dd Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/145c59dd Branch: refs/heads/ignite-zk Commit: 145c59dd79fd796d6f5590d3e1f822d6a305db41 Parents: ca6a009 Author: devozerov Authored: Thu Nov 9 11:01:05 2017 +0300 Committer: devozerov Committed: Thu Nov 9 11:01:05 2017 +0300 ---------------------------------------------------------------------- .../apache/ignite/internal/sql/SqlKeyword.java | 237 ++++++++++++ .../apache/ignite/internal/sql/SqlLexer.java | 213 +++++++++++ .../internal/sql/SqlLexerLookAheadToken.java | 75 ++++ .../ignite/internal/sql/SqlLexerToken.java | 48 +++ .../ignite/internal/sql/SqlLexerTokenType.java | 112 ++++++ .../ignite/internal/sql/SqlParseException.java | 99 +++++ .../apache/ignite/internal/sql/SqlParser.java | 174 +++++++++ .../ignite/internal/sql/SqlParserUtils.java | 363 +++++++++++++++++++ .../ignite/internal/sql/command/SqlCommand.java | 43 +++ .../sql/command/SqlCreateIndexCommand.java | 200 ++++++++++ .../internal/sql/command/SqlIndexColumn.java | 61 ++++ .../internal/sql/command/SqlQualifiedName.java | 70 ++++ .../ignite/internal/sql/SqlParserSelfTest.java | 198 ++++++++++ .../processors/query/h2/IgniteH2Indexing.java | 66 +++- .../query/h2/ddl/DdlStatementsProcessor.java | 80 +++- .../IgniteCacheQuerySelfTestSuite.java | 3 + 16 files changed, 2037 insertions(+), 5 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlKeyword.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlKeyword.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlKeyword.java new file mode 100644 index 0000000..ac826cc --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlKeyword.java @@ -0,0 +1,237 @@ +/* + * 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.sql; + +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.util.typedef.F; + +import java.lang.reflect.Field; +import java.util.HashSet; + +/** + * SQL keyword constants. + */ +public class SqlKeyword { + /** Keyword: ASC. */ + public static final String ASC = "ASC"; + + /** Keyword: BIGINT */ + public static final String BIGINT = "BIGINT"; + + /** Keyword: BIT. */ + public static final String BIT = "BIT"; + + /** Keyword: BOOL. */ + public static final String BOOL = "BOOL"; + + /** Keyword: BOOLEAN. */ + public static final String BOOLEAN = "BOOLEAN"; + + /** Keyword: CASCADE. */ + public static final String CASCADE = "CASCADE"; + + /** Keyword: CHAR. */ + public static final String CHAR = "CHAR"; + + /** Keyword: CHARACTER. */ + public static final String CHARACTER = "CHARACTER"; + + /** Keyword: CREATE. */ + public static final String CREATE = "CREATE"; + + /** Keyword: DATE. */ + public static final String DATE = "DATE"; + + /** Keyword: DATETIME. */ + public static final String DATETIME = "DATETIME"; + + /** Keyword: DEC. */ + public static final String DEC = "DEC"; + + /** Keyword: DECIMAL. */ + public static final String DECIMAL = "DECIMAL"; + + /** Keyword: DESC. */ + public static final String DESC = "DESC"; + + /** Keyword: DOUBLE. */ + public static final String DOUBLE = "DOUBLE"; + + /** Keyword: DROP. */ + public static final String DROP = "DROP"; + + /** Keyword: EXISTS. */ + public static final String EXISTS = "EXISTS"; + + /** Keyword: FLOAT. */ + public static final String FLOAT = "FLOAT"; + + /** Keyword: FLOAT4. */ + public static final String FLOAT4 = "FLOAT4"; + + /** Keyword: FLOAT8. */ + public static final String FLOAT8 = "FLOAT8"; + + /** Keyword: FULLTEXT. */ + public static final String FULLTEXT = "FULLTEXT"; + + /** Keyword: UNIQUE. */ + public static final String HASH = "HASH"; + + /** Keyword: IF. */ + public static final String IF = "IF"; + + /** Keyword: INDEX. */ + public static final String INDEX = "INDEX"; + + /** Keyword: INT. */ + public static final String INT = "INT"; + + /** Keyword: INT2. */ + public static final String INT2 = "INT2"; + + /** Keyword: INT4. */ + public static final String INT4 = "INT4"; + + /** Keyword: INT8. */ + public static final String INT8 = "INT8"; + + /** Keyword: INTEGER. */ + public static final String INTEGER = "INTEGER"; + + /** Keyword: KEY. */ + public static final String KEY = "KEY"; + + /** Keyword: LONGVARCHAR. */ + public static final String LONGVARCHAR = "LONGVARCHAR"; + + /** Keyword: MEDIUMINT. */ + public static final String MEDIUMINT = "MEDIUMINT"; + + /** Keyword: NCHAR. */ + public static final String NCHAR = "NCHAR"; + + /** Keyword: NOT. */ + public static final String NOT = "NOT"; + + /** Keyword: NUMBER. */ + public static final String NUMBER = "NUMBER"; + + /** Keyword: NUMERIC. */ + public static final String NUMERIC = "NUMERIC"; + + /** Keyword: NVARCHAR. */ + public static final String NVARCHAR = "NVARCHAR"; + + /** Keyword: NVARCHAR2. */ + public static final String NVARCHAR2 = "NVARCHAR2"; + + /** Keyword: ON. */ + public static final String ON = "ON"; + + /** Keyword: PRECISION. */ + public static final String PRECISION = "PRECISION"; + + /** Keyword: PRIMARY. */ + public static final String PRIMARY = "PRIMARY"; + + /** Keyword: REAL. */ + public static final String REAL = "REAL"; + + /** Keyword: RESTRICT. */ + public static final String RESTRICT = "RESTRICT"; + + /** Keyword: SIGNED. */ + public static final String SIGNED = "SIGNED"; + + /** Keyword: SMALLDATETIME. */ + public static final String SMALLDATETIME = "SMALLDATETIME"; + + /** Keyword: SMALLINT. */ + public static final String SMALLINT = "SMALLINT"; + + /** Keyword: SPATIAL. */ + public static final String SPATIAL = "SPATIAL"; + + /** Keyword: TABLE. */ + public static final String TABLE = "TABLE"; + + /** Keyword: TIME. */ + public static final String TIME = "TIME"; + + /** Keyword: TIMESTAMP. */ + public static final String TIMESTAMP = "TIMESTAMP"; + + /** Keyword: TINYINT. */ + public static final String TINYINT = "TINYINT"; + + /** Keyword: UNIQUE. */ + public static final String UNIQUE = "UNIQUE"; + + /** Keyword: UUID. */ + public static final String UUID = "UUID"; + + /** Keyword: VARCHAR. */ + public static final String VARCHAR = "VARCHAR"; + + /** Keyword: VARCHAR2. */ + public static final String VARCHAR2 = "VARCHAR2"; + + /** Keyword: VARCHAR_CASESENSITIVE. */ + public static final String VARCHAR_CASESENSITIVE = "VARCHAR_CASESENSITIVE"; + + /** Keyword: YEAR. */ + public static final String YEAR = "YEAR"; + + /** All keywords. */ + private static final HashSet KEYWORDS; + + static { + KEYWORDS = new HashSet<>(); + + try { + for (Field field : SqlKeyword.class.getDeclaredFields()) { + if (F.eq(String.class, field.getType())) { + String val = (String) field.get(null); + + KEYWORDS.add(val); + } + } + } + catch (ReflectiveOperationException e) { + throw new IgniteException("Failed to initialize keywords collection.", e); + } + } + + /** + * Check if string is a keyword. + * + * @param str String. + * @return {@code True} if it is a keyword. + */ + public static boolean isKeyword(String str) { + return KEYWORDS.contains(str); + } + + /** + * Private constructor. + */ + private SqlKeyword() { + // No-op. + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexer.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexer.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexer.java new file mode 100644 index 0000000..a8009b7 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexer.java @@ -0,0 +1,213 @@ +/* + * 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.sql; + +import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode; + +/** + * SQL lexer. + */ +public class SqlLexer implements SqlLexerToken { + /** Original input. */ + private final String sql; + + /** Input characters. */ + private final char[] inputChars; + + /** Current position. */ + private int pos; + + /** Current token start. */ + private int tokenPos; + + /** Current token. */ + private String token; + + /** Token type. */ + private SqlLexerTokenType tokenTyp; + + /** + * Constructor. + * + * @param sql Input. + */ + public SqlLexer(String sql) { + assert sql != null; + + this.sql = sql; + + // Additional slot for look-ahead convenience. + inputChars = new char[sql.length() + 1]; + + for (int i = 0; i < sql.length(); i++) + inputChars[i] = sql.charAt(i); + } + + /** + * Get next token without lexer state change. + * + * @return Next token. + */ + public SqlLexerToken lookAhead() { + int pos0 = pos; + String token0 = token; + int tokenPos0 = tokenPos; + SqlLexerTokenType tokenTyp0 = tokenTyp; + + try { + if (shift()) + return new SqlLexerLookAheadToken(sql, token, tokenPos, tokenTyp); + else + return new SqlLexerLookAheadToken(sql, null, tokenPos, SqlLexerTokenType.EOF); + } + finally { + pos = pos0; + token = token0; + tokenPos = tokenPos0; + tokenTyp = tokenTyp0; + } + } + + /** + * Shift lexer to the next position. + * + * @return {@code True} if next token was found, {@code false} in case of end-of-file. + */ + public boolean shift() { + while (!eod()) { + int tokenStartPos0 = pos; + + String token0 = null; + SqlLexerTokenType tokenTyp0 = null; + + char c = inputChars[pos++]; + + switch (c) { + case '-': + if (inputChars[pos] == '-') { + // Full-line comment. + pos++; + + while (!eod()) { + char c1 = inputChars[pos]; + + if (c1 == '\n' || c1 == '\r') + break; + + pos++; + } + } + else { + // Minus. + token0 = "-"; + tokenTyp0 = SqlLexerTokenType.MINUS; + } + + break; + + case '\"': + while (true) { + if (eod()) { + throw new SqlParseException(sql, tokenStartPos0, IgniteQueryErrorCode.PARSING, + "Unclosed quoted identifier."); + } + + char c1 = inputChars[pos]; + + pos++; + + if (c1 == '\"') + break; + } + + token0 = sql.substring(tokenStartPos0 + 1, pos - 1); + tokenTyp0 = SqlLexerTokenType.QUOTED; + + break; + + case '.': + case ',': + case ';': + case '(': + case ')': + token0 = Character.toString(c); + tokenTyp0 = SqlLexerTokenType.forChar(c); + + break; + + default: + if (c <= ' ' || Character.isSpaceChar(c)) + continue; + + while (!eod()) { + char c1 = inputChars[pos]; + + if (!Character.isJavaIdentifierPart(c1)) + break; + + pos++; + } + + token0 = sql.substring(tokenStartPos0, pos).toUpperCase(); + tokenTyp0 = SqlLexerTokenType.DEFAULT; + } + + if (tokenTyp0 != null) { + token = token0; + tokenPos = tokenStartPos0; + tokenTyp = tokenTyp0; + + return true; + } + } + + return false; + } + + /** {@inheritDoc} */ + public String sql() { + return sql; + } + + /** {@inheritDoc} */ + public String token() { + return token; + } + + /** {@inheritDoc} */ + public char tokenFirstChar() { + return token.charAt(0); + } + + /** {@inheritDoc} */ + public int tokenPosition() { + return tokenPos; + } + + /** {@inheritDoc} */ + public SqlLexerTokenType tokenType() { + return tokenTyp; + } + + /** + * @return {@code True} if end of data is reached. + */ + private boolean eod() { + return pos == inputChars.length - 1; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerLookAheadToken.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerLookAheadToken.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerLookAheadToken.java new file mode 100644 index 0000000..e697473 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerLookAheadToken.java @@ -0,0 +1,75 @@ +/* + * 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.sql; + +/** + * Plain immutable look-ahead parser token. + */ +public class SqlLexerLookAheadToken implements SqlLexerToken { + /** SQL. */ + private final String sql; + + /** Token. */ + private final String token; + + /** Token position. */ + private final int tokenPos; + + /** Token type. */ + private final SqlLexerTokenType tokenTyp; + + /** + * Constructor. + * + * @param sql Original SQL. + * @param token Token. + * @param tokenPos Token position. + * @param tokenTyp Token type. + */ + public SqlLexerLookAheadToken(String sql, String token, int tokenPos, SqlLexerTokenType tokenTyp) { + this.sql = sql; + this.token = token; + this.tokenPos = tokenPos; + this.tokenTyp = tokenTyp; + } + + /** {@inheritDoc} */ + public String sql() { + return sql; + } + + /** {@inheritDoc} */ + @Override public String token() { + return token; + } + + /** {@inheritDoc} */ + @Override public char tokenFirstChar() { + return token.charAt(0); + } + + /** {@inheritDoc} */ + @Override public int tokenPosition() { + return tokenPos; + } + + /** {@inheritDoc} */ + @Override public SqlLexerTokenType tokenType() { + return tokenTyp; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerToken.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerToken.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerToken.java new file mode 100644 index 0000000..a172635 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerToken.java @@ -0,0 +1,48 @@ +/* + * 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.sql; + +/** + * SQL parser token interface. + */ +public interface SqlLexerToken { + /** + * @return Original SQL. + */ + public String sql(); + + /** + * @return Current token. + */ + public String token(); + + /** + * @return First character of the current token. + */ + public char tokenFirstChar(); + + /** + * @return Current token start position. + */ + public int tokenPosition(); + + /** + * @return Token type. + */ + public SqlLexerTokenType tokenType(); +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerTokenType.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerTokenType.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerTokenType.java new file mode 100644 index 0000000..693832b --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlLexerTokenType.java @@ -0,0 +1,112 @@ +/* + * 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.sql; + +import java.util.HashMap; + +/** + * Lexer token type. + */ +public enum SqlLexerTokenType { + /** Standard word. */ + DEFAULT, + + /** Quoted phrase. */ + QUOTED, + + /** Minus sign. */ + MINUS('-'), + + /** Dot. */ + DOT('.'), + + /** Comma. */ + COMMA(','), + + /** Parenthesis: left. */ + PARENTHESIS_LEFT('('), + + /** Parenthesis: right. */ + PARENTHESIS_RIGHT(')'), + + /** Semicolon. */ + SEMICOLON(';'), + + /** End of string. */ + EOF; + + /** Mapping from character to type.. */ + private static final HashMap CHAR_TO_TYP = new HashMap<>(); + + /** Character. */ + private final Character c; + + /** Character as string. */ + private final String str; + + static { + for (SqlLexerTokenType typ : SqlLexerTokenType.values()) { + Character c = typ.asChar(); + + if (c != null) + CHAR_TO_TYP.put(c, typ); + } + } + + /** + * Get token type for character. + * + * @param c Character. + * @return Type. + */ + public static SqlLexerTokenType forChar(char c) { + return CHAR_TO_TYP.get(c); + } + + /** + * Constructor. + */ + SqlLexerTokenType() { + this(null); + } + + /** + * Constructor. + * + * @param c Corresponding character. + */ + SqlLexerTokenType(Character c) { + this.c = c; + + str = c != null ? c.toString() : null; + } + + /** + * @return Character. + */ + public Character asChar() { + return c; + } + + /** + * @return Character as string. + */ + public String asString() { + return str; + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParseException.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParseException.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParseException.java new file mode 100644 index 0000000..96d385d --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParseException.java @@ -0,0 +1,99 @@ +/* + * 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.sql; + +import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.util.typedef.internal.S; + +/** + * Parse exception. + */ +public class SqlParseException extends IgniteException { + /** */ + private static final long serialVersionUID = 0L; + + /** SQL command. */ + private final String sql; + + /** Position. */ + private final int pos; + + /** Error code. */ + private final int code; + + /** + * Constructor. + * + * @param sql SQL command. + * @param pos Position. + * @param code Error code (parsing, unsupported operation, etc.). + * @param msg Message. + */ + public SqlParseException(String sql, int pos, int code, String msg) { + super(prepareMessage(sql, pos, msg)); + + this.sql = sql; + this.pos = pos; + this.code = code; + } + + /** + * Prepare message. + * + * @param sql Original SQL. + * @param pos Position. + * @param msg Message. + * @return Prepared message. + */ + private static String prepareMessage(String sql, int pos, String msg) { + String sql0; + + if (pos == sql.length()) + sql0 = sql + "[*]"; + else + sql0 = sql.substring(0, pos) + "[*]" + sql.substring(pos); + + return "Failed to parse SQL statement \"" + sql0 + "\": " + msg; + } + + /** + * @return SQL command. + */ + public String sql() { + return sql; + } + + /** + * @return Position. + */ + public int position() { + return pos; + } + + /** + * @return Error code. + */ + public int code() { + return code; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SqlParseException.class, this, "msg", getMessage()); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParser.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParser.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParser.java new file mode 100644 index 0000000..9e0eee0 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParser.java @@ -0,0 +1,174 @@ +/* + * 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.sql; + +import org.apache.ignite.internal.sql.command.SqlCommand; +import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand; +import org.jetbrains.annotations.Nullable; + +import static org.apache.ignite.internal.sql.SqlKeyword.CREATE; +import static org.apache.ignite.internal.sql.SqlKeyword.DROP; +import static org.apache.ignite.internal.sql.SqlKeyword.HASH; +import static org.apache.ignite.internal.sql.SqlKeyword.INDEX; +import static org.apache.ignite.internal.sql.SqlKeyword.PRIMARY; +import static org.apache.ignite.internal.sql.SqlKeyword.SPATIAL; +import static org.apache.ignite.internal.sql.SqlKeyword.TABLE; +import static org.apache.ignite.internal.sql.SqlKeyword.UNIQUE; +import static org.apache.ignite.internal.sql.SqlParserUtils.errorUnexpectedToken; +import static org.apache.ignite.internal.sql.SqlParserUtils.errorUnsupported; +import static org.apache.ignite.internal.sql.SqlParserUtils.errorUnsupportedIfMatchesKeyword; +import static org.apache.ignite.internal.sql.SqlParserUtils.matchesKeyword; + +/** + * SQL parser. + */ +public class SqlParser { + /** Scheme name. */ + private final String schemaName; + + /** Lexer. */ + private final SqlLexer lex; + + /** + * Constructor. + * + * @param schemaName Schema name. + * @param sql Original SQL. + */ + public SqlParser(@Nullable String schemaName, String sql) { + this.schemaName = schemaName; + + lex = new SqlLexer(sql); + } + + /** + * Get next command. + * + * @return Command or {@code null} if end of script is reached. + */ + public SqlCommand nextCommand() { + SqlCommand cmd = nextCommand0(); + + if (cmd != null) { + if (cmd.schemaName() == null) + cmd.schemaName(schemaName); + } + + return cmd; + } + + /** + * Get next command. + * + * @return Command or {@code null} if end of script is reached. + */ + private SqlCommand nextCommand0() { + while (true) { + if (!lex.shift()) + return null; + + switch (lex.tokenType()) { + case SEMICOLON: + // Empty command, skip. + continue; + + case DEFAULT: + SqlCommand cmd = null; + + switch (lex.token()) { + case CREATE: + cmd = processCreate(); + + break; + + case DROP: + cmd = processDrop(); + + break; + } + + if (cmd != null) { + // If there is something behind the command, this is a syntax error. + if (lex.shift() && lex.tokenType() != SqlLexerTokenType.SEMICOLON) + throw errorUnexpectedToken(lex); + + return cmd; + } + else + throw errorUnexpectedToken(lex, CREATE, DROP); + + case QUOTED: + case MINUS: + case DOT: + case COMMA: + case PARENTHESIS_LEFT: + case PARENTHESIS_RIGHT: + default: + throw errorUnexpectedToken(lex); + } + } + } + + /** + * Process CREATE keyword. + * + * @return Command. + */ + private SqlCommand processCreate() { + if (lex.shift() && lex.tokenType() == SqlLexerTokenType.DEFAULT) { + SqlCommand cmd = null; + + switch (lex.token()) { + case INDEX: + cmd = new SqlCreateIndexCommand(); + + break; + + case TABLE: + throw errorUnsupported(lex); + + case SPATIAL: + if (lex.shift() && matchesKeyword(lex, INDEX)) + cmd = new SqlCreateIndexCommand().spatial(true); + else + throw errorUnexpectedToken(lex, INDEX); + + break; + } + + if (cmd != null) + return cmd.parse(lex); + + errorUnsupportedIfMatchesKeyword(lex, HASH, PRIMARY, UNIQUE); + } + + throw errorUnexpectedToken(lex, INDEX, TABLE, SPATIAL); + } + + /** + * Process DROP keyword. + * + * @return Command. + */ + private SqlCommand processDrop() { + if (lex.shift() && lex.tokenType() == SqlLexerTokenType.DEFAULT) + throw errorUnsupported(lex); + + throw errorUnexpectedToken(lex, INDEX, TABLE); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParserUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParserUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParserUtils.java new file mode 100644 index 0000000..cfe4b6f --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/SqlParserUtils.java @@ -0,0 +1,363 @@ +/* + * 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.sql; + +import org.apache.ignite.internal.processors.cache.query.IgniteQueryErrorCode; +import org.apache.ignite.internal.sql.command.SqlQualifiedName; +import org.apache.ignite.internal.util.typedef.F; + +import static org.apache.ignite.internal.sql.SqlKeyword.EXISTS; +import static org.apache.ignite.internal.sql.SqlKeyword.IF; +import static org.apache.ignite.internal.sql.SqlKeyword.NOT; + +/** + * Parser utility methods. + */ +public class SqlParserUtils { + /** + * Parse IF EXISTS statement. + * + * @param lex Lexer. + * @return {@code True} if statement is found. + */ + public static boolean parseIfExists(SqlLexer lex) { + SqlLexerToken token = lex.lookAhead(); + + if (matchesKeyword(token, IF)) { + lex.shift(); + + skipIfMatchesKeyword(lex, EXISTS); + + return true; + } + + return false; + } + + /** + * Parse IF NOT EXISTS statement. + * + * @param lex Lexer. + * @return {@code True} if statement is found. + */ + public static boolean parseIfNotExists(SqlLexer lex) { + SqlLexerToken token = lex.lookAhead(); + + if (matchesKeyword(token, IF)) { + lex.shift(); + + skipIfMatchesKeyword(lex, NOT); + skipIfMatchesKeyword(lex, EXISTS); + + return true; + } + + return false; + } + + /** + * Skip commr or right parenthesis. + * + * @param lex Lexer. + * @return {@code True} if right parenthesis is found. + */ + public static boolean skipCommaOrRightParenthesis(SqlLexer lex) { + if (lex.shift()) { + switch (lex.tokenType()) { + case COMMA: + return false; + + case PARENTHESIS_RIGHT: + return true; + } + } + + throw errorUnexpectedToken(lex, ",", ")"); + } + + /** + * Parse integer value. + * + * @param lex Lexer. + * @return Integer value. + */ + public static int parseInt(SqlLexer lex) { + if (lex.shift() && lex.tokenType() == SqlLexerTokenType.DEFAULT) { + try { + return Integer.parseInt(lex.token()); + } + catch (NumberFormatException e) { + // No-op. + } + } + + throw errorUnexpectedToken(lex, "[number]"); + } + + /** + * Process name. + * + * @param lex Lexer. + * @param additionalExpTokens Additional expected tokens in case of error. + * @return Name. + */ + public static String parseIdentifier(SqlLexer lex, String... additionalExpTokens) { + if (lex.shift() && isVaildIdentifier(lex)) + return lex.token(); + + throw errorUnexpectedToken(lex, "[identifier]", additionalExpTokens); + } + + /** + * Process qualified name. + * + * @param lex Lexer. + * @param additionalExpTokens Additional expected tokens in case of error. + * @return Qualified name. + */ + public static SqlQualifiedName parseQualifiedIdentifier(SqlLexer lex, String... additionalExpTokens) { + if (lex.shift() && isVaildIdentifier(lex)) { + SqlQualifiedName res = new SqlQualifiedName(); + + String first = lex.token(); + + SqlLexerToken nextToken = lex.lookAhead(); + + if (nextToken.tokenType() == SqlLexerTokenType.DOT) { + lex.shift(); + + String second = parseIdentifier(lex); + + return res.schemaName(first).name(second); + } + else + return res.name(first); + } + + throw errorUnexpectedToken(lex, "[qualified identifier]", additionalExpTokens); + } + + /** + * Check if token is identifier. + * + * @param token Token. + * @return {@code True} if we are standing on possible identifier. + */ + public static boolean isVaildIdentifier(SqlLexerToken token) { + switch (token.tokenType()) { + case DEFAULT: + char c = token.tokenFirstChar(); + + if ((c >= 'A' && c <= 'Z') || c == '_') { + if (SqlKeyword.isKeyword(token.token())) + throw errorUnexpectedToken(token, "[identifier]"); + + return true; + } + + throw error(token, "Illegal identifier name: " + token.token()); + + case QUOTED: + return true; + + default: + return false; + } + } + + /** + * Check if current lexer token matches expected. + * + * @param token Token.. + * @param expKeyword Expected keyword. + * @return {@code True} if matches. + */ + public static boolean matchesKeyword(SqlLexerToken token, String expKeyword) { + return token.tokenType() == SqlLexerTokenType.DEFAULT && expKeyword.equals(token.token()); + } + + /** + * Skip token if it matches expected keyword. + * + * @param lex Lexer. + * @param expKeyword Expected keyword. + */ + public static void skipIfMatchesKeyword(SqlLexer lex, String expKeyword) { + if (lex.shift() && matchesKeyword(lex, expKeyword)) + return; + + throw errorUnexpectedToken(lex, expKeyword); + } + + /** + * Skip next token if it matches expected type. + * + * @param lex Lexer. + * @param tokenTyp Expected token type. + */ + public static void skipIfMatches(SqlLexer lex, SqlLexerTokenType tokenTyp) { + if (lex.shift() && F.eq(lex.tokenType(), tokenTyp)) + return; + + throw errorUnexpectedToken(lex, tokenTyp.asString()); + } + + /** + * Create parse exception referring to current lexer position. + * + * @param token Token. + * @param msg Message. + * @return Exception. + */ + public static SqlParseException error(SqlLexerToken token, String msg) { + return error0(token, IgniteQueryErrorCode.PARSING, msg); + } + + /** + * Create parse exception referring to current lexer position. + * + * @param token Token. + * @param code Error code. + * @param msg Message. + * @return Exception. + */ + private static SqlParseException error0(SqlLexerToken token, int code, String msg) { + return new SqlParseException(token.sql(), token.tokenPosition(), code, msg); + } + + /** + * Create generic parse exception due to unexpected token. + * + * @param token Token. + * @return Exception. + */ + public static SqlParseException errorUnexpectedToken(SqlLexerToken token) { + return errorUnexpectedToken0(token); + } + + /** + * Throw unsupported token exception if passed keyword is found. + * + * @param token Token. + * @param keyword Keyword. + */ + public static void errorUnsupportedIfMatchesKeyword(SqlLexerToken token, String keyword) { + if (matchesKeyword(token, keyword)) + throw errorUnsupported(token); + } + + /** + * Throw unsupported token exception if one of passed keywords is found. + * + * @param token Token. + * @param keywords Keywords. + */ + public static void errorUnsupportedIfMatchesKeyword(SqlLexerToken token, String... keywords) { + if (F.isEmpty(keywords)) + return; + + for (String keyword : keywords) + errorUnsupportedIfMatchesKeyword(token, keyword); + } + + /** + * Error on unsupported keyword. + * + * @param token Token. + * @return Error. + */ + public static SqlParseException errorUnsupported(SqlLexerToken token) { + throw error0(token, IgniteQueryErrorCode.UNSUPPORTED_OPERATION, + "Unsupported keyword: \"" + token.token() + "\""); + } + + /** + * Create generic parse exception due to unexpected token. + * + * @param lex Lexer. + * @param expToken Expected token. + * @return Exception. + */ + public static SqlParseException errorUnexpectedToken(SqlLexer lex, String expToken) { + return errorUnexpectedToken0(lex, expToken); + } + + /** + * Create generic parse exception due to unexpected token. + * + * @param token Token. + * @param firstExpToken First expected token. + * @param expTokens Additional expected tokens (if any). + * @return Exception. + */ + public static SqlParseException errorUnexpectedToken(SqlLexerToken token, String firstExpToken, + String... expTokens) { + if (F.isEmpty(expTokens)) + return errorUnexpectedToken0(token, firstExpToken); + else { + String[] expTokens0 = new String[expTokens.length + 1]; + + expTokens0[0] = firstExpToken; + + System.arraycopy(expTokens, 0, expTokens0, 1, expTokens.length); + + throw errorUnexpectedToken0(token, expTokens0); + } + } + + /** + * Create generic parse exception due to unexpected token. + * + * @param token Token. + * @param expTokens Expected tokens (if any). + * @return Exception. + */ + @SuppressWarnings("StringConcatenationInsideStringBufferAppend") + private static SqlParseException errorUnexpectedToken0(SqlLexerToken token, String... expTokens) { + String token0 = token.token(); + + StringBuilder msg = new StringBuilder( + token0 == null ? "Unexpected end of command" : "Unexpected token: \"" + token0 + "\""); + + if (!F.isEmpty(expTokens)) { + msg.append(" (expected: "); + + boolean first = true; + + for (String expToken : expTokens) { + if (first) + first = false; + else + msg.append(", "); + + msg.append("\"" + expToken + "\""); + } + + msg.append(")"); + } + + throw error(token, msg.toString()); + } + + /** + * Private constructor. + */ + private SqlParserUtils() { + // No-op. + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCommand.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCommand.java new file mode 100644 index 0000000..61ff31f --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCommand.java @@ -0,0 +1,43 @@ +/* + * 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.sql.command; + +import org.apache.ignite.internal.sql.SqlLexer; + +/** + * Generic SQL command. + */ +public interface SqlCommand { + /** + * Parse command. + * + * @param lex Lexer. + * @return This instance. + */ + public SqlCommand parse(SqlLexer lex); + + /** + * @return Schema name. + */ + public String schemaName(); + + /** + * @param schemaName Schema name. + */ + public void schemaName(String schemaName); +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCreateIndexCommand.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCreateIndexCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCreateIndexCommand.java new file mode 100644 index 0000000..897aea5 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlCreateIndexCommand.java @@ -0,0 +1,200 @@ +/* + * 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.sql.command; + +import org.apache.ignite.internal.sql.SqlLexer; +import org.apache.ignite.internal.sql.SqlLexerTokenType; +import org.apache.ignite.internal.sql.SqlLexerToken; +import org.apache.ignite.internal.util.tostring.GridToStringExclude; +import org.apache.ignite.internal.util.tostring.GridToStringInclude; +import org.apache.ignite.internal.util.typedef.internal.S; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +import static org.apache.ignite.internal.sql.SqlKeyword.ASC; +import static org.apache.ignite.internal.sql.SqlKeyword.DESC; +import static org.apache.ignite.internal.sql.SqlKeyword.IF; +import static org.apache.ignite.internal.sql.SqlKeyword.ON; +import static org.apache.ignite.internal.sql.SqlParserUtils.error; +import static org.apache.ignite.internal.sql.SqlParserUtils.errorUnexpectedToken; +import static org.apache.ignite.internal.sql.SqlParserUtils.matchesKeyword; +import static org.apache.ignite.internal.sql.SqlParserUtils.parseIdentifier; +import static org.apache.ignite.internal.sql.SqlParserUtils.parseIfNotExists; +import static org.apache.ignite.internal.sql.SqlParserUtils.parseQualifiedIdentifier; +import static org.apache.ignite.internal.sql.SqlParserUtils.skipCommaOrRightParenthesis; +import static org.apache.ignite.internal.sql.SqlParserUtils.skipIfMatchesKeyword; + +/** + * CREATE INDEX command. + */ +public class SqlCreateIndexCommand implements SqlCommand { + /** Schema name. */ + private String schemaName; + + /** Table name. */ + private String tblName; + + /** Index name. */ + private String idxName; + + /** IF NOT EXISTS flag. */ + private boolean ifNotExists; + + /** Spatial index flag. */ + private boolean spatial; + + /** Columns. */ + @GridToStringInclude + private Collection cols; + + /** Column names. */ + @GridToStringExclude + private Set colNames; + + /** {@inheritDoc} */ + @Override public String schemaName() { + return schemaName; + } + + /** {@inheritDoc} */ + @Override public void schemaName(String schemaName) { + this.schemaName = schemaName; + } + + /** + * @return Table name. + */ + public String tableName() { + return tblName; + } + + /** + * @return Index name. + */ + public String indexName() { + return idxName; + } + + /** + * @return IF NOT EXISTS flag. + */ + public boolean ifNotExists() { + return ifNotExists; + } + + /** + * @return Spatial index flag. + */ + public boolean spatial() { + return spatial; + } + + /** + * @param spatial Spatial index flag. + * @return This instance. + */ + public SqlCreateIndexCommand spatial(boolean spatial) { + this.spatial = spatial; + + return this; + } + + /** + * @return Columns. + */ + public Collection columns() { + return cols != null ? cols : Collections.emptySet(); + } + + /** {@inheritDoc} */ + @Override public SqlCommand parse(SqlLexer lex) { + ifNotExists = parseIfNotExists(lex); + + idxName = parseIdentifier(lex, IF); + + skipIfMatchesKeyword(lex, ON); + + SqlQualifiedName tblQName = parseQualifiedIdentifier(lex); + + schemaName = tblQName.schemaName(); + tblName = tblQName.name(); + + parseColumnList(lex); + + return this; + } + + /* + * @param lex Lexer. + */ + private void parseColumnList(SqlLexer lex) { + if (!lex.shift() || lex.tokenType() != SqlLexerTokenType.PARENTHESIS_LEFT) + throw errorUnexpectedToken(lex, "("); + + while (true) { + perseIndexColumn(lex); + + if (skipCommaOrRightParenthesis(lex)) + break; + } + } + + /** + * @param lex Lexer. + */ + private void perseIndexColumn(SqlLexer lex) { + String name = parseIdentifier(lex); + boolean desc = false; + + SqlLexerToken nextToken = lex.lookAhead(); + + if (matchesKeyword(nextToken, ASC) || matchesKeyword(nextToken, DESC)) { + lex.shift(); + + if (matchesKeyword(lex, DESC)) + desc = true; + } + + addColumn(lex, new SqlIndexColumn(name, desc)); + } + + /** + * @param lex Lexer. + * @param col Column. + */ + private void addColumn(SqlLexer lex, SqlIndexColumn col) { + if (cols == null) { + cols = new LinkedList<>(); + colNames = new HashSet<>(); + } + + if (!colNames.add(col.name())) + throw error(lex, "Column already defined: " + col.name()); + + cols.add(col); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SqlCreateIndexCommand.class, this); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlIndexColumn.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlIndexColumn.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlIndexColumn.java new file mode 100644 index 0000000..227c02a --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlIndexColumn.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.sql.command; + +import org.apache.ignite.internal.util.typedef.internal.S; + +/** + * Index column definition. + */ +public class SqlIndexColumn { + /** Column name. */ + private final String name; + + /** Descending flag. */ + private final boolean desc; + + /** + * Constructor. + * + * @param name Column name. + * @param desc Descending flag. + */ + public SqlIndexColumn(String name, boolean desc) { + this.name = name; + this.desc = desc; + } + + /** + * @return Column name. + */ + public String name() { + return name; + } + + /** + * @return Descending flag. + */ + public boolean descending() { + return desc; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SqlIndexColumn.class, this); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlQualifiedName.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlQualifiedName.java b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlQualifiedName.java new file mode 100644 index 0000000..965e0ef --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/sql/command/SqlQualifiedName.java @@ -0,0 +1,70 @@ +/* + * 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.sql.command; + +import org.apache.ignite.internal.util.typedef.internal.S; + +/** + * SQL qualified name. + */ +public class SqlQualifiedName { + /** Schema name. */ + private String schemaName; + + /** Object name. */ + private String name; + + /** + * @return Schema name. + */ + public String schemaName() { + return schemaName; + } + + /** + * @param schemaName Schema name. + * @return This instance. + */ + public SqlQualifiedName schemaName(String schemaName) { + this.schemaName = schemaName; + + return this; + } + + /** + * @return Object name. + */ + public String name() { + return name; + } + + /** + * @param name Object name. + * @return This instance. + */ + public SqlQualifiedName name(String name) { + this.name = name; + + return this; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SqlQualifiedName.class, this); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/core/src/test/java/org/apache/ignite/internal/sql/SqlParserSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/internal/sql/SqlParserSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/sql/SqlParserSelfTest.java new file mode 100644 index 0000000..98a6aae --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/sql/SqlParserSelfTest.java @@ -0,0 +1,198 @@ +/* + * 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.sql; + +import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand; +import org.apache.ignite.internal.sql.command.SqlIndexColumn; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; + +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.Callable; + +/** + * Test for parser. + */ +@SuppressWarnings({"UnusedReturnValue", "ThrowableNotThrown"}) +public class SqlParserSelfTest extends GridCommonAbstractTest { + /** + * Tests for CREATE INDEX command. + * + * @throws Exception If failed. + */ + public void testCreateIndex() throws Exception { + // Base. + parseValidate(null, "CREATE INDEX idx ON tbl(a)", null, "TBL", "IDX", "A", false); + parseValidate(null, "CREATE INDEX idx ON tbl(a ASC)", null, "TBL", "IDX", "A", false); + parseValidate(null, "CREATE INDEX idx ON tbl(a DESC)", null, "TBL", "IDX", "A", true); + + // Case (in)sensitivity. + parseValidate(null, "CREATE INDEX IDX ON TBL(COL)", null, "TBL", "IDX", "COL", false); + parseValidate(null, "CREATE INDEX iDx ON tBl(cOl)", null, "TBL", "IDX", "COL", false); + + parseValidate(null, "CREATE INDEX \"idx\" ON tbl(col)", null, "TBL", "idx", "COL", false); + parseValidate(null, "CREATE INDEX \"iDx\" ON tbl(col)", null, "TBL", "iDx", "COL", false); + + parseValidate(null, "CREATE INDEX idx ON \"tbl\"(col)", null, "tbl", "IDX", "COL", false); + parseValidate(null, "CREATE INDEX idx ON \"tBl\"(col)", null, "tBl", "IDX", "COL", false); + + parseValidate(null, "CREATE INDEX idx ON tbl(\"col\")", null, "TBL", "IDX", "col", false); + parseValidate(null, "CREATE INDEX idx ON tbl(\"cOl\")", null, "TBL", "IDX", "cOl", false); + + parseValidate(null, "CREATE INDEX idx ON tbl(\"cOl\" ASC)", null, "TBL", "IDX", "cOl", false); + parseValidate(null, "CREATE INDEX idx ON tbl(\"cOl\" DESC)", null, "TBL", "IDX", "cOl", true); + + // Columns. + parseValidate(null, "CREATE INDEX idx ON tbl(a, b)", null, "TBL", "IDX", "A", false, "B", false); + + parseValidate(null, "CREATE INDEX idx ON tbl(a ASC, b)", null, "TBL", "IDX", "A", false, "B", false); + parseValidate(null, "CREATE INDEX idx ON tbl(a, b ASC)", null, "TBL", "IDX", "A", false, "B", false); + parseValidate(null, "CREATE INDEX idx ON tbl(a ASC, b ASC)", null, "TBL", "IDX", "A", false, "B", false); + + parseValidate(null, "CREATE INDEX idx ON tbl(a DESC, b)", null, "TBL", "IDX", "A", true, "B", false); + parseValidate(null, "CREATE INDEX idx ON tbl(a, b DESC)", null, "TBL", "IDX", "A", false, "B", true); + parseValidate(null, "CREATE INDEX idx ON tbl(a DESC, b DESC)", null, "TBL", "IDX", "A", true, "B", true); + + parseValidate(null, "CREATE INDEX idx ON tbl(a ASC, b DESC)", null, "TBL", "IDX", "A", false, "B", true); + parseValidate(null, "CREATE INDEX idx ON tbl(a DESC, b ASC)", null, "TBL", "IDX", "A", true, "B", false); + + parseValidate(null, "CREATE INDEX idx ON tbl(a, b, c)", null, "TBL", "IDX", "A", false, "B", false, "C", false); + parseValidate(null, "CREATE INDEX idx ON tbl(a DESC, b, c)", null, "TBL", "IDX", "A", true, "B", false, "C", false); + parseValidate(null, "CREATE INDEX idx ON tbl(a, b DESC, c)", null, "TBL", "IDX", "A", false, "B", true, "C", false); + parseValidate(null, "CREATE INDEX idx ON tbl(a, b, c DESC)", null, "TBL", "IDX", "A", false, "B", false, "C", true); + + // Negative cases. + parseError(null, "CREATE INDEX idx ON tbl()", "Unexpected token"); + parseError(null, "CREATE INDEX idx ON tbl(a, a)", "Column already defined: A"); + parseError(null, "CREATE INDEX idx ON tbl(a, b, a)", "Column already defined: A"); + parseError(null, "CREATE INDEX idx ON tbl(b, a, a)", "Column already defined: A"); + + // Tests with schema. + parseValidate(null, "CREATE INDEX idx ON schema.tbl(a)", "SCHEMA", "TBL", "IDX", "A", false); + parseValidate(null, "CREATE INDEX idx ON \"schema\".tbl(a)", "schema", "TBL", "IDX", "A", false); + parseValidate(null, "CREATE INDEX idx ON \"sChema\".tbl(a)", "sChema", "TBL", "IDX", "A", false); + + parseValidate("SCHEMA", "CREATE INDEX idx ON tbl(a)", "SCHEMA", "TBL", "IDX", "A", false); + parseValidate("schema", "CREATE INDEX idx ON tbl(a)", "schema", "TBL", "IDX", "A", false); + parseValidate("sChema", "CREATE INDEX idx ON tbl(a)", "sChema", "TBL", "IDX", "A", false); + + // NOT EXISTS + SqlCreateIndexCommand cmd; + + cmd = parseValidate(null, "CREATE INDEX idx ON schema.tbl(a)", "SCHEMA", "TBL", "IDX", "A", false); + assertFalse(cmd.ifNotExists()); + + cmd = parseValidate(null, "CREATE INDEX IF NOT EXISTS idx ON schema.tbl(a)", "SCHEMA", "TBL", "IDX", "A", false); + assertTrue(cmd.ifNotExists()); + + parseError(null, "CREATE INDEX IF idx ON tbl(a)", "Unexpected token: \"IDX\""); + parseError(null, "CREATE INDEX IF NOT idx ON tbl(a)", "Unexpected token: \"IDX\""); + parseError(null, "CREATE INDEX IF EXISTS idx ON tbl(a)", "Unexpected token: \"EXISTS\""); + parseError(null, "CREATE INDEX NOT EXISTS idx ON tbl(a)", "Unexpected token: \"NOT\""); + + // SPATIAL + cmd = parseValidate(null, "CREATE INDEX idx ON schema.tbl(a)", "SCHEMA", "TBL", "IDX", "A", false); + assertFalse(cmd.spatial()); + + cmd = parseValidate(null, "CREATE SPATIAL INDEX idx ON schema.tbl(a)", "SCHEMA", "TBL", "IDX", "A", false); + assertTrue(cmd.spatial()); + + // UNIQUE + parseError(null, "CREATE UNIQUE INDEX idx ON tbl(a)", "Unsupported keyword: \"UNIQUE\""); + + // HASH + parseError(null, "CREATE HASH INDEX idx ON tbl(a)", "Unsupported keyword: \"HASH\""); + + // PRIMARY KEY + parseError(null, "CREATE PRIMARY KEY INDEX idx ON tbl(a)", "Unsupported keyword: \"PRIMARY\""); + } + + /** + * Make sure that parse error occurs. + * + * @param schema Schema. + * @param sql SQL. + * @param msg Expected error message. + */ + private static void parseError(final String schema, final String sql, String msg) { + GridTestUtils.assertThrows(null, new Callable() { + @Override public Void call() throws Exception { + new SqlParser(schema, sql).nextCommand(); + + return null; + } + }, SqlParseException.class, msg); + } + + /** + * Parse and validate SQL script. + * + * @param schema Schema. + * @param sql SQL. + * @param expSchemaName Expected schema name. + * @param expTblName Expected table name. + * @param expIdxName Expected index name. + * @param expColDefs Expected column definitions. + * @return Command. + */ + private static SqlCreateIndexCommand parseValidate(String schema, String sql, String expSchemaName, + String expTblName, String expIdxName, Object... expColDefs) { + SqlCreateIndexCommand cmd = (SqlCreateIndexCommand)new SqlParser(schema, sql).nextCommand(); + + validate(cmd, expSchemaName, expTblName, expIdxName, expColDefs); + + return cmd; + } + + /** + * Validate create index command. + * + * @param cmd Command. + * @param expSchemaName Expected schema name. + * @param expTblName Expected table name. + * @param expIdxName Expected index name. + * @param expColDefs Expected column definitions. + */ + private static void validate(SqlCreateIndexCommand cmd, String expSchemaName, String expTblName, String expIdxName, + Object... expColDefs) { + assertEquals(expSchemaName, cmd.schemaName()); + assertEquals(expTblName, cmd.tableName()); + assertEquals(expIdxName, cmd.indexName()); + + if (F.isEmpty(expColDefs) || expColDefs.length % 2 == 1) + throw new IllegalArgumentException("Column definitions must be even."); + + Collection cols = cmd.columns(); + + assertEquals(expColDefs.length / 2, cols.size()); + + Iterator colIter = cols.iterator(); + + for (int i = 0; i < expColDefs.length;) { + SqlIndexColumn col = colIter.next(); + + String expColName = (String)expColDefs[i++]; + Boolean expDesc = (Boolean) expColDefs[i++]; + + assertEquals(expColName, col.name()); + assertEquals(expDesc, (Boolean)col.descending()); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java index 31902ac..884752d 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/IgniteH2Indexing.java @@ -115,6 +115,9 @@ import org.apache.ignite.internal.processors.query.h2.twostep.MapQueryLazyWorker import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitor; import org.apache.ignite.internal.processors.query.schema.SchemaIndexCacheVisitorClosure; import org.apache.ignite.internal.processors.timeout.GridTimeoutProcessor; +import org.apache.ignite.internal.sql.SqlParser; +import org.apache.ignite.internal.sql.command.SqlCommand; +import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand; import org.apache.ignite.internal.util.GridBoundedConcurrentLinkedHashMap; import org.apache.ignite.internal.util.GridEmptyCloseableIterator; import org.apache.ignite.internal.util.GridSpinBusyLock; @@ -1321,9 +1324,65 @@ public class IgniteH2Indexing implements GridQueryIndexing { }; } + /** + * Try executing query using native facilities. + * + * @param schemaName Schema name. + * @param qry Query. + * @return Result or {@code null} if cannot parse/process this query. + */ + private List>> tryQueryDistributedSqlFieldsNative(String schemaName, SqlFieldsQuery qry) { + // Heuristic check for fast return. + if (!qry.getSql().toUpperCase().contains("INDEX")) + return null; + + // Parse. + SqlCommand cmd; + + try { + SqlParser parser = new SqlParser(schemaName, qry.getSql()); + + cmd = parser.nextCommand(); + + // No support for multiple commands for now. + if (parser.nextCommand() != null) + return null; + + // Only CREATE INDEX is supported for now. + if (!(cmd instanceof SqlCreateIndexCommand)) + return null; + } + catch (Exception e) { + // Cannot parse, return. + if (log.isDebugEnabled()) + log.debug("Failed to parse SQL with native parser [qry=" + qry.getSql() + ", err=" + e + ']'); + + return null; + } + + // Execute. + try { + List>> ress = new ArrayList<>(1); + + FieldsQueryCursor> res = ddlProc.runDdlStatement(qry.getSql(), cmd); + + ress.add(res); + + return ress; + } + catch (IgniteCheckedException e) { + throw new IgniteSQLException("Failed to execute DDL statement [stmt=" + qry.getSql() + ']', e); + } + } + /** {@inheritDoc} */ @Override public List>> queryDistributedSqlFields(String schemaName, SqlFieldsQuery qry, boolean keepBinary, GridQueryCancel cancel, @Nullable Integer mainCacheId, boolean failOnMultipleStmts) { + List>> res = tryQueryDistributedSqlFieldsNative(schemaName, qry); + + if (res != null) + return res; + Connection c = connectionForSchema(schemaName); final boolean enforceJoinOrder = qry.isEnforceJoinOrder(); @@ -1336,6 +1395,7 @@ public class IgniteH2Indexing implements GridQueryIndexing { H2TwoStepCachedQueryKey cachedQryKey = new H2TwoStepCachedQueryKey(schemaName, sqlQry, grpByCollocated, distributedJoins, enforceJoinOrder, qry.isLocal()); + H2TwoStepCachedQuery cachedQry = twoStepCache.get(cachedQryKey); if (cachedQry != null) { @@ -1345,14 +1405,12 @@ public class IgniteH2Indexing implements GridQueryIndexing { List meta = cachedQry.meta(); - List>> res = Collections.singletonList(executeTwoStepsQuery(schemaName, qry.getPageSize(), qry.getPartitions(), + return Collections.singletonList(executeTwoStepsQuery(schemaName, qry.getPageSize(), qry.getPartitions(), qry.getArgs(), keepBinary, qry.isLazy(), qry.getTimeout(), cancel, sqlQry, enforceJoinOrder, twoStepQry, meta)); - - return res; } - List>> res = new ArrayList<>(1); + res = new ArrayList<>(1); Object[] argsOrig = qry.getArgs(); int firstArg = 0; http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java index d29a063..fd425c2 100644 --- a/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java +++ b/modules/indexing/src/main/java/org/apache/ignite/internal/processors/query/h2/ddl/DdlStatementsProcessor.java @@ -27,6 +27,7 @@ import java.util.Set; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.cache.QueryEntity; import org.apache.ignite.cache.QueryIndex; +import org.apache.ignite.cache.QueryIndexType; import org.apache.ignite.cache.query.FieldsQueryCursor; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.internal.GridKernalContext; @@ -50,6 +51,9 @@ import org.apache.ignite.internal.processors.query.h2.sql.GridSqlDropTable; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlQueryParser; import org.apache.ignite.internal.processors.query.h2.sql.GridSqlStatement; import org.apache.ignite.internal.processors.query.schema.SchemaOperationException; +import org.apache.ignite.internal.sql.command.SqlCommand; +import org.apache.ignite.internal.sql.command.SqlCreateIndexCommand; +import org.apache.ignite.internal.sql.command.SqlIndexColumn; import org.apache.ignite.internal.util.future.GridFinishedFuture; import org.apache.ignite.internal.util.typedef.F; import org.h2.command.Prepared; @@ -87,6 +91,79 @@ public class DdlStatementsProcessor { } /** + * Run DDL statement. + * + * @param sql Original SQL. + * @param cmd Command. + * @return Result. + * @throws IgniteCheckedException On error. + */ + @SuppressWarnings("unchecked") + public FieldsQueryCursor> runDdlStatement(String sql, SqlCommand cmd) throws IgniteCheckedException{ + IgniteInternalFuture fut; + + try { + if (cmd instanceof SqlCreateIndexCommand) { + SqlCreateIndexCommand cmd0 = (SqlCreateIndexCommand)cmd; + + GridH2Table tbl = idx.dataTable(cmd0.schemaName(), cmd0.tableName()); + + if (tbl == null) + throw new SchemaOperationException(SchemaOperationException.CODE_TABLE_NOT_FOUND, cmd0.tableName()); + + assert tbl.rowDescriptor() != null; + + QueryIndex newIdx = new QueryIndex(); + + newIdx.setName(cmd0.indexName()); + + newIdx.setIndexType(cmd0.spatial() ? QueryIndexType.GEOSPATIAL : QueryIndexType.SORTED); + + LinkedHashMap flds = new LinkedHashMap<>(); + + // Let's replace H2's table and property names by those operated by GridQueryProcessor. + GridQueryTypeDescriptor typeDesc = tbl.rowDescriptor().type(); + + for (SqlIndexColumn col : cmd0.columns()) { + GridQueryProperty prop = typeDesc.property(col.name()); + + if (prop == null) + throw new SchemaOperationException(SchemaOperationException.CODE_COLUMN_NOT_FOUND, col.name()); + + flds.put(prop.name(), !col.descending()); + } + + newIdx.setFields(flds); + + fut = ctx.query().dynamicIndexCreate(tbl.cacheName(), cmd.schemaName(), typeDesc.tableName(), + newIdx, cmd0.ifNotExists()); + } + else + throw new IgniteSQLException("Unsupported DDL operation: " + sql, + IgniteQueryErrorCode.UNSUPPORTED_OPERATION); + + if (fut != null) + fut.get(); + + QueryCursorImpl> resCur = (QueryCursorImpl>)new QueryCursorImpl(Collections.singletonList + (Collections.singletonList(0L)), null, false); + + resCur.fieldsMeta(UPDATE_RESULT_META); + + return resCur; + } + catch (SchemaOperationException e) { + throw convert(e); + } + catch (IgniteSQLException e) { + throw e; + } + catch (Exception e) { + throw new IgniteSQLException("Unexpected DDL operation failure: " + e.getMessage(), e); + } + } + + /** * Execute DDL statement. * * @param sql SQL. @@ -97,7 +174,6 @@ public class DdlStatementsProcessor { @SuppressWarnings({"unchecked", "ThrowableResultOfMethodCallIgnored"}) public FieldsQueryCursor> runDdlStatement(String sql, Prepared prepared) throws IgniteCheckedException { - IgniteInternalFuture fut = null; try { @@ -402,6 +478,8 @@ public class DdlStatementsProcessor { } } + assert valCol != null; + valTypeName = DataType.getTypeClassName(valCol.column().getType()); res.setValueFieldName(valCol.columnName()); http://git-wip-us.apache.org/repos/asf/ignite/blob/145c59dd/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java index 0b1a753..5339865 100644 --- a/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java +++ b/modules/indexing/src/test/java/org/apache/ignite/testsuites/IgniteCacheQuerySelfTestSuite.java @@ -154,6 +154,7 @@ import org.apache.ignite.internal.processors.query.h2.sql.GridQueryParsingTest; import org.apache.ignite.internal.processors.query.h2.sql.H2CompareBigQueryDistributedJoinsTest; import org.apache.ignite.internal.processors.query.h2.sql.H2CompareBigQueryTest; import org.apache.ignite.internal.processors.sql.SqlConnectorConfigurationValidationSelfTest; +import org.apache.ignite.internal.sql.SqlParserSelfTest; import org.apache.ignite.spi.communication.tcp.GridOrderedMessageCancelSelfTest; import org.apache.ignite.testframework.IgniteTestSuite; @@ -168,6 +169,8 @@ public class IgniteCacheQuerySelfTestSuite extends TestSuite { public static TestSuite suite() throws Exception { IgniteTestSuite suite = new IgniteTestSuite("Ignite Cache Queries Test Suite"); + suite.addTestSuite(SqlParserSelfTest.class); + suite.addTestSuite(SqlConnectorConfigurationValidationSelfTest.class); suite.addTestSuite(ClientConnectorConfigurationValidationSelfTest.class);