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 <voze...@gridgain.com>
Authored: Thu Nov 9 11:01:05 2017 +0300
Committer: devozerov <voze...@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);
 

Reply via email to