You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vo...@apache.org on 2017/11/09 08:01:17 UTC
ignite git commit: IGNITE-6480: SQL: implemented base parser/lexer
and CREATE INDEX command support. This closes #3001.
Repository: ignite
Updated Branches:
refs/heads/master ca6a00989 -> 145c59dd7
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/master
Commit: 145c59dd79fd796d6f5590d3e1f822d6a305db41
Parents: ca6a009
Author: devozerov <vo...@gridgain.com>
Authored: Thu Nov 9 11:01:05 2017 +0300
Committer: devozerov <vo...@gridgain.com>
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<String> 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<Character, SqlLexerTokenType> 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<SqlIndexColumn> cols;
+
+ /** Column names. */
+ @GridToStringExclude
+ private Set<String> 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<SqlIndexColumn> columns() {
+ return cols != null ? cols : Collections.<SqlIndexColumn>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<Void>() {
+ @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<SqlIndexColumn> cols = cmd.columns();
+
+ assertEquals(expColDefs.length / 2, cols.size());
+
+ Iterator<SqlIndexColumn> 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<FieldsQueryCursor<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<FieldsQueryCursor<List<?>>> ress = new ArrayList<>(1);
+
+ FieldsQueryCursor<List<?>> 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<FieldsQueryCursor<List<?>>> queryDistributedSqlFields(String schemaName, SqlFieldsQuery qry,
boolean keepBinary, GridQueryCancel cancel, @Nullable Integer mainCacheId, boolean failOnMultipleStmts) {
+ List<FieldsQueryCursor<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<GridQueryFieldMetadata> meta = cachedQry.meta();
- List<FieldsQueryCursor<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<FieldsQueryCursor<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<List<?>> 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<String, Boolean> 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<List<?>> resCur = (QueryCursorImpl<List<?>>)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<List<?>> 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);