Merge branch 'master' of https://git-wip-us.apache.org/repos/asf/tajo into index_support

Conflicts:
	CHANGES
diff --git a/CHANGES b/CHANGES
index 420aa4b..2c32817 100644
--- a/CHANGES
+++ b/CHANGES
@@ -191,6 +191,13 @@
 
   BUG FIXES
 
+    TAJO-1608: Fix test failure in index_support branch. (jihoon)
+
+    TAJO-1594: Catalog schema is invalid for some databases. (jihoon)
+
+    TAJO-1657: Tajo Rest API /database/{database-name]/tables should return table 
+    names only without invalid external table info. 
+
     TAJO-1597: Problem of ignoring theta join condition. (jihoon)
 
     TAJO-1697: RCFile progress causes NPE occasionally. (jinho)
@@ -404,8 +411,12 @@
 
   SUB TASKS
 
+<<<<<<< HEAD
+    TAJO-1302: Support index metadata backup and restore. (jihoon)
+=======
     TAJO-1484 Apply on ColPartitionStoreExec. (Contributed by Navis, 
     committed by hyunsik)
+>>>>>>> bcec5eb7e247dc983184b37a3dbb4143afd19ac8
 
     TAJO-1464: Add ORCFileScanner to read ORCFile table. (Contributed by 
     Jongyoung Park, Committed by jihoon)
diff --git a/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateDatabase.java b/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateDatabase.java
index 1144b6e..6e74d3d 100644
--- a/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateDatabase.java
+++ b/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateDatabase.java
@@ -32,7 +32,7 @@
   private String tablespaceName;
   @Expose @SerializedName("IfNotExists")
   private boolean ifNotExists;
-  @Expose @SerializedName("DatabaseProperties")
+  @Expose @SerializedName("Properties")
   private Map<String, String> params;
 
   public CreateDatabase(final String databaseName, final String tablespaceName, final boolean ifNotExists) {
diff --git a/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateIndex.java b/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateIndex.java
new file mode 100644
index 0000000..a9f734d
--- /dev/null
+++ b/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateIndex.java
@@ -0,0 +1,146 @@
+/*
+ * 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.tajo.algebra;
+
+import com.google.common.base.Objects;
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+import org.apache.tajo.algebra.Sort.SortSpec;
+import org.apache.tajo.util.TUtil;
+
+import java.util.Map;
+
+public class CreateIndex extends UnaryOperator {
+  @Expose @SerializedName("IsUnique")
+  private boolean unique = false;
+  @Expose @SerializedName("IndexName")
+  private String indexName;
+  @Expose @SerializedName("SortSpecs")
+  private SortSpec[] sortSpecs;
+  @Expose @SerializedName("Properties")
+  private Map<String, String> params;
+  @Expose @SerializedName("IndexMethodSpec")
+  private IndexMethodSpec methodSpec;
+  private boolean external = false;
+  @Expose @SerializedName("IndexPath")
+  private String indexPath;
+
+  public CreateIndex(final String indexName, final SortSpec[] sortSpecs) {
+    super(OpType.CreateIndex);
+    this.indexName = indexName;
+    this.sortSpecs = sortSpecs;
+    this.methodSpec = new IndexMethodSpec("TWO_LEVEL_BIN_TREE");
+  }
+
+  public void setUnique(boolean unique) {
+    this.unique = unique;
+  }
+
+  public boolean isUnique() {
+    return this.unique;
+  }
+
+  public void setIndexName(String indexName) {
+    this.indexName = indexName;
+  }
+
+  public String getIndexName() {
+    return indexName;
+  }
+
+  public void setSortSpecs(SortSpec[] sortSpecs) {
+    this.sortSpecs = sortSpecs;
+  }
+
+  public SortSpec[] getSortSpecs() {
+    return sortSpecs;
+  }
+
+  public void setParams(Map<String, String> params) {
+    this.params = params;
+  }
+
+  public Map<String, String> getParams() {
+    return this.params;
+  }
+
+  public void setMethodSpec(IndexMethodSpec methodSpec) {
+    this.methodSpec = methodSpec;
+  }
+
+  public IndexMethodSpec getMethodSpec() {
+    return this.methodSpec;
+  }
+
+  public void setIndexPath(String indexPath) {
+    this.external = true;
+    this.indexPath = indexPath;
+  }
+
+  public boolean isExternal() {
+    return this.external;
+  }
+
+  public String getIndexPath() {
+    return this.indexPath;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(unique, indexName,  sortSpecs, params, methodSpec, external);
+  }
+
+  @Override
+  boolean equalsTo(Expr expr) {
+    CreateIndex other = (CreateIndex) expr;
+    return this.unique == other.unique &&
+        this.indexName.equals(other.indexName) &&
+        TUtil.checkEquals(this.sortSpecs, other.sortSpecs) &&
+        TUtil.checkEquals(this.params, other.params) &&
+        this.methodSpec.equals(other.methodSpec) &&
+        this.external == other.external;
+  }
+
+  public static class IndexMethodSpec {
+    @Expose @SerializedName("IndexMethodName")
+    private String name;
+
+    public IndexMethodSpec(final String name) {
+      this.name = name;
+    }
+
+    public String getName() {
+      return this.name;
+    }
+
+    @Override
+    public int hashCode() {
+      return name.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof IndexMethodSpec) {
+        IndexMethodSpec other = (IndexMethodSpec) o;
+        return this.name.equals(other.name);
+      }
+      return false;
+    }
+  }
+}
diff --git a/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateTable.java b/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateTable.java
index 5d1599d..ef10c4c 100644
--- a/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateTable.java
+++ b/tajo-algebra/src/main/java/org/apache/tajo/algebra/CreateTable.java
@@ -46,7 +46,7 @@
   private String location;
   @Expose @SerializedName("SubPlan")
   private Expr subquery;
-  @Expose @SerializedName("TableProperties")
+  @Expose @SerializedName("Properties")
   private Map<String, String> params;
   @Expose @SerializedName("PartitionMethodDesc")
   private PartitionMethodDescExpr partition;
diff --git a/tajo-algebra/src/main/java/org/apache/tajo/algebra/DropIndex.java b/tajo-algebra/src/main/java/org/apache/tajo/algebra/DropIndex.java
new file mode 100644
index 0000000..5a75e78
--- /dev/null
+++ b/tajo-algebra/src/main/java/org/apache/tajo/algebra/DropIndex.java
@@ -0,0 +1,54 @@
+/*
+ * 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.tajo.algebra;
+
+import com.google.gson.annotations.Expose;
+import com.google.gson.annotations.SerializedName;
+
+public class DropIndex extends Expr {
+  @Expose @SerializedName("IndexName")
+  private String indexName;
+
+  public DropIndex(final String indexName) {
+    super(OpType.DropIndex);
+    this.indexName = indexName;
+  }
+
+  @Override
+  public int hashCode() {
+    return indexName.hashCode();
+  }
+
+  @Override
+  boolean equalsTo(Expr expr) {
+    DropIndex other = (DropIndex) expr;
+    return this.indexName.equals(other.indexName);
+  }
+
+  @Override
+  public Object clone() throws CloneNotSupportedException {
+    DropIndex clone = (DropIndex) super.clone();
+    clone.indexName = indexName;
+    return clone;
+  }
+
+  public String getIndexName() {
+    return indexName;
+  }
+}
diff --git a/tajo-algebra/src/main/java/org/apache/tajo/algebra/Insert.java b/tajo-algebra/src/main/java/org/apache/tajo/algebra/Insert.java
index 0826d90..f0cd5f9 100644
--- a/tajo-algebra/src/main/java/org/apache/tajo/algebra/Insert.java
+++ b/tajo-algebra/src/main/java/org/apache/tajo/algebra/Insert.java
@@ -39,7 +39,7 @@
   private String location;
   @Expose @SerializedName("SubPlan")
   private Expr subquery;
-  @Expose @SerializedName("InsertParams")
+  @Expose @SerializedName("Properties")
   private Map<String, String> params;
 
   public Insert() {
diff --git a/tajo-algebra/src/main/java/org/apache/tajo/algebra/OpType.java b/tajo-algebra/src/main/java/org/apache/tajo/algebra/OpType.java
index 3e7d277..47fea64 100644
--- a/tajo-algebra/src/main/java/org/apache/tajo/algebra/OpType.java
+++ b/tajo-algebra/src/main/java/org/apache/tajo/algebra/OpType.java
@@ -53,6 +53,8 @@
   DropTable(DropTable.class),
   AlterTablespace(AlterTablespace.class),
   AlterTable(AlterTable.class),
+  CreateIndex(CreateIndex.class),
+  DropIndex(DropIndex.class),
   TruncateTable(TruncateTable.class),
 
   // Insert or Update
diff --git a/tajo-catalog/tajo-catalog-client/src/main/java/org/apache/tajo/catalog/AbstractCatalogClient.java b/tajo-catalog/tajo-catalog-client/src/main/java/org/apache/tajo/catalog/AbstractCatalogClient.java
index f7f7785..402df0f 100644
--- a/tajo-catalog/tajo-catalog-client/src/main/java/org/apache/tajo/catalog/AbstractCatalogClient.java
+++ b/tajo-catalog/tajo-catalog-client/src/main/java/org/apache/tajo/catalog/AbstractCatalogClient.java
@@ -34,8 +34,10 @@
 import org.apache.tajo.error.Errors.ResultCode;
 import org.apache.tajo.exception.ReturnStateUtil;
 import org.apache.tajo.rpc.protocolrecords.PrimitiveProtos.NullProto;
+import org.apache.tajo.rpc.protocolrecords.PrimitiveProtos.ReturnState;
 import org.apache.tajo.rpc.protocolrecords.PrimitiveProtos.StringListResponse;
 import org.apache.tajo.util.ProtoUtil;
+import org.apache.tajo.util.TUtil;
 
 import java.io.Closeable;
 import java.util.ArrayList;
@@ -315,6 +317,18 @@
   }
 
   @Override
+  public List<IndexDescProto> getAllIndexes() {
+    try {
+      CatalogProtocolService.BlockingInterface stub = getStub();
+      IndexListResponse response = stub.getAllIndexes(null, ProtoUtil.NULL_PROTO);
+      return response.getIndexDescList();
+    } catch (ServiceException e) {
+      LOG.error(e.getMessage(), e);
+      return null;
+    }
+  }
+
+  @Override
   public final PartitionMethodDesc getPartitionMethod(final String databaseName, final String tableName) {
 
     try {
@@ -496,7 +510,13 @@
     try {
       final BlockingInterface stub = getStub();
 
-      return isSuccess(stub.createIndex(null, index.getProto()));
+      final ReturnState state = stub.createIndex(null, index.getProto());
+      if (isSuccess(state)) {
+        return true;
+      } else {
+        // TODO
+        return false;
+      }
 
     } catch (ServiceException e) {
       throw new RuntimeException(e);
@@ -521,18 +541,35 @@
   }
 
   @Override
-  public boolean existIndexByColumn(final String databaseName, final String tableName, final String columnName) {
+  public boolean existIndexByColumns(final String databaseName, final String tableName, final Column [] columns) {
+    return existIndexByColumnNames(databaseName, tableName, extractColumnNames(columns));
+  }
+
+  @Override
+  public boolean existIndexByColumnNames(final String databaseName, final String tableName, final String [] columnNames) {
     try {
 
-      final GetIndexByColumnRequest request = GetIndexByColumnRequest.newBuilder()
-          .setTableIdentifier(buildTableIdentifier(databaseName, tableName))
-          .setColumnName(columnName)
-          .build();
+      GetIndexByColumnNamesRequest.Builder builder = GetIndexByColumnNamesRequest.newBuilder();
+      builder.setTableIdentifier(CatalogUtil.buildTableIdentifier(databaseName, tableName));
+      for (String colunName : columnNames) {
+        builder.addColumnNames(colunName);
+      }
 
       final BlockingInterface stub = getStub();
 
-      return isSuccess(stub.existIndexByColumn(null, request));
+      return isSuccess(stub.existIndexByColumnNames(null, builder.build()));
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+  }
 
+  @Override
+  public boolean existIndexesByTable(final String databaseName, final String tableName) {
+    try {
+      final BlockingInterface stub = getStub();
+
+      return isSuccess(
+          stub.existIndexesByTable(null, CatalogUtil.buildTableIdentifier(databaseName, tableName)));
     } catch (ServiceException e) {
       throw new RuntimeException(e);
     }
@@ -548,33 +585,67 @@
           .build();
 
       final BlockingInterface stub = getStub();
-      final GetIndexResponse response = stub.getIndexByName(null, request);
+      final IndexResponse response = stub.getIndexByName(null, request);
       ensureOk(response.getState());
 
-      return new IndexDesc(response.getIndex());
+      return new IndexDesc(response.getIndexDesc());
 
     } catch (ServiceException e) {
       throw new RuntimeException(e);
     }
   }
 
-  @Override
-  public final IndexDesc getIndexByColumn(final String databaseName,
-                                          final String tableName,
-                                          final String columnName) {
-    try {
+  private static String[] extractColumnNames(Column[] columns) {
+    String[] columnNames = new String [columns.length];
+    for (int i = 0; i < columnNames.length; i++) {
+      columnNames[i] = columns[i].getSimpleName();
+    }
+    return columnNames;
+  }
 
-      final GetIndexByColumnRequest request = GetIndexByColumnRequest.newBuilder()
-          .setTableIdentifier(buildTableIdentifier(databaseName, tableName))
-          .setColumnName(columnName)
-          .build();
+  @Override
+  public final IndexDesc getIndexByColumns(final String databaseName,
+                                               final String tableName,
+                                               final Column [] columns) {
+    return getIndexByColumnNames(databaseName, tableName, extractColumnNames(columns));
+  }
+
+  @Override
+  public final IndexDesc getIndexByColumnNames(final String databaseName,
+                                           final String tableName,
+                                           final String [] columnNames) {
+    try {
+      GetIndexByColumnNamesRequest.Builder builder = GetIndexByColumnNamesRequest.newBuilder();
+      builder.setTableIdentifier(CatalogUtil.buildTableIdentifier(databaseName, tableName));
+      for (String columnName : columnNames) {
+        builder.addColumnNames(columnName);
+      }
 
       final BlockingInterface stub = getStub();
-      final GetIndexResponse response = stub.getIndexByColumn(null, request);
-      ensureOk(response.getState());;
+      final IndexResponse response = stub.getIndexByColumnNames(null, builder.build());
+      ensureOk(response.getState());
 
-      return new IndexDesc(response.getIndex());
+      return new IndexDesc(response.getIndexDesc());
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+  }
 
+  @Override
+  public final Collection<IndexDesc> getAllIndexesByTable(final String databaseName,
+                                                          final String tableName) {
+    try {
+      TableIdentifierProto proto = CatalogUtil.buildTableIdentifier(databaseName, tableName);
+
+      final BlockingInterface stub = getStub();
+      final IndexListResponse response = stub.getAllIndexesByTable(null, proto);
+      ensureOk(response.getState());
+
+      List<IndexDesc> indexDescs = TUtil.newList();
+      for (IndexDescProto descProto : response.getIndexDescList()) {
+        indexDescs.add(new IndexDesc(descProto));
+      }
+      return indexDescs;
     } catch (ServiceException e) {
       throw new RuntimeException(e);
     }
@@ -598,21 +669,6 @@
   }
 
   @Override
-  public List<IndexProto> getAllIndexes() {
-
-    try {
-      final BlockingInterface stub = getStub();
-      final GetIndexesResponse response = stub.getAllIndexes(null, ProtoUtil.NULL_PROTO);
-      ensureOk(response.getState());
-
-      return response.getIndexList();
-
-    } catch (ServiceException e) {
-      throw new RuntimeException(e);
-    }
-  }
-
-  @Override
   public final boolean createFunction(final FunctionDesc funcDesc) {
 
     try {
diff --git a/tajo-catalog/tajo-catalog-client/src/main/proto/CatalogProtocol.proto b/tajo-catalog/tajo-catalog-client/src/main/proto/CatalogProtocol.proto
index 8d0eef6..ee74aa0 100644
--- a/tajo-catalog/tajo-catalog-client/src/main/proto/CatalogProtocol.proto
+++ b/tajo-catalog/tajo-catalog-client/src/main/proto/CatalogProtocol.proto
@@ -69,14 +69,14 @@
   optional PartitionDescProto partition = 2;
 }
 
-message GetIndexResponse {
+message GetIndexeDescResponse {
   required ReturnState state = 1;
   optional IndexDescProto index = 2;
 }
 
-message GetIndexesResponse {
-  required ReturnState state = 1;
-  repeated IndexProto index = 2;
+message GetIndexByColumnNamesRequest {
+  required TableIdentifierProto tableIdentifier = 1;
+  repeated string columnNames = 2;
 }
 
 message GetPartitionsResponse {
@@ -128,10 +128,12 @@
   rpc createIndex(IndexDescProto) returns (ReturnState);
   rpc dropIndex(IndexNameProto) returns (ReturnState);
   rpc existIndexByName(IndexNameProto) returns (ReturnState);
-  rpc existIndexByColumn(GetIndexByColumnRequest) returns (ReturnState);
-  rpc getIndexByName(IndexNameProto) returns (GetIndexResponse);
-  rpc getIndexByColumn(GetIndexByColumnRequest) returns (GetIndexResponse);
-  rpc getAllIndexes(NullProto) returns (GetIndexesResponse);
+  rpc existIndexByColumnNames(GetIndexByColumnNamesRequest) returns (ReturnState);
+  rpc existIndexesByTable(TableIdentifierProto) returns (ReturnState);
+  rpc getIndexByName(IndexNameProto) returns (IndexResponse);
+  rpc getIndexByColumnNames(GetIndexByColumnNamesRequest) returns (IndexResponse);
+  rpc getAllIndexesByTable(TableIdentifierProto) returns (IndexListResponse);
+  rpc getAllIndexes(NullProto) returns (IndexListResponse);
 
   rpc createFunction(FunctionDescProto) returns (ReturnState);
   rpc dropFunction(UnregisterFunctionRequest) returns (ReturnState);
diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogConstants.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogConstants.java
index 8265e38..721bcf1 100644
--- a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogConstants.java
+++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogConstants.java
@@ -53,6 +53,7 @@
   public static final String COL_TABLESPACE_PK = "SPACE_ID";
   public static final String COL_DATABASES_PK = "DB_ID";
   public static final String COL_TABLES_PK = "TID";
+  public static final String COL_INDEXES_PK = "INDEX_ID";
   public static final String COL_TABLES_NAME = "TABLE_NAME";
 
   public static final String COL_PARTITIONS_PK = "PARTITION_ID";
diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogService.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogService.java
index 5dc5412..3d56a7a 100644
--- a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogService.java
+++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogService.java
@@ -21,10 +21,10 @@
 import org.apache.tajo.catalog.exception.UndefinedFunctionException;
 import org.apache.tajo.catalog.exception.UndefinedPartitionException;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
+import org.apache.tajo.catalog.proto.CatalogProtos.*;
 import org.apache.tajo.catalog.proto.CatalogProtos;
 import org.apache.tajo.catalog.proto.CatalogProtos.ColumnProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.DatabaseProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.IndexProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableDescriptorProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableOptionProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TablePartitionProto;
@@ -35,12 +35,6 @@
 import java.util.Collection;
 import java.util.List;
 
-import static org.apache.tajo.catalog.proto.CatalogProtos.AlterTablespaceProto;
-import static org.apache.tajo.catalog.proto.CatalogProtos.FunctionType;
-import static org.apache.tajo.catalog.proto.CatalogProtos.TablespaceProto;
-import static org.apache.tajo.catalog.proto.CatalogProtos.UpdateTableStatsProto;
-
-
 public interface CatalogService {
 
   /**
@@ -158,6 +152,8 @@
    */
   List<ColumnProto> getAllColumns();
 
+  List<IndexDescProto> getAllIndexes();
+
   /**
    *
    * @return All FunctionDescs
@@ -199,15 +195,21 @@
 
   boolean existIndexByName(String databaseName, String indexName);
 
-  boolean existIndexByColumn(String databaseName, String tableName, String columnName);
+  boolean existIndexByColumns(String databaseName, String tableName, Column[] columns);
+
+  boolean existIndexByColumnNames(String databaseName, String tableName, String [] columnNames);
+
+  boolean existIndexesByTable(String databaseName, String tableName);
 
   IndexDesc getIndexByName(String databaseName, String indexName);
 
-  IndexDesc getIndexByColumn(String databaseName, String tableName, String columnName);
+  IndexDesc getIndexByColumns(String databaseName, String tableName, Column [] columns);
+
+  IndexDesc getIndexByColumnNames(String databaseName, String tableName, String [] columnNames);
+
+  Collection<IndexDesc> getAllIndexesByTable(String databaseName, String tableName);
 
   boolean dropIndex(String databaseName, String indexName);
-  
-  List<IndexProto> getAllIndexes();
 
   boolean createFunction(FunctionDesc funcDesc);
 
diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogUtil.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogUtil.java
index 8205e9b..378bbcb 100644
--- a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogUtil.java
+++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/CatalogUtil.java
@@ -29,6 +29,7 @@
 import org.apache.tajo.catalog.proto.CatalogProtos;
 import org.apache.tajo.catalog.proto.CatalogProtos.PartitionKeyProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.SchemaProto;
+import org.apache.tajo.catalog.proto.CatalogProtos.StoreType;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableDescProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableIdentifierProto;
 import org.apache.tajo.common.TajoDataTypes;
@@ -46,12 +47,8 @@
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 
-import static org.apache.tajo.catalog.proto.CatalogProtos.StoreType;
 import static org.apache.tajo.common.TajoDataTypes.Type;
 
 public class CatalogUtil {
@@ -967,4 +964,43 @@
 
     return options;
   }
+
+  /**
+   * Make a unique name by concatenating column names.
+   * The concatenation is performed in sequence of columns' occurrence in the relation schema.
+   *
+   * @param originalSchema original relation schema
+   * @param columnNames column names which will be unified
+   * @return unified name
+   */
+  public static String getUnifiedSimpleColumnName(Schema originalSchema, String[] columnNames) {
+    String[] simpleNames = new String[columnNames.length];
+    for (int i = 0; i < simpleNames.length; i++) {
+      String[] identifiers = columnNames[i].split(CatalogConstants.IDENTIFIER_DELIMITER_REGEXP);
+      simpleNames[i] = identifiers[identifiers.length-1];
+    }
+    Arrays.sort(simpleNames, new ColumnPosComparator(originalSchema));
+    StringBuilder sb = new StringBuilder();
+    for (String colName : simpleNames) {
+      sb.append(colName).append("_");
+    }
+    sb.deleteCharAt(sb.length()-1);
+    return sb.toString();
+  }
+
+  /**
+   * Given column names, compare the position of columns in the relation schema.
+   */
+  public static class ColumnPosComparator implements Comparator<String> {
+
+    private Schema originlSchema;
+    public ColumnPosComparator(Schema originalSchema) {
+      this.originlSchema = originalSchema;
+    }
+
+    @Override
+    public int compare(String o1, String o2) {
+      return originlSchema.getColumnId(o1) - originlSchema.getColumnId(o2);
+    }
+  }
 }
diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/DDLBuilder.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/DDLBuilder.java
index f313aa5..2923654 100644
--- a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/DDLBuilder.java
+++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/DDLBuilder.java
@@ -68,6 +68,28 @@
     return sb.toString();
   }
 
+  public static String buildDDLForIndex(IndexDesc desc) {
+    StringBuilder sb = new StringBuilder();
+
+    sb.append("--\n")
+        .append("-- Name: ").append(CatalogUtil.denormalizeIdentifier(desc.getName())).append("; Type: INDEX;")
+        .append(" Index Method: ").append(desc.getIndexMethod());
+    sb.append("\n--\n");
+    sb.append("CREATE INDEX ").append(CatalogUtil.denormalizeIdentifier(desc.getName()));
+    sb.append(" on ").append(CatalogUtil.denormalizeIdentifier(desc.getTableName())).append(" ( ");
+
+    for (SortSpec sortSpec : desc.getKeySortSpecs()) {
+      sb.append(sortSpec.getSortKey().getQualifiedName()).append(" ");
+      sb.append(sortSpec.isAscending() ? "asc" : "desc").append(" ");
+      sb.append(sortSpec.isNullFirst() ? "null first" : "null last").append(", ");
+    }
+    sb.replace(sb.length()-2, sb.length()-1, " )");
+
+    sb.append(" location '").append(desc.getIndexPath()).append("';");
+
+    return sb.toString();
+  }
+
   public static void buildSchema(StringBuilder sb, Schema schema) {
     boolean first = true;
 
diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/IndexDesc.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/IndexDesc.java
index 151b919..9f64913 100644
--- a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/IndexDesc.java
+++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/IndexDesc.java
@@ -26,66 +26,86 @@
 import org.apache.tajo.catalog.proto.CatalogProtos.IndexMethod;
 import org.apache.tajo.common.ProtoObject;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+
 public class IndexDesc implements ProtoObject<IndexDescProto>, Cloneable {
-  private String indexName;            // required
   private String databaseName;         // required
   private String tableName;            // required
-  private Column column;               // required
-  private IndexMethod indexMethod;     // required
-  private boolean isUnique = false;    // optional [default = false]
-  private boolean isClustered = false; // optional [default = false]
-  private boolean isAscending = false; // optional [default = false]
-  
+  private IndexMeta indexMeta;         // required
+
   public IndexDesc() {
   }
   
-  public IndexDesc(String idxName, String databaseName, String tableName, Column column,
-                   IndexMethod type,  boolean isUnique, boolean isClustered, boolean isAscending) {
+  public IndexDesc(String databaseName, String tableName, String indexName, URI indexPath, SortSpec[] keySortSpecs,
+                   IndexMethod type,  boolean isUnique, boolean isClustered, Schema targetRelationSchema) {
     this();
-    this.indexName = idxName;
-    this.databaseName = databaseName;
-    this.tableName = tableName;
-    this.column = column;
-    this.indexMethod = type;
-    this.isUnique = isUnique;
-    this.isClustered = isClustered;
-    this.isAscending = isAscending;
+    this.set(databaseName, tableName, indexName, indexPath, keySortSpecs, type, isUnique, isClustered,
+        targetRelationSchema);
   }
   
   public IndexDesc(IndexDescProto proto) {
-    this(proto.getIndexName(),
-        proto.getTableIdentifier().getDatabaseName(),
-        proto.getTableIdentifier().getTableName(),
-        new Column(proto.getColumn()),
-        proto.getIndexMethod(), proto.getIsUnique(), proto.getIsClustered(), proto.getIsAscending());
+    this();
+
+    SortSpec[] keySortSpecs = new SortSpec[proto.getKeySortSpecsCount()];
+    for (int i = 0; i < keySortSpecs.length; i++) {
+      keySortSpecs[i] = new SortSpec(proto.getKeySortSpecs(i));
+    }
+
+    try {
+      this.set(proto.getTableIdentifier().getDatabaseName(),
+          proto.getTableIdentifier().getTableName(),
+          proto.getIndexName(), new URI(proto.getIndexPath()),
+          keySortSpecs,
+          proto.getIndexMethod(), proto.getIsUnique(), proto.getIsClustered(),
+          new Schema(proto.getTargetRelationSchema()));
+    } catch (URISyntaxException e) {
+      e.printStackTrace();
+    }
   }
-  
-  public String getIndexName() {
-    return indexName;
+
+  public void set(String databaseName, String tableName, String indexName, URI indexPath, SortSpec[] keySortSpecs,
+                  IndexMethod type,  boolean isUnique, boolean isClustered, Schema targetRelationSchema) {
+    this.databaseName = databaseName;
+    this.tableName = tableName;
+    this.indexMeta = new IndexMeta(indexName, indexPath, keySortSpecs, type, isUnique, isClustered,
+        targetRelationSchema);
   }
-  
+
+  public String getDatabaseName() {
+    return databaseName;
+  }
+
   public String getTableName() {
     return tableName;
   }
+
+  public String getName() {
+    return indexMeta.getIndexName();
+  }
   
-  public Column getColumn() {
-    return column;
+  public URI getIndexPath() {
+    return indexMeta.getIndexPath();
+  }
+
+  public SortSpec[] getKeySortSpecs() {
+    return indexMeta.getKeySortSpecs();
   }
   
   public IndexMethod getIndexMethod() {
-    return this.indexMethod;
+    return indexMeta.getIndexMethod();
   }
   
   public boolean isClustered() {
-    return this.isClustered;
+    return indexMeta.isClustered();
   }
   
   public boolean isUnique() {
-    return this.isUnique;
+    return indexMeta.isUnique();
   }
-  
-  public boolean isAscending() {
-    return this.isAscending;
+
+  public Schema getTargetRelationSchema() {
+    return indexMeta.getTargetRelationSchema();
   }
 
   @Override
@@ -101,12 +121,15 @@
     }
 
     builder.setTableIdentifier(tableIdentifierBuilder.build());
-    builder.setIndexName(this.indexName);
-    builder.setColumn(this.column.getProto());
-    builder.setIndexMethod(indexMethod);
-    builder.setIsUnique(this.isUnique);
-    builder.setIsClustered(this.isClustered);
-    builder.setIsAscending(this.isAscending);
+    builder.setIndexName(indexMeta.getIndexName());
+    builder.setIndexPath(indexMeta.getIndexPath().toString());
+    for (SortSpec colSpec : indexMeta.getKeySortSpecs()) {
+      builder.addKeySortSpecs(colSpec.getProto());
+    }
+    builder.setIndexMethod(indexMeta.getIndexMethod());
+    builder.setIsUnique(indexMeta.isUnique());
+    builder.setIsClustered(indexMeta.isClustered());
+    builder.setTargetRelationSchema(indexMeta.getTargetRelationSchema().getProto());
 
     return builder.build();
   }
@@ -114,32 +137,23 @@
   public boolean equals(Object obj) {
     if (obj instanceof IndexDesc) {
       IndexDesc other = (IndexDesc) obj;
-      return getIndexName().equals(other.getIndexName())
+      return getDatabaseName().equals(other.getDatabaseName())
           && getTableName().equals(other.getTableName())
-          && getColumn().equals(other.getColumn())
-          && getIndexMethod().equals(other.getIndexMethod())
-          && isUnique() == other.isUnique()
-          && isClustered() == other.isClustered()
-          && isAscending() == other.isAscending();
+          && this.indexMeta.equals(other.indexMeta);
     } else {
       return false;
     }
   }
   
   public int hashCode() {
-    return Objects.hashCode(getIndexName(), getTableName(), getColumn(),
-        getIndexMethod(), isUnique(), isClustered(), isAscending());
+    return Objects.hashCode(databaseName, tableName, indexMeta);
   }
 
   public Object clone() throws CloneNotSupportedException {
     IndexDesc desc = (IndexDesc) super.clone();
-    desc.indexName = indexName;
-    desc.tableName = tableName;
-    desc.column = column;
-    desc.indexMethod = indexMethod;
-    desc.isUnique = isUnique;
-    desc.isClustered = isClustered;
-    desc.isAscending = isAscending;
+    desc.databaseName = this.databaseName;
+    desc.tableName = this.tableName;
+    desc.indexMeta = (IndexMeta) this.indexMeta.clone();
     return desc;
   }
   
@@ -147,4 +161,4 @@
     Gson gson = new GsonBuilder().setPrettyPrinting().create();
     return gson.toJson(this);
   }
-}
\ No newline at end of file
+}
diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/IndexMeta.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/IndexMeta.java
new file mode 100644
index 0000000..a911055
--- /dev/null
+++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/IndexMeta.java
@@ -0,0 +1,180 @@
+/**
+ * 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.tajo.catalog;
+
+import com.google.common.base.Objects;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.catalog.proto.CatalogProtos.IndexMethod;
+import org.apache.tajo.util.KeyValueSet;
+import org.apache.tajo.util.TUtil;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * IndexMeta contains meta information of an index.
+ * Meta information is the name, an index method, a path to the stored location, index keys, and so on.
+ */
+public class IndexMeta implements Cloneable {
+  @Expose private String indexName;              // index name
+  @Expose private IndexMethod indexMethod;       // index method
+  @Expose private URI indexPath;                 // path to the location
+  @Expose private SortSpec[] keySortSpecs;       // index keys. This array should always be sorted
+                                                 // according to the position in the targetRelationSchema
+  @Expose private boolean isUnique = false;      // unique key or not
+  @Expose private boolean isClustered = false;   // clustered index or not
+  @Expose private Schema targetRelationSchema;   // schema of the indexed relation
+  @Expose private KeyValueSet options;           // index options. TODO: will be added
+
+  public IndexMeta() {}
+
+  public IndexMeta(String indexName, URI indexPath, SortSpec[] keySortSpecs,
+                   IndexMethod type,  boolean isUnique, boolean isClustered,
+                   Schema targetRelationSchema) {
+    this.indexName = indexName;
+    this.indexPath = indexPath;
+    this.indexMethod = type;
+    this.isUnique = isUnique;
+    this.isClustered = isClustered;
+    this.targetRelationSchema = targetRelationSchema;
+    initKeySortSpecs(targetRelationSchema, keySortSpecs);
+  }
+
+  private void initKeySortSpecs(final Schema targetRelationSchema, final SortSpec[] keySortSpecs) {
+    this.targetRelationSchema = targetRelationSchema;
+    this.keySortSpecs = new SortSpec[keySortSpecs.length];
+    for (int i = 0; i < keySortSpecs.length; i++) {
+      this.keySortSpecs[i] = new SortSpec(keySortSpecs[i].getSortKey(), keySortSpecs[i].isAscending(),
+          keySortSpecs[i].isNullFirst());
+    }
+    Arrays.sort(this.keySortSpecs, new Comparator<SortSpec>() {
+      @Override
+      public int compare(SortSpec o1, SortSpec o2) {
+        return targetRelationSchema.getColumnId(o1.getSortKey().getSimpleName())
+            - targetRelationSchema.getColumnId(o2.getSortKey().getSimpleName());
+      }
+    });
+  }
+
+  public String getIndexName() {
+    return indexName;
+  }
+
+  public void setIndexName(final String indexName) {
+    this.indexName = indexName;
+  }
+
+  public IndexMethod getIndexMethod() {
+    return indexMethod;
+  }
+
+  public void setIndexMethod(final IndexMethod type) {
+    this.indexMethod = type;
+  }
+
+  public URI getIndexPath() {
+    return indexPath;
+  }
+
+  public void setIndexPath(final URI indexPath) {
+    this.indexPath = indexPath;
+  }
+
+  public SortSpec[] getKeySortSpecs() {
+    return keySortSpecs;
+  }
+
+  public void setKeySortSpecs(final Schema targetRelationSchema, final SortSpec[] keySortSpecs) {
+    initKeySortSpecs(targetRelationSchema, keySortSpecs);
+  }
+
+  public boolean isUnique() {
+    return isUnique;
+  }
+
+  public void setUnique(boolean unique) {
+    this.isUnique = unique;
+  }
+
+  public boolean isClustered() {
+    return isClustered;
+  }
+
+  public void setClustered(boolean clustered) {
+    this.isClustered = clustered;
+  }
+
+  public Schema getTargetRelationSchema() {
+    return targetRelationSchema;
+  }
+
+  public KeyValueSet getOptions() {
+    return options;
+  }
+
+  public void setOptions(KeyValueSet options) {
+    this.options = options;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (o instanceof IndexMeta) {
+      IndexMeta other = (IndexMeta) o;
+      return this.indexName.equals(other.indexName)
+          && this.indexPath.equals(other.indexPath)
+          && this.indexMethod.equals(other.indexMethod)
+          && TUtil.checkEquals(this.keySortSpecs, other.keySortSpecs)
+          && this.isUnique == other.isUnique
+          && this.isClustered == other.isClustered
+          && this.targetRelationSchema.equals(other.targetRelationSchema);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(indexName, indexPath, indexMethod, Objects.hashCode(keySortSpecs),
+        isUnique, isClustered, targetRelationSchema);
+  }
+
+  @Override
+  public Object clone() throws CloneNotSupportedException {
+    IndexMeta clone = (IndexMeta) super.clone();
+    clone.indexName = indexName;
+    clone.indexPath = indexPath;
+    clone.indexMethod = indexMethod;
+    clone.keySortSpecs = new SortSpec[keySortSpecs.length];
+    for (int i = 0; i < keySortSpecs.length; i++) {
+      clone.keySortSpecs[i] = new SortSpec(this.keySortSpecs[i].getProto());
+    }
+    clone.isUnique = this.isUnique;
+    clone.isClustered = this.isClustered;
+    clone.targetRelationSchema = this.targetRelationSchema;
+    return clone;
+  }
+
+  @Override
+  public String toString() {
+    Gson gson = new GsonBuilder().setPrettyPrinting().create();
+    return gson.toJson(this);
+  }
+}
diff --git a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/TableDesc.java b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/TableDesc.java
index 4700322..add77f2 100644
--- a/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/TableDesc.java
+++ b/tajo-catalog/tajo-catalog-common/src/main/java/org/apache/tajo/catalog/TableDesc.java
@@ -22,9 +22,6 @@
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
 import com.google.gson.annotations.Expose;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
 import org.apache.tajo.catalog.json.CatalogGsonHelper;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableDescProto;
@@ -37,8 +34,6 @@
 import java.net.URI;
 
 public class TableDesc implements ProtoObject<TableDescProto>, GsonObject, Cloneable {
-  private final Log LOG = LogFactory.getLog(TableDesc.class);
-
 	@Expose protected String tableName;                        // required
   @Expose protected Schema schema;
   @Expose protected TableMeta meta;                          // required
diff --git a/tajo-catalog/tajo-catalog-common/src/main/proto/CatalogProtos.proto b/tajo-catalog/tajo-catalog-common/src/main/proto/CatalogProtos.proto
index f95df0a..86fee86 100644
--- a/tajo-catalog/tajo-catalog-common/src/main/proto/CatalogProtos.proto
+++ b/tajo-catalog/tajo-catalog-common/src/main/proto/CatalogProtos.proto
@@ -119,11 +119,12 @@
 message IndexDescProto {
   required TableIdentifierProto tableIdentifier = 1;
   required string indexName = 2;
-  required ColumnProto column = 3;
-  required IndexMethod indexMethod = 4;
-  optional bool isUnique = 5 [default = false];
-  optional bool isClustered = 6 [default = false];
-  optional bool isAscending = 7 [default = false];
+  required IndexMethod indexMethod = 3;
+  required string indexPath = 4;
+  repeated SortSpecProto key_sort_specs = 5;
+  required SchemaProto targetRelationSchema = 6;
+  optional bool isUnique = 7 [default = false];
+  optional bool isClustered = 8 [default = false];
 }
 
 enum IndexMethod {
@@ -133,20 +134,10 @@
   BITMAP_IDX = 3;
 }
 
-message GetIndexesProto {
-  repeated IndexProto index = 1;
-}
-
-message IndexProto {
-  required int32 dbId = 1;
-  required int32 tId = 2;
+message IndexNameProto {
+  required string databaseName = 1;
+  optional string namespace = 2;
   required string indexName = 3;
-  required string columnName = 4;
-  required string dataType = 5;
-  required string indexType = 6;
-  optional bool isUnique = 7 [default = false];
-  optional bool isClustered = 8 [default = false];
-  optional bool isAscending = 9 [default = false];
 }
 
 message TableOptionProto {
@@ -161,17 +152,6 @@
   optional string path = 4;
 }
 
-message GetIndexByColumnRequest {
-  required TableIdentifierProto tableIdentifier = 1;
-  required string columnName = 2;
-}
-
-message IndexNameProto {
-  required string databaseName = 1;
-  optional string namespace = 2;
-  required string indexName = 3;
-}
-
 message GetFunctionsResponse {
   repeated FunctionDescProto functionDesc = 1;
 }
@@ -407,3 +387,13 @@
   required ReturnState state = 1;
   repeated FunctionDescProto function = 2;
 }
+
+message IndexListResponse {
+  required ReturnState state = 1;
+  repeated IndexDescProto indexDesc = 2;
+}
+
+message IndexResponse {
+  required ReturnState state = 1;
+  optional IndexDescProto indexDesc = 2;
+}
\ No newline at end of file
diff --git a/tajo-catalog/tajo-catalog-common/src/test/java/org/apache/tajo/catalog/TestIndexDesc.java b/tajo-catalog/tajo-catalog-common/src/test/java/org/apache/tajo/catalog/TestIndexDesc.java
index 3fa8f53..247cd41 100644
--- a/tajo-catalog/tajo-catalog-common/src/test/java/org/apache/tajo/catalog/TestIndexDesc.java
+++ b/tajo-catalog/tajo-catalog-common/src/test/java/org/apache/tajo/catalog/TestIndexDesc.java
@@ -25,6 +25,9 @@
 import org.junit.BeforeClass;
 import org.junit.Test;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+
 import static org.apache.tajo.TajoConstants.DEFAULT_DATABASE_NAME;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotSame;
@@ -33,23 +36,29 @@
   static IndexDesc desc1;
   static IndexDesc desc2;
   static IndexDesc desc3;
-  
-  static {
-    desc1 = new IndexDesc(
-        "idx_test", DEFAULT_DATABASE_NAME, "indexed", new Column("id", Type.INT4),
-        IndexMethod.TWO_LEVEL_BIN_TREE, true, true, true);
-    
-    desc2 = new IndexDesc(
-        "idx_test2", DEFAULT_DATABASE_NAME, "indexed", new Column("score", Type.FLOAT8),
-        IndexMethod.TWO_LEVEL_BIN_TREE, false, false, false);
-    
-    desc3 = new IndexDesc(
-        "idx_test", DEFAULT_DATABASE_NAME, "indexed", new Column("id", Type.INT4),
-        IndexMethod.TWO_LEVEL_BIN_TREE, true, true, true);
-  }
+  static Schema relationSchema;
 
   @BeforeClass
   public static void setUp() throws Exception {
+    relationSchema = new Schema(new Column[]{new Column("id", Type.INT4),
+        new Column("score", Type.FLOAT8), new Column("name", Type.TEXT)});
+    SortSpec[] colSpecs1 = new SortSpec[1];
+    colSpecs1[0] = new SortSpec(new Column("id", Type.INT4), true, true);
+    desc1 = new IndexDesc(DEFAULT_DATABASE_NAME, "indexed",
+        "idx_test", new URI("idx_test"), colSpecs1,
+        IndexMethod.TWO_LEVEL_BIN_TREE, true, true, relationSchema);
+
+    SortSpec[] colSpecs2 = new SortSpec[1];
+    colSpecs2[0] = new SortSpec(new Column("score", Type.FLOAT8), false, false);
+    desc2 = new IndexDesc(DEFAULT_DATABASE_NAME, "indexed",
+        "idx_test2", new URI("idx_test2"), colSpecs2,
+        IndexMethod.TWO_LEVEL_BIN_TREE, false, false, relationSchema);
+
+    SortSpec[] colSpecs3 = new SortSpec[1];
+    colSpecs3[0] = new SortSpec(new Column("id", Type.INT4), true, true);
+    desc3 = new IndexDesc(DEFAULT_DATABASE_NAME, "indexed",
+        "idx_test", new URI("idx_test"), colSpecs3,
+        IndexMethod.TWO_LEVEL_BIN_TREE, true, true, relationSchema);
   }
 
   @AfterClass
@@ -64,22 +73,28 @@
   }
 
   @Test
-  public void testGetFields() {
-    assertEquals("idx_test", desc1.getIndexName());
+  public void testGetFields() throws URISyntaxException {
+    assertEquals("idx_test", desc1.getName());
     assertEquals("indexed", desc1.getTableName());
-    assertEquals(new Column("id", Type.INT4), desc1.getColumn());
+    assertEquals(1, desc1.getKeySortSpecs().length);
+    assertEquals(new Column("id", Type.INT4), desc1.getKeySortSpecs()[0].getSortKey());
+    assertEquals(true, desc1.getKeySortSpecs()[0].isAscending());
+    assertEquals(true, desc1.getKeySortSpecs()[0].isNullFirst());
     assertEquals(IndexMethod.TWO_LEVEL_BIN_TREE, desc1.getIndexMethod());
+    assertEquals(new URI("idx_test"), desc1.getIndexPath());
     assertEquals(true, desc1.isUnique());
     assertEquals(true, desc1.isClustered());
-    assertEquals(true, desc1.isAscending());
-    
-    assertEquals("idx_test2", desc2.getIndexName());
+
+    assertEquals("idx_test2", desc2.getName());
     assertEquals("indexed", desc2.getTableName());
-    assertEquals(new Column("score", Type.FLOAT8), desc2.getColumn());
+    assertEquals(1, desc2.getKeySortSpecs().length);
+    assertEquals(new Column("score", Type.FLOAT8), desc2.getKeySortSpecs()[0].getSortKey());
+    assertEquals(false, desc2.getKeySortSpecs()[0].isAscending());
+    assertEquals(false, desc2.getKeySortSpecs()[0].isNullFirst());
     assertEquals(IndexMethod.TWO_LEVEL_BIN_TREE, desc2.getIndexMethod());
+    assertEquals(new URI("idx_test2"), desc2.getIndexPath());
     assertEquals(false, desc2.isUnique());
     assertEquals(false, desc2.isClustered());
-    assertEquals(false, desc2.isAscending());
   }
 
   @Test
diff --git a/tajo-catalog/tajo-catalog-drivers/tajo-hive/src/main/java/org/apache/tajo/catalog/store/HiveCatalogStore.java b/tajo-catalog/tajo-catalog-drivers/tajo-hive/src/main/java/org/apache/tajo/catalog/store/HiveCatalogStore.java
index 8a08b77..b49499f 100644
--- a/tajo-catalog/tajo-catalog-drivers/tajo-hive/src/main/java/org/apache/tajo/catalog/store/HiveCatalogStore.java
+++ b/tajo-catalog/tajo-catalog-drivers/tajo-hive/src/main/java/org/apache/tajo/catalog/store/HiveCatalogStore.java
@@ -41,7 +41,7 @@
 import org.apache.tajo.catalog.proto.CatalogProtos;
 import org.apache.tajo.catalog.proto.CatalogProtos.ColumnProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.DatabaseProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.IndexProto;
+import org.apache.tajo.catalog.proto.CatalogProtos.IndexDescProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableDescriptorProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableOptionProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TablePartitionProto;
@@ -762,22 +762,26 @@
   @Override
   public void addPartitionMethod(CatalogProtos.PartitionMethodProto partitionMethodProto) throws CatalogException {
     // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
   public CatalogProtos.PartitionMethodProto getPartitionMethod(String databaseName, String tableName)
       throws CatalogException {
-    return null;  // TODO - not implemented yet
+    // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
   public boolean existPartitionMethod(String databaseName, String tableName) throws CatalogException {
-    return false;  // TODO - not implemented yet
+    // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
   public void dropPartitionMethod(String databaseName, String tableName) throws CatalogException {
     // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
@@ -827,63 +831,75 @@
   @Override
   public final void addFunction(final FunctionDesc func) throws CatalogException {
     // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
   public final void deleteFunction(final FunctionDesc func) throws CatalogException {
     // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
   public final void existFunction(final FunctionDesc func) throws CatalogException {
     // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
   public final List<String> getAllFunctionNames() throws CatalogException {
     // TODO - not implemented yet
-    return null;
-  }
-
-  @Override
-  public void dropIndex(String databaseName, String indexName) throws CatalogException {
-    // TODO - not implemented yet
-  }
-
-  @Override
-  public boolean existIndexByName(String databaseName, String indexName) throws CatalogException {
-    // TODO - not implemented yet
-    return false;
-  }
-
-  @Override
-  public CatalogProtos.IndexDescProto[] getIndexes(String databaseName, String tableName) throws CatalogException {
-    // TODO - not implemented yet
-    return null;
+    throw new UnsupportedOperationException();
   }
 
   @Override
   public void createIndex(CatalogProtos.IndexDescProto proto) throws CatalogException {
     // TODO - not implemented yet
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public void dropIndex(String databaseName, String indexName) throws CatalogException {
+    // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
   public CatalogProtos.IndexDescProto getIndexByName(String databaseName, String indexName) throws CatalogException {
     // TODO - not implemented yet
-    return null;
+    throw new UnsupportedOperationException();
   }
 
   @Override
-  public CatalogProtos.IndexDescProto getIndexByColumn(String databaseName, String tableName, String columnName)
+  public CatalogProtos.IndexDescProto getIndexByColumns(String databaseName, String tableName, String[] columnNames)
       throws CatalogException {
     // TODO - not implemented yet
-    return null;
+    throw new UnsupportedOperationException();
   }
 
   @Override
-  public boolean existIndexByColumn(String databaseName, String tableName, String columnName) throws CatalogException {
+  public boolean existIndexByName(String databaseName, String indexName) throws CatalogException {
     // TODO - not implemented yet
-    return false;
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean existIndexByColumns(String databaseName, String tableName, String[] columnNames)
+      throws CatalogException {
+    // TODO - not implemented yet
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public List<String> getAllIndexNamesByTable(String databaseName, String tableName) throws CatalogException {
+    // TODO - not implemented yet
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public boolean existIndexesByTable(String databaseName, String tableName) throws CatalogException {
+    // TODO - not implemented yet
+    throw new UnsupportedOperationException();
   }
 
   @Override
@@ -931,7 +947,7 @@
   }
 
   @Override
-  public List<IndexProto> getAllIndexes() throws CatalogException {
+  public List<IndexDescProto> getAllIndexes() throws CatalogException {
     throw new UnsupportedOperationException();
   }
 
diff --git a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/CatalogServer.java b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/CatalogServer.java
index 6a3fc16..2494012 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/CatalogServer.java
+++ b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/CatalogServer.java
@@ -1106,29 +1106,44 @@
     }
 
     @Override
-    public ReturnState existIndexByColumn(RpcController controller, GetIndexByColumnRequest request) {
+    public ReturnState existIndexByColumnNames(RpcController controller, GetIndexByColumnNamesRequest request)
+        throws ServiceException {
 
       TableIdentifierProto identifier = request.getTableIdentifier();
       String databaseName = identifier.getDatabaseName();
       String tableName = identifier.getTableName();
-      String columnName = request.getColumnName();
+      List<String> columnNames = request.getColumnNamesList();
 
       rlock.lock();
       try {
-        return store.existIndexByColumn(databaseName, tableName, columnName) ?
-            OK : errUndefinedIndex(tableName, columnName);
-
+        return store.existIndexByColumns(databaseName, tableName,
+            columnNames.toArray(new String[columnNames.size()])) ? OK : errUndefinedIndex(tableName, columnNames);
       } catch (Throwable t) {
         printStackTraceIfError(LOG, t);
         return returnError(t);
-
       } finally {
         rlock.unlock();
       }
     }
 
     @Override
-    public GetIndexResponse getIndexByName(RpcController controller, IndexNameProto request)
+    public ReturnState existIndexesByTable(RpcController controller, TableIdentifierProto request) throws ServiceException {
+      String databaseName = request.getDatabaseName();
+      String tableName = request.getTableName();
+
+      rlock.lock();
+      try {
+        return store.existIndexesByTable(databaseName, tableName) ? OK : errUndefinedIndex(tableName);
+      } catch (Throwable t) {
+        printStackTraceIfError(LOG, t);
+        return returnError(t);
+      } finally {
+        rlock.unlock();
+      }
+    }
+
+    @Override
+    public IndexResponse getIndexByName(RpcController controller, IndexNameProto request)
         throws ServiceException {
 
       String databaseName = request.getDatabaseName();
@@ -1138,20 +1153,20 @@
       try {
 
         if (!store.existIndexByName(databaseName, indexName)) {
-          return GetIndexResponse.newBuilder()
+          return IndexResponse.newBuilder()
               .setState(errUndefinedIndexName(indexName))
               .build();
         }
 
-        return GetIndexResponse.newBuilder()
+        return IndexResponse.newBuilder()
             .setState(OK)
-            .setIndex(store.getIndexByName(databaseName, indexName))
+            .setIndexDesc(store.getIndexByName(databaseName, indexName))
             .build();
 
       } catch (Throwable t) {
         printStackTraceIfError(LOG, t);
 
-        return GetIndexResponse.newBuilder()
+        return IndexResponse.newBuilder()
             .setState(returnError(t))
             .build();
 
@@ -1161,31 +1176,77 @@
     }
 
     @Override
-    public GetIndexResponse getIndexByColumn(RpcController controller, GetIndexByColumnRequest request)
+    public IndexResponse getIndexByColumnNames(RpcController controller, GetIndexByColumnNamesRequest request)
         throws ServiceException {
 
       TableIdentifierProto identifier = request.getTableIdentifier();
       String databaseName = identifier.getDatabaseName();
       String tableName = identifier.getTableName();
-      String columnName = request.getColumnName();
+      List<String> columnNamesList = request.getColumnNamesList();
+      String[] columnNames = new String[columnNamesList.size()];
+      columnNames = columnNamesList.toArray(columnNames);
 
       rlock.lock();
       try {
-        if (!store.existIndexByColumn(databaseName, tableName, columnName)) {
-          return GetIndexResponse.newBuilder()
-              .setState(errUndefinedIndex(tableName, columnName))
+        if (!store.existIndexByColumns(databaseName, tableName, columnNames)) {
+          return IndexResponse.newBuilder()
+              .setState(errUndefinedIndex(tableName, columnNamesList))
               .build();
         }
-
-        return GetIndexResponse.newBuilder()
+        return IndexResponse.newBuilder()
             .setState(OK)
-            .setIndex(store.getIndexByColumn(databaseName, tableName, columnName))
+            .setIndexDesc(store.getIndexByColumns(databaseName, tableName, columnNames))
             .build();
+      } catch (Throwable t) {
+        printStackTraceIfError(LOG, t);
+
+        return IndexResponse.newBuilder()
+            .setState(returnError(t))
+            .build();
+      } finally {
+        rlock.unlock();
+      }
+    }
+
+    @Override
+    public IndexListResponse getAllIndexesByTable(RpcController controller, TableIdentifierProto request)
+        throws ServiceException {
+      String databaseName = request.getDatabaseName();
+      String tableName = request.getTableName();
+
+      rlock.lock();
+      try {
+        if (!store.existIndexesByTable(databaseName, tableName)) {
+          return IndexListResponse.newBuilder()
+              .setState(errUndefinedIndex(tableName))
+              .build();
+        }
+        IndexListResponse.Builder builder = IndexListResponse.newBuilder().setState(OK);
+        for (String eachIndexName : store.getAllIndexNamesByTable(databaseName, tableName)) {
+          builder.addIndexDesc(store.getIndexByName(databaseName, eachIndexName));
+        }
+        return builder.build();
+      } catch (Throwable t) {
+        printStackTraceIfError(LOG, t);
+
+        return IndexListResponse.newBuilder()
+            .setState(returnError(t))
+            .build();
+      } finally {
+        rlock.unlock();
+      }
+    }
+
+    @Override
+    public IndexListResponse getAllIndexes(RpcController controller, NullProto request) throws ServiceException {
+      rlock.lock();
+      try {
+        return IndexListResponse.newBuilder().addAllIndexDesc(store.getAllIndexes()).build();
 
       } catch (Throwable t) {
         printStackTraceIfError(LOG, t);
 
-        return GetIndexResponse.newBuilder()
+        return IndexListResponse.newBuilder()
             .setState(returnError(t))
             .build();
 
@@ -1217,24 +1278,6 @@
         wlock.unlock();
       }
     }
-    
-    @Override
-    public GetIndexesResponse getAllIndexes(RpcController controller, NullProto request) throws ServiceException {
-      rlock.lock();
-      try {
-        return GetIndexesResponse.newBuilder().addAllIndex(store.getAllIndexes()).build();
-
-      } catch (Throwable t) {
-        printStackTraceIfError(LOG, t);
-
-        return GetIndexesResponse.newBuilder()
-            .setState(returnError(t))
-            .build();
-
-      } finally {
-        rlock.unlock();
-      }
-    }
 
     private boolean containFunction(String signature) {
       List<FunctionDescProto> found = findFunction(signature);
diff --git a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/dictionary/IndexesTableDescriptor.java b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/dictionary/IndexesTableDescriptor.java
index a079a93..d527b19 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/dictionary/IndexesTableDescriptor.java
+++ b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/dictionary/IndexesTableDescriptor.java
@@ -27,12 +27,8 @@
       new ColumnDescriptor("db_id", Type.INT4, 0),
       new ColumnDescriptor("tid", Type.INT4, 0),
       new ColumnDescriptor("index_name", Type.TEXT, 0),
-      new ColumnDescriptor("column_name", Type.TEXT, 0),
-      new ColumnDescriptor("data_type", Type.TEXT, 0),
-      new ColumnDescriptor("index_type", Type.TEXT, 0),
-      new ColumnDescriptor("is_unique", Type.BOOLEAN, 0),
-      new ColumnDescriptor("is_clustered", Type.BOOLEAN, 0),
-      new ColumnDescriptor("is_ascending", Type.BOOLEAN, 0)
+      new ColumnDescriptor("index_method", Type.TEXT, 0),
+      new ColumnDescriptor("index_path", Type.TEXT, 0),
   };
 
   public IndexesTableDescriptor(InfoSchemaMetadataDictionary metadataDictionary) {
diff --git a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/AbstractDBStore.java b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/AbstractDBStore.java
index c6b7d36..2e9c340 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/AbstractDBStore.java
+++ b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/AbstractDBStore.java
@@ -27,9 +27,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
 import org.apache.tajo.annotation.Nullable;
-import org.apache.tajo.catalog.CatalogConstants;
-import org.apache.tajo.catalog.CatalogUtil;
-import org.apache.tajo.catalog.FunctionDesc;
+import org.apache.tajo.catalog.*;
 import org.apache.tajo.catalog.exception.*;
 import org.apache.tajo.catalog.proto.CatalogProtos;
 import org.apache.tajo.catalog.proto.CatalogProtos.*;
@@ -2177,36 +2175,55 @@
     Connection conn = null;
     PreparedStatement pstmt = null;
 
-    String databaseName = proto.getTableIdentifier().getDatabaseName();
-    String tableName = proto.getTableIdentifier().getTableName();
-    String columnName = CatalogUtil.extractSimpleName(proto.getColumn().getName());
+    final String databaseName = proto.getTableIdentifier().getDatabaseName();
+    final String tableName = CatalogUtil.extractSimpleName(proto.getTableIdentifier().getTableName());
 
     try {
+      // indexes table
       int databaseId = getDatabaseId(databaseName);
       int tableId = getTableId(databaseId, databaseName, tableName);
 
       String sql = "INSERT INTO " + TB_INDEXES +
           " (" + COL_DATABASES_PK + ", " + COL_TABLES_PK + ", INDEX_NAME, " +
-          "" + COL_COLUMN_NAME + ", DATA_TYPE, INDEX_TYPE, IS_UNIQUE, IS_CLUSTERED, IS_ASCENDING) " +
-        "VALUES (?,?,?,?,?,?,?,?,?)";
+          "INDEX_TYPE, PATH, COLUMN_NAMES, DATA_TYPES, ORDERS, NULL_ORDERS, IS_UNIQUE, IS_CLUSTERED) " +
+          "VALUES (?,?,?,?,?,?,?,?,?,?,?)";
 
       if (LOG.isDebugEnabled()) {
         LOG.debug(sql);
       }
 
+      StringBuilder columnNamesBuilder = new StringBuilder();
+      StringBuilder dataTypesBuilder= new StringBuilder();
+      StringBuilder ordersBuilder = new StringBuilder();
+      StringBuilder nullOrdersBuilder = new StringBuilder();
+      for (SortSpecProto columnSpec : proto.getKeySortSpecsList()) {
+        // Since the key columns are always sorted in order of their occurrence position in the relation schema,
+        // the concatenated name can be uniquely identified.
+        columnNamesBuilder.append(columnSpec.getColumn().getName()).append(",");
+        dataTypesBuilder.append(columnSpec.getColumn().getDataType().getType().name()).append(",");
+        ordersBuilder.append(columnSpec.getAscending()).append(",");
+        nullOrdersBuilder.append(columnSpec.getNullFirst()).append(",");
+      }
+      columnNamesBuilder.deleteCharAt(columnNamesBuilder.length()-1);
+      dataTypesBuilder.deleteCharAt(dataTypesBuilder.length()-1);
+      ordersBuilder.deleteCharAt(ordersBuilder.length()-1);
+      nullOrdersBuilder.deleteCharAt(nullOrdersBuilder.length()-1);
+
       conn = getConnection();
       conn.setAutoCommit(false);
 
       pstmt = conn.prepareStatement(sql);
       pstmt.setInt(1, databaseId);
       pstmt.setInt(2, tableId);
-      pstmt.setString(3, proto.getIndexName());
-      pstmt.setString(4, columnName);
-      pstmt.setString(5, proto.getColumn().getDataType().getType().name());
-      pstmt.setString(6, proto.getIndexMethod().toString());
-      pstmt.setBoolean(7, proto.hasIsUnique() && proto.getIsUnique());
-      pstmt.setBoolean(8, proto.hasIsClustered() && proto.getIsClustered());
-      pstmt.setBoolean(9, proto.hasIsAscending() && proto.getIsAscending());
+      pstmt.setString(3, proto.getIndexName()); // index name
+      pstmt.setString(4, proto.getIndexMethod().toString()); // index type
+      pstmt.setString(5, proto.getIndexPath()); // index path
+      pstmt.setString(6, columnNamesBuilder.toString());
+      pstmt.setString(7, dataTypesBuilder.toString());
+      pstmt.setString(8, ordersBuilder.toString());
+      pstmt.setString(9, nullOrdersBuilder.toString());
+      pstmt.setBoolean(10, proto.hasIsUnique() && proto.getIsUnique());
+      pstmt.setBoolean(11, proto.hasIsClustered() && proto.getIsClustered());
       pstmt.executeUpdate();
       conn.commit();
     } catch (SQLException se) {
@@ -2260,9 +2277,7 @@
   }
 
   final static String GET_INDEXES_SQL =
-      "SELECT " + COL_TABLES_PK + ", INDEX_NAME, COLUMN_NAME, DATA_TYPE, INDEX_TYPE, IS_UNIQUE, " +
-          "IS_CLUSTERED, IS_ASCENDING FROM " + TB_INDEXES;
-
+      "SELECT * FROM " + TB_INDEXES;
 
   @Override
   public IndexDescProto getIndexByName(String databaseName, final String indexName)
@@ -2293,6 +2308,7 @@
       resultToIndexDescProtoBuilder(builder, res);
       String tableName = getTableName(conn, res.getInt(COL_TABLES_PK));
       builder.setTableIdentifier(CatalogUtil.buildTableIdentifier(databaseName, tableName));
+      builder.setTargetRelationSchema(getTable(databaseName, tableName).getSchema());
       proto = builder.build();
     } catch (SQLException se) {
       throw new TajoInternalError(se);
@@ -2304,9 +2320,8 @@
   }
 
   @Override
-  public IndexDescProto getIndexByColumn(final String databaseName,
-                                         final String tableName,
-                                         final String columnName) throws CatalogException {
+  public IndexDescProto getIndexByColumns(String databaseName, String tableName, String[] columnNames)
+      throws CatalogException {
     Connection conn = null;
     ResultSet res = null;
     PreparedStatement pstmt = null;
@@ -2314,25 +2329,35 @@
 
     try {
       int databaseId = getDatabaseId(databaseName);
+      int tableId = getTableId(databaseId, databaseName, tableName);
+      TableDescProto tableDescProto = getTable(databaseName, tableName);
 
-      String sql = GET_INDEXES_SQL + " WHERE " + COL_DATABASES_PK + "=? AND COLUMN_NAME=?";
+      String sql = GET_INDEXES_SQL + " WHERE " + COL_DATABASES_PK + "=? AND " +
+          COL_TABLES_PK + "=? AND COLUMN_NAMES=?";
 
       if (LOG.isDebugEnabled()) {
         LOG.debug(sql);
       }
 
+      // Since the column names in the unified name are always sorted
+      // in order of occurrence position in the relation schema,
+      // they can be uniquely identified.
+      String unifiedName = CatalogUtil.buildFQName(databaseName, tableName,
+          CatalogUtil.getUnifiedSimpleColumnName(new Schema(tableDescProto.getSchema()), columnNames));
       conn = getConnection();
       pstmt = conn.prepareStatement(sql);
       pstmt.setInt(1, databaseId);
-      ;
-      pstmt.setString(2, columnName);
+      pstmt.setInt(2, tableId);
+      pstmt.setString(3, unifiedName);
       res = pstmt.executeQuery();
       if (!res.next()) {
-        throw new TajoInternalError("ERROR: there is no index matched to " + columnName);
+        throw new TajoInternalError("ERROR: there is no index matched to " + unifiedName);
       }
+
       IndexDescProto.Builder builder = IndexDescProto.newBuilder();
       resultToIndexDescProtoBuilder(builder, res);
       builder.setTableIdentifier(CatalogUtil.buildTableIdentifier(databaseName, tableName));
+      builder.setTargetRelationSchema(tableDescProto.getSchema());
       proto = builder.build();
     } catch (SQLException se) {
       throw new TajoInternalError(se);
@@ -2377,7 +2402,7 @@
   }
 
   @Override
-  public boolean existIndexByColumn(String databaseName, String tableName, String columnName)
+  public boolean existIndexByColumns(String databaseName, String tableName, String[] columnNames)
       throws CatalogException {
     Connection conn = null;
     ResultSet res = null;
@@ -2387,18 +2412,27 @@
 
     try {
       int databaseId = getDatabaseId(databaseName);
+      int tableId = getTableId(databaseId, databaseName, tableName);
+      Schema relationSchema = new Schema(getTable(databaseName, tableName).getSchema());
 
       String sql =
-          "SELECT INDEX_NAME FROM " + TB_INDEXES + " WHERE " + COL_DATABASES_PK + "=? AND COLUMN_NAME=?";
+          "SELECT " + COL_INDEXES_PK + " FROM " + TB_INDEXES +
+              " WHERE " + COL_DATABASES_PK + "=? AND " + COL_TABLES_PK + "=? AND COLUMN_NAMES=?";
 
       if (LOG.isDebugEnabled()) {
         LOG.debug(sql);
       }
 
+      // Since the column names in the unified name are always sorted
+      // in order of occurrence position in the relation schema,
+      // they can be uniquely identified.
+      String unifiedName = CatalogUtil.buildFQName(databaseName, tableName,
+          CatalogUtil.getUnifiedSimpleColumnName(new Schema(relationSchema), columnNames));
       conn = getConnection();
       pstmt = conn.prepareStatement(sql);
       pstmt.setInt(1, databaseId);
-      pstmt.setString(2, columnName);
+      pstmt.setInt(2, tableId);
+      pstmt.setString(3, unifiedName);
       res = pstmt.executeQuery();
       exist = res.next();
     } catch (SQLException se) {
@@ -2410,22 +2444,18 @@
   }
 
   @Override
-  public IndexDescProto[] getIndexes(String databaseName, final String tableName)
+  public List<String> getAllIndexNamesByTable(final String databaseName, final String tableName)
       throws CatalogException {
-    Connection conn = null;
     ResultSet res = null;
     PreparedStatement pstmt = null;
-    final List<IndexDescProto> protos = new ArrayList<IndexDescProto>();
+    final List<String> indexNames = new ArrayList<String>();
 
     try {
       final int databaseId = getDatabaseId(databaseName);
       final int tableId = getTableId(databaseId, databaseName, tableName);
-      final TableIdentifierProto tableIdentifier = CatalogUtil.buildTableIdentifier(databaseName, tableName);
-
 
       String sql = GET_INDEXES_SQL + " WHERE " + COL_DATABASES_PK + "=? AND " + COL_TABLES_PK + "=?";
 
-
       if (LOG.isDebugEnabled()) {
         LOG.debug(sql);
       }
@@ -2437,10 +2467,7 @@
       res = pstmt.executeQuery();
 
       while (res.next()) {
-        IndexDescProto.Builder builder = IndexDescProto.newBuilder();
-        resultToIndexDescProtoBuilder(builder, res);
-        builder.setTableIdentifier(tableIdentifier);
-        protos.add(builder.build());
+        indexNames.add(res.getString("index_name"));
       }
     } catch (SQLException se) {
       throw new TajoInternalError(se);
@@ -2448,56 +2475,73 @@
       CatalogUtil.closeQuietly(pstmt, res);
     }
 
-    return protos.toArray(new IndexDescProto[protos.size()]);
+    return indexNames;
   }
-  
-  @Override
-  public List<IndexProto> getAllIndexes() throws CatalogException {
-    Connection conn = null;
-    Statement stmt = null;
-    ResultSet resultSet = null;
 
-    List<IndexProto> indexes = new ArrayList<IndexProto>();
+  @Override
+  public boolean existIndexesByTable(String databaseName, String tableName) throws CatalogException {
+    ResultSet res = null;
+    PreparedStatement pstmt = null;
+    final List<String> indexNames = new ArrayList<String>();
 
     try {
-      String sql = "SELECT " + COL_DATABASES_PK + ", " + COL_TABLES_PK + ", INDEX_NAME, " +
-        "COLUMN_NAME, DATA_TYPE, INDEX_TYPE, IS_UNIQUE, IS_CLUSTERED, IS_ASCENDING FROM " + TB_INDEXES;
+      final int databaseId = getDatabaseId(databaseName);
+      final int tableId = getTableId(databaseId, databaseName, tableName);
+
+      String sql = GET_INDEXES_SQL + " WHERE " + COL_DATABASES_PK + "=? AND " + COL_TABLES_PK + "=?";
+
+      if (LOG.isDebugEnabled()) {
+        LOG.debug(sql);
+      }
 
       conn = getConnection();
-      stmt = conn.createStatement();
-      resultSet = stmt.executeQuery(sql);
-      while (resultSet.next()) {
-        IndexProto.Builder builder = IndexProto.newBuilder();
-        
-        builder.setDbId(resultSet.getInt(COL_DATABASES_PK));
-        builder.setTId(resultSet.getInt(COL_TABLES_PK));
-        builder.setIndexName(resultSet.getString("INDEX_NAME"));
-        builder.setColumnName(resultSet.getString("COLUMN_NAME"));
-        builder.setDataType(resultSet.getString("DATA_TYPE"));
-        builder.setIndexType(resultSet.getString("INDEX_TYPE"));
-        builder.setIsUnique(resultSet.getBoolean("IS_UNIQUE"));
-        builder.setIsClustered(resultSet.getBoolean("IS_CLUSTERED"));
-        builder.setIsAscending(resultSet.getBoolean("IS_ASCENDING"));
-        
-        indexes.add(builder.build());
-      }
+      pstmt = conn.prepareStatement(sql);
+      pstmt.setInt(1, databaseId);
+      pstmt.setInt(2, tableId);
+      res = pstmt.executeQuery();
+
+      return res.next();
     } catch (SQLException se) {
       throw new TajoInternalError(se);
     } finally {
-      CatalogUtil.closeQuietly(stmt, resultSet);
+      CatalogUtil.closeQuietly(pstmt, res);
     }
-    
-    return indexes;
+  }
+
+  @Override
+  public List<IndexDescProto> getAllIndexes() throws CatalogException {
+    List<IndexDescProto> indexDescProtos = TUtil.newList();
+    for (String databaseName : getAllDatabaseNames()) {
+      for (String tableName : getAllTableNames(databaseName)) {
+        for (String indexName: getAllIndexNamesByTable(databaseName, tableName)) {
+          indexDescProtos.add(getIndexByName(databaseName, indexName));
+        }
+      }
+    }
+    return indexDescProtos;
   }
 
   private void resultToIndexDescProtoBuilder(IndexDescProto.Builder builder,
                                              final ResultSet res) throws SQLException {
     builder.setIndexName(res.getString("index_name"));
-    builder.setColumn(indexResultToColumnProto(res));
     builder.setIndexMethod(getIndexMethod(res.getString("index_type").trim()));
+    builder.setIndexPath(res.getString("path"));
+    String[] columnNames, dataTypes, orders, nullOrders;
+    columnNames = res.getString("column_names").trim().split(",");
+    dataTypes = res.getString("data_types").trim().split(",");
+    orders = res.getString("orders").trim().split(",");
+    nullOrders = res.getString("null_orders").trim().split(",");
+    int columnNum = columnNames.length;
+    for (int i = 0; i < columnNum; i++) {
+      SortSpecProto.Builder colSpecBuilder = SortSpecProto.newBuilder();
+      colSpecBuilder.setColumn(ColumnProto.newBuilder().setName(columnNames[i])
+          .setDataType(CatalogUtil.newSimpleDataType(getDataType(dataTypes[i]))).build());
+      colSpecBuilder.setAscending(orders[i].equals("true"));
+      colSpecBuilder.setNullFirst(nullOrders[i].equals("true"));
+      builder.addKeySortSpecs(colSpecBuilder.build());
+    }
     builder.setIsUnique(res.getBoolean("is_unique"));
     builder.setIsClustered(res.getBoolean("is_clustered"));
-    builder.setIsAscending(res.getBoolean("is_ascending"));
   }
 
   /**
diff --git a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/CatalogStore.java b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/CatalogStore.java
index 4ffedcf..d8d0103 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/CatalogStore.java
+++ b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/CatalogStore.java
@@ -20,14 +20,7 @@
 
 import org.apache.tajo.catalog.FunctionDesc;
 import org.apache.tajo.catalog.proto.CatalogProtos;
-import org.apache.tajo.catalog.proto.CatalogProtos.ColumnProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.DatabaseProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.IndexDescProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.IndexProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.TableDescriptorProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.TableOptionProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.TablePartitionProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.TableStatsProto;
+import org.apache.tajo.catalog.proto.CatalogProtos.*;
 
 import java.io.Closeable;
 
@@ -38,10 +31,6 @@
 import java.util.Collection;
 import java.util.List;
 
-import static org.apache.tajo.catalog.proto.CatalogProtos.AlterTablespaceProto;
-import static org.apache.tajo.catalog.proto.CatalogProtos.PartitionMethodProto;
-import static org.apache.tajo.catalog.proto.CatalogProtos.TablespaceProto;
-
 public interface CatalogStore extends Closeable {
   /*************************** Tablespace ******************************/
   void createTablespace(String spaceName, String spaceUri) throws CatalogException;
@@ -123,18 +112,20 @@
   void dropIndex(String databaseName, String indexName) throws CatalogException;
   
   IndexDescProto getIndexByName(String databaseName, String indexName) throws CatalogException;
-  
-  IndexDescProto getIndexByColumn(String databaseName, String tableName, String columnName)
+
+  IndexDescProto getIndexByColumns(String databaseName, String tableName, String[] columnNames)
       throws CatalogException;
   
   boolean existIndexByName(String databaseName, String indexName) throws CatalogException;
-  
-  boolean existIndexByColumn(String databaseName, String tableName, String columnName)
+
+  boolean existIndexByColumns(String databaseName, String tableName, String[] columnNames)
       throws CatalogException;
 
-  IndexDescProto [] getIndexes(String databaseName, String tableName) throws CatalogException;
-  
-  List<IndexProto> getAllIndexes() throws CatalogException;
+  List<String> getAllIndexNamesByTable(String databaseName, String tableName) throws CatalogException;
+
+  boolean existIndexesByTable(String databaseName, String tableName) throws CatalogException;
+
+  List<IndexDescProto> getAllIndexes() throws CatalogException;
 
   /************************** FUNCTION *****************************/
 
diff --git a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/MemStore.java b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/MemStore.java
index 5763f31..74b6023 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/MemStore.java
+++ b/tajo-catalog/tajo-catalog-server/src/main/java/org/apache/tajo/catalog/store/MemStore.java
@@ -26,15 +26,17 @@
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
 import org.apache.tajo.TajoConstants;
+import org.apache.tajo.catalog.CatalogConstants;
 import org.apache.tajo.catalog.CatalogUtil;
 import org.apache.tajo.catalog.FunctionDesc;
+import org.apache.tajo.catalog.Schema;
 import org.apache.tajo.catalog.TableMeta;
 import org.apache.tajo.catalog.exception.*;
 import org.apache.tajo.catalog.proto.CatalogProtos;
 import org.apache.tajo.catalog.proto.CatalogProtos.ColumnProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.DatabaseProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.IndexDescProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.IndexProto;
+import org.apache.tajo.catalog.proto.CatalogProtos.SortSpecProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableDescProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableDescriptorProto;
 import org.apache.tajo.catalog.proto.CatalogProtos.TableOptionProto;
@@ -64,7 +66,6 @@
 
   public MemStore(Configuration conf) {
   }
-
   
   public void close() throws IOException {
     databases.clear();
@@ -151,6 +152,8 @@
     }
 
     databases.put(databaseName, new HashMap<String, CatalogProtos.TableDescProto>());
+    indexes.put(databaseName, new HashMap<String, IndexDescProto>());
+    indexesByColumn.put(databaseName, new HashMap<String, IndexDescProto>());
   }
 
   @Override
@@ -164,6 +167,8 @@
       throw new UndefinedDatabaseException(databaseName);
     }
     databases.remove(databaseName);
+    indexes.remove(databaseName);
+    indexesByColumn.remove(databaseName);
   }
 
   @Override
@@ -609,17 +614,23 @@
   @Override
   public void createIndex(IndexDescProto proto) throws CatalogException {
     final String databaseName = proto.getTableIdentifier().getDatabaseName();
+    final String tableName = CatalogUtil.extractSimpleName(proto.getTableIdentifier().getTableName());
 
     Map<String, IndexDescProto> index = checkAndGetDatabaseNS(indexes, databaseName);
     Map<String, IndexDescProto> indexByColumn = checkAndGetDatabaseNS(indexesByColumn, databaseName);
+    TableDescProto tableDescProto = getTable(databaseName, tableName);
 
     if (index.containsKey(proto.getIndexName())) {
       throw new DuplicateIndexException(proto.getIndexName());
     }
 
     index.put(proto.getIndexName(), proto);
-    indexByColumn.put(proto.getTableIdentifier().getTableName() + "."
-        + CatalogUtil.extractSimpleName(proto.getColumn().getName()), proto);
+    String originalTableName = proto.getTableIdentifier().getTableName();
+    String simpleTableName = CatalogUtil.extractSimpleName(originalTableName);
+    indexByColumn.put(CatalogUtil.buildFQName(proto.getTableIdentifier().getDatabaseName(),
+            simpleTableName,
+            getUnifiedNameForIndexByColumn(proto)),
+        proto);
   }
 
   /* (non-Javadoc)
@@ -628,10 +639,19 @@
   @Override
   public void dropIndex(String databaseName, String indexName) throws CatalogException {
     Map<String, IndexDescProto> index = checkAndGetDatabaseNS(indexes, databaseName);
+    Map<String, IndexDescProto> indexByColumn = checkAndGetDatabaseNS(indexesByColumn, databaseName);
     if (!index.containsKey(indexName)) {
       throw new UndefinedIndexException(indexName);
     }
+    IndexDescProto proto = index.get(indexName);
+    final String tableName = CatalogUtil.extractSimpleName(proto.getTableIdentifier().getTableName());
+    TableDescProto tableDescProto = getTable(databaseName, tableName);
     index.remove(indexName);
+    String originalTableName = proto.getTableIdentifier().getTableName();
+    String simpleTableName = CatalogUtil.extractSimpleName(originalTableName);
+    indexByColumn.remove(CatalogUtil.buildFQName(proto.getTableIdentifier().getDatabaseName(),
+        simpleTableName,
+        getUnifiedNameForIndexByColumn(proto)));
   }
 
   /* (non-Javadoc)
@@ -647,19 +667,18 @@
     return index.get(indexName);
   }
 
-  /* (non-Javadoc)
-   * @see CatalogStore#getIndexByName(java.lang.String, java.lang.String)
-   */
   @Override
-  public IndexDescProto getIndexByColumn(String databaseName, String tableName, String columnName)
-      throws CatalogException {
-
+  public IndexDescProto getIndexByColumns(String databaseName, String tableName, String[] columnNames) throws CatalogException {
     Map<String, IndexDescProto> indexByColumn = checkAndGetDatabaseNS(indexesByColumn, databaseName);
-    if (!indexByColumn.containsKey(columnName)) {
-      throw new UndefinedIndexException(CatalogUtil.buildFQName(databaseName, tableName), columnName);
+    String simpleTableName = CatalogUtil.extractSimpleName(tableName);
+    TableDescProto tableDescProto = getTable(databaseName, simpleTableName);
+    String qualifiedColumnName = CatalogUtil.buildFQName(databaseName, simpleTableName,
+        CatalogUtil.getUnifiedSimpleColumnName(new Schema(tableDescProto.getSchema()), columnNames));
+    if (!indexByColumn.containsKey(qualifiedColumnName)) {
+      throw new UndefinedIndexException(qualifiedColumnName);
     }
 
-    return indexByColumn.get(columnName);
+    return indexByColumn.get(qualifiedColumnName);
   }
 
   @Override
@@ -669,50 +688,47 @@
   }
 
   @Override
-  public boolean existIndexByColumn(String databaseName, String tableName, String columnName)
-      throws CatalogException {
+  public boolean existIndexByColumns(String databaseName, String tableName, String[] columnNames) throws CatalogException {
     Map<String, IndexDescProto> indexByColumn = checkAndGetDatabaseNS(indexesByColumn, databaseName);
-    return indexByColumn.containsKey(columnName);
+    TableDescProto tableDescProto = getTable(databaseName, tableName);
+    return indexByColumn.containsKey(
+        CatalogUtil.buildFQName(databaseName, CatalogUtil.extractSimpleName(tableName),
+            CatalogUtil.getUnifiedSimpleColumnName(new Schema(tableDescProto.getSchema()), columnNames)));
   }
 
   @Override
-  public IndexDescProto[] getIndexes(String databaseName, String tableName) throws CatalogException {
-    List<IndexDescProto> protos = new ArrayList<IndexDescProto>();
+  public List<String> getAllIndexNamesByTable(String databaseName, String tableName) throws CatalogException {
+    List<String> indexNames = new ArrayList<String>();
     Map<String, IndexDescProto> indexByColumn = checkAndGetDatabaseNS(indexesByColumn, databaseName);
+    String simpleTableName = CatalogUtil.extractSimpleName(tableName);
     for (IndexDescProto proto : indexByColumn.values()) {
-      if (proto.getTableIdentifier().getTableName().equals(tableName)) {
-        protos.add(proto);
+      if (proto.getTableIdentifier().getTableName().equals(simpleTableName)) {
+        indexNames.add(proto.getIndexName());
       }
     }
 
-    return protos.toArray(new IndexDescProto[protos.size()]);
+    return indexNames;
   }
-  
+
   @Override
-  public List<IndexProto> getAllIndexes() throws CatalogException {
-    List<IndexProto> indexList = new ArrayList<CatalogProtos.IndexProto>();
-    Set<String> databases = indexes.keySet();
-    
-    for (String databaseName: databases) {
-      Map<String, IndexDescProto> indexMap = indexes.get(databaseName);
-      
-      for (Map.Entry<String, IndexDescProto> entry: indexMap.entrySet()) {
-        IndexDescProto indexDesc = entry.getValue();
-        IndexProto.Builder builder = IndexProto.newBuilder();
-        
-        builder.setColumnName(indexDesc.getColumn().getName());
-        builder.setDataType(indexDesc.getColumn().getDataType().getType().toString());
-        builder.setIndexName(entry.getKey());
-        builder.setIndexType(indexDesc.getIndexMethod().toString());
-        builder.setIsAscending(indexDesc.hasIsAscending() && indexDesc.getIsAscending());
-        builder.setIsClustered(indexDesc.hasIsClustered() && indexDesc.getIsClustered());
-        builder.setIsUnique(indexDesc.hasIsUnique() && indexDesc.getIsUnique());
-        
-        indexList.add(builder.build());
+  public boolean existIndexesByTable(String databaseName, String tableName) throws CatalogException {
+    Map<String, IndexDescProto> indexByColumn = checkAndGetDatabaseNS(indexesByColumn, databaseName);
+    String simpleTableName = CatalogUtil.extractSimpleName(tableName);
+    for (IndexDescProto proto : indexByColumn.values()) {
+      if (proto.getTableIdentifier().getTableName().equals(simpleTableName)) {
+        return true;
       }
     }
-    
-    return indexList;
+    return false;
+  }
+
+  @Override
+  public List<IndexDescProto> getAllIndexes() throws CatalogException {
+    List<IndexDescProto> indexDescProtos = TUtil.newList();
+    for (Map<String,IndexDescProto> indexMap : indexes.values()) {
+      indexDescProtos.addAll(indexMap.values());
+    }
+    return indexDescProtos;
   }
 
   @Override
@@ -736,4 +752,13 @@
     return null;
   }
 
+  public static String getUnifiedNameForIndexByColumn(IndexDescProto proto) {
+    StringBuilder sb = new StringBuilder();
+    for (SortSpecProto columnSpec : proto.getKeySortSpecsList()) {
+      String[] identifiers = columnSpec.getColumn().getName().split(CatalogConstants.IDENTIFIER_DELIMITER_REGEXP);
+      sb.append(identifiers[identifiers.length-1]).append("_");
+    }
+    sb.deleteCharAt(sb.length()-1);
+    return sb.toString();
+  }
 }
diff --git a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/derby/derby.xml b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/derby/derby.xml
index e0bd469..196a4a8 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/derby/derby.xml
+++ b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/derby/derby.xml
@@ -105,16 +105,22 @@
 			<tns:Object order="11" type="table" name="INDEXES">
 				<tns:sql><![CDATA[
 				CREATE TABLE INDEXES (
-  				DB_ID INT NOT NULL REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
-  				TID INT NOT NULL REFERENCES TABLES (TID) ON DELETE CASCADE,
-  				INDEX_NAME VARCHAR(128) NOT NULL,
-  				COLUMN_NAME VARCHAR(128) NOT NULL,
-  				DATA_TYPE VARCHAR(128) NOT NULL,
-  				INDEX_TYPE CHAR(32) NOT NULL,
-  				IS_UNIQUE BOOLEAN NOT NULL,
-  				IS_CLUSTERED BOOLEAN NOT NULL,
-  				IS_ASCENDING BOOLEAN NOT NULL,
-  				CONSTRAINT C_INDEXES_PK PRIMARY KEY (DB_ID, INDEX_NAME)
+					INDEX_ID INT NOT NULL GENERATED ALWAYS AS IDENTITY (START WITH 1, INCREMENT BY 1),
+					DB_ID INT NOT NULL REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
+					TID INT NOT NULL REFERENCES TABLES (TID) ON DELETE CASCADE,
+					INDEX_NAME VARCHAR(128) NOT NULL,
+					INDEX_TYPE CHAR(32) NOT NULL,
+					PATH VARCHAR(4096),
+					COLUMN_NAMES VARCHAR(128) NOT NULL, -- array of column names
+					DATA_TYPES VARCHAR(128) NOT NULL, -- array of column types
+					ORDERS VARCHAR(128) NOT NULL, -- array of column orders
+					NULL_ORDERS VARCHAR(128) NOT NULL, -- array of null orderings
+					IS_UNIQUE BOOLEAN NOT NULL,
+					IS_CLUSTERED BOOLEAN NOT NULL,
+					FOREIGN KEY (DB_ID) REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
+					FOREIGN KEY (TID) REFERENCES TABLES (TID) ON DELETE CASCADE,
+					CONSTRAINT INDEXES_PK PRIMARY KEY (INDEX_ID),
+					CONSTRAINT C_INDEXES_UNIQ UNIQUE (DB_ID, INDEX_NAME)
 				)]]>
 				</tns:sql>
 			</tns:Object>
@@ -122,7 +128,7 @@
 				<tns:sql><![CDATA[CREATE UNIQUE INDEX idx_indexes_pk ON INDEXES (DB_ID,index_name)]]></tns:sql>
 			</tns:Object>
 			<tns:Object order="13" type="index" name="IDX_INDEXES_COLUMNS" dependsOn="INDEXES">
-				<tns:sql><![CDATA[CREATE INDEX idx_indexes_columns ON INDEXES (DB_ID,column_name)]]></tns:sql>
+				<tns:sql><![CDATA[CREATE INDEX idx_col_names ON INDEXES (DB_ID,TID,column_names)]]></tns:sql>
 			</tns:Object>
 			<tns:Object order="14" type="table" name="STATS">
 				<tns:sql><![CDATA[
diff --git a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/mariadb/mariadb.xml b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/mariadb/mariadb.xml
index 7485da1..e607baa 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/mariadb/mariadb.xml
+++ b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/mariadb/mariadb.xml
@@ -95,20 +95,21 @@
       <tns:Object order="6" type="table" name="INDEXES">
         <tns:sql><![CDATA[
         CREATE TABLE INDEXES (
+          INDEX_ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
           DB_ID INT NOT NULL,
           TID INT NOT NULL,
-          INDEX_NAME VARCHAR(128) BINARY NOT NULL,
-          COLUMN_NAME VARCHAR(128) BINARY NOT NULL,
-          DATA_TYPE VARCHAR(128) NOT NULL,
-          INDEX_TYPE CHAR(32) NOT NULL,
+          INDEX_NAME VARCHAR(128) NOT NULL,
+          INDEX_TYPE CHAR(32) BINARY NOT NULL,
+          PATH VARCHAR(4096) NOT NULL,
+          COLUMN_NAMES VARCHAR(256) NOT NULL, -- array of column names
+          DATA_TYPES VARCHAR(128) NOT NULL, -- array of column types
+          ORDERS VARCHAR(128) NOT NULL, -- array of column orders
+          NULL_ORDERS VARCHAR(128) NOT NULL, -- array of null orderings
           IS_UNIQUE BOOLEAN NOT NULL,
           IS_CLUSTERED BOOLEAN NOT NULL,
-          IS_ASCENDING BOOLEAN NOT NULL,
-          PRIMARY KEY (DB_ID, INDEX_NAME),
           FOREIGN KEY (DB_ID) REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
           FOREIGN KEY (TID) REFERENCES TABLES (TID) ON DELETE CASCADE,
-          UNIQUE INDEX IDX_DB_ID_NAME (DB_ID, INDEX_NAME),
-          INDEX IDX_TID_COLUMN_NAME (TID, COLUMN_NAME)
+          UNIQUE INDEX IDX_DB_ID_NAME (DB_ID, INDEX_NAME)
         )]]>
         </tns:sql>
       </tns:Object>
diff --git a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/mysql/mysql.xml b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/mysql/mysql.xml
index 2bde04f..809f42f 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/mysql/mysql.xml
+++ b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/mysql/mysql.xml
@@ -96,20 +96,21 @@
       <tns:Object order="6" type="table" name="INDEXES">
         <tns:sql><![CDATA[
         CREATE TABLE INDEXES (
+          INDEX_ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
           DB_ID INT NOT NULL,
           TID INT NOT NULL,
-          INDEX_NAME VARCHAR(128) BINARY NOT NULL,
-          COLUMN_NAME VARCHAR(128) BINARY NOT NULL,
-          DATA_TYPE VARCHAR(128) NOT NULL,
-          INDEX_TYPE CHAR(32) NOT NULL,
+          INDEX_NAME VARCHAR(128) NOT NULL,
+          INDEX_TYPE CHAR(32) BINARY NOT NULL,
+          PATH VARCHAR(4096) NOT NULL,
+          COLUMN_NAMES VARCHAR(256) NOT NULL, -- array of column names
+          DATA_TYPES VARCHAR(128) NOT NULL, -- array of column types
+          ORDERS VARCHAR(128) NOT NULL, -- array of column orders
+          NULL_ORDERS VARCHAR(128) NOT NULL, -- array of null orderings
           IS_UNIQUE BOOLEAN NOT NULL,
           IS_CLUSTERED BOOLEAN NOT NULL,
-          IS_ASCENDING BOOLEAN NOT NULL,
-          PRIMARY KEY (DB_ID, INDEX_NAME),
           FOREIGN KEY (DB_ID) REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
           FOREIGN KEY (TID) REFERENCES TABLES (TID) ON DELETE CASCADE,
-          UNIQUE INDEX IDX_DB_ID_NAME (DB_ID, INDEX_NAME),
-          INDEX IDX_TID_COLUMN_NAME (TID, COLUMN_NAME)
+          UNIQUE INDEX IDX_DB_ID_NAME (DB_ID, INDEX_NAME)
         )]]>
         </tns:sql>
       </tns:Object>
diff --git a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/oracle/oracle.xml b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/oracle/oracle.xml
index 2778e0c..f105c36 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/oracle/oracle.xml
+++ b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/oracle/oracle.xml
@@ -147,18 +147,21 @@
   		<tns:Object order="14" type="table" name="INDEXES">
   			<tns:sql><![CDATA[
   			CREATE TABLE INDEXES (
-					DB_ID INT NOT NULL,
-					TID INT NOT NULL,
-					INDEX_NAME VARCHAR2(128) NOT NULL,
-					COLUMN_NAME VARCHAR2(128) NOT NULL,
-					DATA_TYPE VARCHAR2(128) NOT NULL,
-					INDEX_TYPE CHAR(32) NOT NULL,
-					IS_UNIQUE CHAR NOT NULL,
-					IS_CLUSTERED CHAR NOT NULL,
-					IS_ASCENDING CHAR NOT NULL,
-					CONSTRAINT INDEXES_PKEY PRIMARY KEY (DB_ID, INDEX_NAME),
-					FOREIGN KEY (DB_ID) REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
-					FOREIGN KEY (TID) REFERENCES TABLES (TID) ON DELETE CASCADE
+  				INDEX_ID NUMBER(10) NOT NULL PRIMARY KEY,
+  				DB_ID INT NOT NULL,
+  				TID INT NOT NULL,
+  				INDEX_NAME VARCHAR2(128) NOT NULL,
+  				INDEX_TYPE CHAR(32) NOT NULL,
+  				PATH VARCHAR(4096) NOT NULL,
+  				COLUMN_NAMES VARCHAR(256) NOT NULL, -- array of column names
+  				DATA_TYPES VARCHAR(128) NOT NULL, -- array of column types
+  				ORDERS VARCHAR(128) NOT NULL, -- array of column orders
+  				NULL_ORDERS VARCHAR(128) NOT NULL, -- array of null orderings
+  				IS_UNIQUE CHAR NOT NULL,
+  				IS_CLUSTERED CHAR NOT NULL,
+  				IS_ASCENDING CHAR NOT NULL,
+  				FOREIGN KEY (DB_ID) REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
+  				FOREIGN KEY (TID) REFERENCES TABLES (TID) ON DELETE CASCADE
 				)]]>
 				</tns:sql>
   		</tns:Object>
diff --git a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/postgresql/postgresql.xml b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/postgresql/postgresql.xml
index 0051242..cab3c12 100644
--- a/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/postgresql/postgresql.xml
+++ b/tajo-catalog/tajo-catalog-server/src/main/resources/schemas/postgresql/postgresql.xml
@@ -107,18 +107,21 @@
 			<tns:Object name="INDEXES" type="table" order="10">
 				<tns:sql><![CDATA[
 				CREATE TABLE INDEXES (
-  				DB_ID INT NOT NULL,
-  				TID INT NOT NULL,
-  				INDEX_NAME VARCHAR(128) NOT NULL,
-  				COLUMN_NAME VARCHAR(128) NOT NULL,
-  				DATA_TYPE VARCHAR(128) NOT NULL,
-  				INDEX_TYPE CHAR(32) NOT NULL,
-  				IS_UNIQUE BOOLEAN NOT NULL,
-  				IS_CLUSTERED BOOLEAN NOT NULL,
-  				IS_ASCENDING BOOLEAN NOT NULL,
-  				CONSTRAINT INDEXES_PKEY PRIMARY KEY (DB_ID, INDEX_NAME),
-  				FOREIGN KEY (DB_ID) REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
-  				FOREIGN KEY (TID) REFERENCES TABLES (TID) ON DELETE CASCADE
+  					INDEX_ID SERIAL NOT NULL PRIMARY KEY,
+  					DB_ID INT NOT NULL,
+  					TID INT NOT NULL,
+  					INDEX_NAME VARCHAR(128) NOT NULL,
+  					INDEX_TYPE CHAR(32) NOT NULL,
+  					PATH VARCHAR(4096) NOT NULL,
+  					COLUMN_NAMES VARCHAR(256) NOT NULL, -- array of column names
+  					DATA_TYPES VARCHAR(128) NOT NULL, -- array of column types
+  					ORDERS VARCHAR(128) NOT NULL, -- array of column orders
+  					NULL_ORDERS VARCHAR(128) NOT NULL, -- array of null orderings
+  					IS_UNIQUE BOOLEAN NOT NULL,
+  					IS_CLUSTERED BOOLEAN NOT NULL,
+  					CONSTRAINT INDEXES_PKEY PRIMARY KEY (INDEX_ID),
+  					FOREIGN KEY (DB_ID) REFERENCES DATABASES_ (DB_ID) ON DELETE CASCADE,
+  					FOREIGN KEY (TID) REFERENCES TABLES (TID) ON DELETE CASCADE
 				)]]>
 				</tns:sql>
 			</tns:Object>
@@ -126,7 +129,7 @@
 				<tns:sql><![CDATA[CREATE UNIQUE INDEX INDEXES_IDX_DB_ID_NAME on INDEXES (DB_ID, INDEX_NAME)]]></tns:sql>
 			</tns:Object>
 			<tns:Object name="INDEXES_IDX_TID_COLUMN_NAME" type="index" order="12" dependsOn="INDEXES">
-				<tns:sql><![CDATA[CREATE INDEX INDEXES_IDX_TID_COLUMN_NAME on INDEXES (TID, COLUMN_NAME)]]></tns:sql>
+				<tns:sql><![CDATA[CREATE INDEX INDEXES_IDX_TID_COLUMN_NAME on INDEXES (DB_ID, TID, COLUMN_NAMES)]]></tns:sql>
 			</tns:Object>
 			<tns:Object name="STATS" type="table" order="13">
 				<tns:sql><![CDATA[
diff --git a/tajo-catalog/tajo-catalog-server/src/test/java/org/apache/tajo/catalog/TestCatalog.java b/tajo-catalog/tajo-catalog-server/src/test/java/org/apache/tajo/catalog/TestCatalog.java
index 40b731e..1206bfa 100644
--- a/tajo-catalog/tajo-catalog-server/src/test/java/org/apache/tajo/catalog/TestCatalog.java
+++ b/tajo-catalog/tajo-catalog-server/src/test/java/org/apache/tajo/catalog/TestCatalog.java
@@ -19,23 +19,18 @@
 package org.apache.tajo.catalog;
 
 import com.google.common.collect.Sets;
-
 import org.apache.hadoop.fs.Path;
 import org.apache.tajo.TajoConstants;
 import org.apache.tajo.catalog.dictionary.InfoSchemaMetadataDictionary;
 import org.apache.tajo.catalog.exception.CatalogException;
 import org.apache.tajo.catalog.exception.UndefinedFunctionException;
 import org.apache.tajo.catalog.partition.PartitionDesc;
-import org.apache.tajo.catalog.store.PostgreSQLStore;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
 import org.apache.tajo.catalog.proto.CatalogProtos;
 import org.apache.tajo.catalog.proto.CatalogProtos.FunctionType;
 import org.apache.tajo.catalog.proto.CatalogProtos.IndexMethod;
 import org.apache.tajo.catalog.proto.CatalogProtos.PartitionKeyProto;
-import org.apache.tajo.catalog.store.DerbyStore;
-import org.apache.tajo.catalog.store.MySQLStore;
-import org.apache.tajo.catalog.store.MariaDBStore;
-import org.apache.tajo.catalog.store.OracleStore;
+import org.apache.tajo.catalog.store.*;
 import org.apache.tajo.common.TajoDataTypes;
 import org.apache.tajo.common.TajoDataTypes.Type;
 import org.apache.tajo.conf.TajoConf;
@@ -49,6 +44,8 @@
 import org.junit.Test;
 
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.*;
 
 import static org.apache.tajo.TajoConstants.DEFAULT_DATABASE_NAME;
@@ -537,36 +534,43 @@
   static IndexDesc desc1;
   static IndexDesc desc2;
   static IndexDesc desc3;
-
-  static {
-    desc1 = new IndexDesc(
-        "idx_test", DEFAULT_DATABASE_NAME, "indexed", new Column("id", Type.INT4),
-        IndexMethod.TWO_LEVEL_BIN_TREE, true, true, true);
-
-    desc2 = new IndexDesc(
-        "idx_test2", DEFAULT_DATABASE_NAME, "indexed", new Column("score", Type.FLOAT8),
-        IndexMethod.TWO_LEVEL_BIN_TREE, false, false, false);
-
-    desc3 = new IndexDesc(
-        "idx_test", DEFAULT_DATABASE_NAME, "indexed", new Column("id", Type.INT4),
-        IndexMethod.TWO_LEVEL_BIN_TREE, true, true, true);
-  }
+  static Schema relationSchema;
 
   public static TableDesc prepareTable() throws IOException {
-    Schema schema = new Schema();
-    schema.addColumn("indexed.id", Type.INT4)
-        .addColumn("indexed.name", Type.TEXT)
-        .addColumn("indexed.age", Type.INT4)
-        .addColumn("indexed.score", Type.FLOAT8);
+    relationSchema = new Schema();
+    relationSchema.addColumn(DEFAULT_DATABASE_NAME + ".indexed.id", Type.INT4)
+        .addColumn(DEFAULT_DATABASE_NAME + ".indexed.name", Type.TEXT)
+        .addColumn(DEFAULT_DATABASE_NAME + ".indexed.age", Type.INT4)
+        .addColumn(DEFAULT_DATABASE_NAME + ".indexed.score", Type.FLOAT8);
 
     String tableName = "indexed";
 
     TableMeta meta = CatalogUtil.newTableMeta("TEXT");
     return new TableDesc(
-        CatalogUtil.buildFQName(TajoConstants.DEFAULT_DATABASE_NAME, tableName), schema, meta,
+        CatalogUtil.buildFQName(TajoConstants.DEFAULT_DATABASE_NAME, tableName), relationSchema, meta,
         new Path(CommonTestingUtil.getTestDir(), "indexed").toUri());
   }
 
+  public static void prepareIndexDescs() throws IOException, URISyntaxException {
+    SortSpec[] colSpecs1 = new SortSpec[1];
+    colSpecs1[0] = new SortSpec(new Column("default.indexed.id", Type.INT4), true, true);
+    desc1 = new IndexDesc(DEFAULT_DATABASE_NAME, "indexed",
+        "idx_test", new URI("idx_test"), colSpecs1,
+        IndexMethod.TWO_LEVEL_BIN_TREE, true, true, relationSchema);
+
+    SortSpec[] colSpecs2 = new SortSpec[1];
+    colSpecs2[0] = new SortSpec(new Column("default.indexed.score", Type.FLOAT8), false, false);
+    desc2 = new IndexDesc(DEFAULT_DATABASE_NAME, "indexed",
+        "idx_test2", new URI("idx_test2"), colSpecs2,
+        IndexMethod.TWO_LEVEL_BIN_TREE, false, false, relationSchema);
+
+    SortSpec[] colSpecs3 = new SortSpec[1];
+    colSpecs3[0] = new SortSpec(new Column("default.indexed.id", Type.INT4), true, false);
+    desc3 = new IndexDesc(DEFAULT_DATABASE_NAME, "indexed",
+        "idx_test", new URI("idx_test"), colSpecs3,
+        IndexMethod.TWO_LEVEL_BIN_TREE, true, true, relationSchema);
+  }
+
   @Test
   public void testCreateSameTables() throws IOException {
     assertTrue(catalog.createDatabase("tmpdb3", TajoConstants.DEFAULT_TABLESPACE_NAME));
@@ -602,25 +606,34 @@
 	@Test
 	public void testAddAndDelIndex() throws Exception {
 	  TableDesc desc = prepareTable();
+    prepareIndexDescs();
 	  assertTrue(catalog.createTable(desc));
 	  
-	  assertFalse(catalog.existIndexByName("db1", desc1.getIndexName()));
-	  assertFalse(catalog.existIndexByColumn(DEFAULT_DATABASE_NAME, "indexed", "id"));
+	  assertFalse(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc1.getName()));
+	  assertFalse(catalog.existIndexByColumnNames(DEFAULT_DATABASE_NAME, "indexed", new String[]{"id"}));
 	  catalog.createIndex(desc1);
-	  assertTrue(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc1.getIndexName()));
-	  assertTrue(catalog.existIndexByColumn(DEFAULT_DATABASE_NAME, "indexed", "id"));
+	  assertTrue(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc1.getName()));
+	  assertTrue(catalog.existIndexByColumnNames(DEFAULT_DATABASE_NAME, "indexed", new String[]{"id"}));
 
 
-	  assertFalse(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc2.getIndexName()));
-	  assertFalse(catalog.existIndexByColumn(DEFAULT_DATABASE_NAME, "indexed", "score"));
+	  assertFalse(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc2.getName()));
+	  assertFalse(catalog.existIndexByColumnNames(DEFAULT_DATABASE_NAME, "indexed", new String[]{"score"}));
 	  catalog.createIndex(desc2);
-	  assertTrue(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc2.getIndexName()));
-	  assertTrue(catalog.existIndexByColumn(DEFAULT_DATABASE_NAME, "indexed", "score"));
+	  assertTrue(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc2.getName()));
+	  assertTrue(catalog.existIndexByColumnNames(DEFAULT_DATABASE_NAME, "indexed", new String[]{"score"}));
+
+    Set<IndexDesc> indexDescs = TUtil.newHashSet();
+    indexDescs.add(desc1);
+    indexDescs.add(desc2);
+    indexDescs.add(desc3);
+    for (IndexDesc index : catalog.getAllIndexesByTable(DEFAULT_DATABASE_NAME, "indexed")) {
+      assertTrue(indexDescs.contains(index));
+    }
 	  
-	  catalog.dropIndex(DEFAULT_DATABASE_NAME, desc1.getIndexName());
-	  assertFalse(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc1.getIndexName()));
-	  catalog.dropIndex(DEFAULT_DATABASE_NAME, desc2.getIndexName());
-	  assertFalse(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc2.getIndexName()));
+	  catalog.dropIndex(DEFAULT_DATABASE_NAME, desc1.getName());
+	  assertFalse(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc1.getName()));
+	  catalog.dropIndex(DEFAULT_DATABASE_NAME, desc2.getName());
+	  assertFalse(catalog.existIndexByName(DEFAULT_DATABASE_NAME, desc2.getName()));
 	  
 	  catalog.dropTable(desc.getName());
     assertFalse(catalog.existsTable(desc.getName()));
diff --git a/tajo-cli/src/main/java/org/apache/tajo/cli/tools/TajoDump.java b/tajo-cli/src/main/java/org/apache/tajo/cli/tools/TajoDump.java
index 7fefc5a..20665e8 100644
--- a/tajo-cli/src/main/java/org/apache/tajo/cli/tools/TajoDump.java
+++ b/tajo-cli/src/main/java/org/apache/tajo/cli/tools/TajoDump.java
@@ -22,10 +22,8 @@
 
 import org.apache.commons.cli.*;
 import org.apache.tajo.auth.UserRoleInfo;
-import org.apache.tajo.catalog.CatalogConstants;
-import org.apache.tajo.catalog.CatalogUtil;
-import org.apache.tajo.catalog.DDLBuilder;
-import org.apache.tajo.catalog.TableDesc;
+import org.apache.tajo.catalog.*;
+import org.apache.tajo.catalog.proto.CatalogProtos;
 import org.apache.tajo.catalog.proto.CatalogProtos.StoreType;
 import org.apache.tajo.client.TajoClient;
 import org.apache.tajo.client.TajoClientImpl;
@@ -186,6 +184,11 @@
         } else {
           writer.write(DDLBuilder.buildDDLForBaseTable(table));
         }
+
+        List<CatalogProtos.IndexDescProto> indexeProtos = client.getIndexes(tableName);
+        for (CatalogProtos.IndexDescProto eachIndexProto : indexeProtos) {
+          writer.write(DDLBuilder.buildDDLForIndex(new IndexDesc(eachIndexProto)));
+        }
         writer.write("\n\n");
       } catch (Exception e) {
         // dump for each table can throw any exception. We need to skip the exception case.
diff --git a/tajo-cli/src/main/java/org/apache/tajo/cli/tsql/commands/DescTableCommand.java b/tajo-cli/src/main/java/org/apache/tajo/cli/tsql/commands/DescTableCommand.java
index 6b2905a..4b7006d 100644
--- a/tajo-cli/src/main/java/org/apache/tajo/cli/tsql/commands/DescTableCommand.java
+++ b/tajo-cli/src/main/java/org/apache/tajo/cli/tsql/commands/DescTableCommand.java
@@ -21,9 +21,12 @@
 import org.apache.commons.lang.CharUtils;
 import org.apache.commons.lang.StringEscapeUtils;
 import org.apache.tajo.TajoConstants;
+import org.apache.tajo.catalog.CatalogUtil;
 import org.apache.tajo.catalog.Column;
 import org.apache.tajo.catalog.TableDesc;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
+import org.apache.tajo.catalog.proto.CatalogProtos.IndexDescProto;
+import org.apache.tajo.catalog.proto.CatalogProtos.SortSpecProto;
 import org.apache.tajo.cli.tsql.TajoCli;
 import org.apache.tajo.util.FileUtil;
 import org.apache.tajo.util.StringUtils;
@@ -51,6 +54,22 @@
         context.getOutput().println("Did not find any relation named \"" + tableName + "\"");
       } else {
         context.getOutput().println(toFormattedString(desc));
+        // If there exists any indexes for the table, print index information
+        if (client.hasIndexes(tableName)) {
+          StringBuilder sb = new StringBuilder();
+          sb.append("Indexes:\n");
+          for (IndexDescProto index : client.getIndexes(tableName)) {
+            sb.append("\"").append(index.getIndexName()).append("\" ");
+            sb.append(index.getIndexMethod()).append(" (");
+            for (SortSpecProto key : index.getKeySortSpecsList()) {
+              sb.append(CatalogUtil.extractSimpleName(key.getColumn().getName()));
+              sb.append(key.getAscending() ? " ASC" : " DESC");
+              sb.append(key.getNullFirst() ? " NULLS FIRST, " : " NULLS LAST, ");
+            }
+            sb.delete(sb.length()-2, sb.length()-1).append(")\n");
+          }
+          context.getOutput().println(sb.toString());
+        }
       }
     } else if (cmd.length == 1) {
       List<String> tableList = client.getTableList(null);
diff --git a/tajo-client/src/main/java/org/apache/tajo/client/CatalogAdminClient.java b/tajo-client/src/main/java/org/apache/tajo/client/CatalogAdminClient.java
index c020ef5..5e923e0 100644
--- a/tajo-client/src/main/java/org/apache/tajo/client/CatalogAdminClient.java
+++ b/tajo-client/src/main/java/org/apache/tajo/client/CatalogAdminClient.java
@@ -24,6 +24,7 @@
 import org.apache.tajo.catalog.TableMeta;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
 import org.apache.tajo.catalog.proto.CatalogProtos;
+import org.apache.tajo.catalog.proto.CatalogProtos.IndexDescProto;
 
 import java.io.Closeable;
 import java.net.URI;
@@ -135,4 +136,18 @@
   TableDesc getTableDesc(final String tableName) throws SQLException;
 
   List<CatalogProtos.FunctionDescProto> getFunctions(final String functionName) throws SQLException;
+
+  IndexDescProto getIndex(final String indexName) throws SQLException;
+
+  boolean existIndex(final String indexName) throws SQLException;
+
+  List<IndexDescProto> getIndexes(final String tableName) throws SQLException;
+
+  boolean hasIndexes(final String tableName) throws SQLException;
+
+  IndexDescProto getIndex(final String tableName, final String[] columnNames) throws SQLException;
+
+  boolean existIndex(final String tableName, final String[] columnName) throws SQLException;
+
+  boolean dropIndex(final String indexName) throws SQLException;
 }
diff --git a/tajo-client/src/main/java/org/apache/tajo/client/CatalogAdminClientImpl.java b/tajo-client/src/main/java/org/apache/tajo/client/CatalogAdminClientImpl.java
index e73a032..8745d19 100644
--- a/tajo-client/src/main/java/org/apache/tajo/client/CatalogAdminClientImpl.java
+++ b/tajo-client/src/main/java/org/apache/tajo/client/CatalogAdminClientImpl.java
@@ -26,8 +26,10 @@
 import org.apache.tajo.catalog.TableMeta;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
 import org.apache.tajo.catalog.proto.CatalogProtos;
-import org.apache.tajo.catalog.proto.CatalogProtos.FunctionDescProto;
-import org.apache.tajo.catalog.proto.CatalogProtos.TableResponse;
+import org.apache.tajo.catalog.proto.CatalogProtos.*;
+import org.apache.tajo.error.Errors.ResultCode;
+import org.apache.tajo.ipc.ClientProtos;
+import org.apache.tajo.ipc.ClientProtos.*;
 import org.apache.tajo.exception.SQLExceptionUtil;
 import org.apache.tajo.ipc.ClientProtos;
 import org.apache.tajo.ipc.ClientProtos.DropTableRequest;
@@ -220,6 +222,110 @@
   }
 
   @Override
+  public IndexDescProto getIndex(final String indexName) throws SQLException {
+    final BlockingInterface stub = conn.getTMStub();
+
+    IndexResponse res;
+    try {
+      res = stub.getIndexWithName(null, conn.getSessionedString(indexName));
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+
+    throwIfError(res.getState());
+    return res.getIndexDesc();
+  }
+
+  @Override
+  public boolean existIndex(final String indexName) throws SQLException {
+    final BlockingInterface stub = conn.getTMStub();
+
+    try {
+      return isSuccess(stub.existIndexWithName(null, conn.getSessionedString(indexName)));
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public List<IndexDescProto> getIndexes(final String tableName) throws SQLException {
+    final BlockingInterface stub = conn.getTMStub();
+
+    IndexListResponse response;
+    try {
+      response = stub.getIndexesForTable(null,
+          conn.getSessionedString(tableName));
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+
+    throwIfError(response.getState());
+    return response.getIndexDescList();
+  }
+
+  @Override
+  public boolean hasIndexes(final String tableName) throws SQLException {
+    final BlockingInterface stub = conn.getTMStub();
+
+    try {
+      return isSuccess(stub.existIndexesForTable(null, conn.getSessionedString(tableName)));
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public IndexDescProto getIndex(final String tableName, final String[] columnNames) throws SQLException {
+    final BlockingInterface stub = conn.getTMStub();
+
+    GetIndexWithColumnsRequest.Builder builder = GetIndexWithColumnsRequest.newBuilder();
+    builder.setSessionId(conn.sessionId);
+    builder.setTableName(tableName);
+    for (String eachColumnName : columnNames) {
+      builder.addColumnNames(eachColumnName);
+    }
+
+    IndexResponse response;
+    try {
+      response = stub.getIndexWithColumns(null, builder.build());
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+
+    throwIfError(response.getState());
+    return response.getIndexDesc();
+  }
+
+  @Override
+  public boolean existIndex(final String tableName, final String[] columnName) throws SQLException {
+    final BlockingInterface stub = conn.getTMStub();
+
+    GetIndexWithColumnsRequest.Builder builder = GetIndexWithColumnsRequest.newBuilder();
+    builder.setSessionId(conn.sessionId);
+    builder.setTableName(tableName);
+    for (String eachColumnName : columnName) {
+      builder.addColumnNames(eachColumnName);
+    }
+
+    try {
+      return isSuccess(stub.existIndexWithColumns(null, builder.build()));
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
+  public boolean dropIndex(final String indexName) throws SQLException {
+    final BlockingInterface stub = conn.getTMStub();
+
+    try {
+      return isSuccess(stub.dropIndex(null, conn.getSessionedString(indexName)));
+    } catch (ServiceException e) {
+      throw new RuntimeException(e);
+    }
+  }
+
+  @Override
   public void close() throws IOException {
   }
 }
diff --git a/tajo-client/src/main/java/org/apache/tajo/client/QueryClientImpl.java b/tajo-client/src/main/java/org/apache/tajo/client/QueryClientImpl.java
index 80a49c2..0a02fcb 100644
--- a/tajo-client/src/main/java/org/apache/tajo/client/QueryClientImpl.java
+++ b/tajo-client/src/main/java/org/apache/tajo/client/QueryClientImpl.java
@@ -161,6 +161,7 @@
     }
 
     return response;
+
   }
 
   @Override
@@ -324,7 +325,6 @@
         .setFetchRowNum(fetchRowNum)
         .build();
 
-
     GetQueryResultDataResponse response;
     try {
       response = stub.getQueryResultData(null, request);
@@ -470,7 +470,7 @@
   public int getMaxRows() {
   	return this.maxRows;
   }
-  
+
   public QueryInfoProto getQueryInfo(final QueryId queryId) throws SQLException {
 
     final BlockingInterface stub = conn.getTMStub();
diff --git a/tajo-client/src/main/java/org/apache/tajo/client/SessionConnection.java b/tajo-client/src/main/java/org/apache/tajo/client/SessionConnection.java
index f875335..4900188 100644
--- a/tajo-client/src/main/java/org/apache/tajo/client/SessionConnection.java
+++ b/tajo-client/src/main/java/org/apache/tajo/client/SessionConnection.java
@@ -374,7 +374,6 @@
         builder.setBaseDatabaseName(baseDatabase);
       }
 
-
       CreateSessionResponse response = null;
 
       try {
diff --git a/tajo-client/src/main/java/org/apache/tajo/client/TajoClientImpl.java b/tajo-client/src/main/java/org/apache/tajo/client/TajoClientImpl.java
index c81fafc..b66d451 100644
--- a/tajo-client/src/main/java/org/apache/tajo/client/TajoClientImpl.java
+++ b/tajo-client/src/main/java/org/apache/tajo/client/TajoClientImpl.java
@@ -28,6 +28,7 @@
 import org.apache.tajo.catalog.TableMeta;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
 import org.apache.tajo.catalog.proto.CatalogProtos;
+import org.apache.tajo.catalog.proto.CatalogProtos.IndexDescProto;
 import org.apache.tajo.ipc.ClientProtos.*;
 import org.apache.tajo.jdbc.TajoMemoryResultSet;
 import org.apache.tajo.service.ServiceTracker;
@@ -227,4 +228,39 @@
   public List<CatalogProtos.FunctionDescProto> getFunctions(final String functionName) throws SQLException {
     return catalogClient.getFunctions(functionName);
   }
+
+  @Override
+  public IndexDescProto getIndex(String indexName) throws SQLException {
+    return catalogClient.getIndex(indexName);
+  }
+
+  @Override
+  public boolean existIndex(String indexName) throws SQLException {
+    return catalogClient.existIndex(indexName);
+  }
+
+  @Override
+  public List<IndexDescProto> getIndexes(String tableName) throws SQLException {
+    return catalogClient.getIndexes(tableName);
+  }
+
+  @Override
+  public boolean hasIndexes(String tableName) throws SQLException {
+    return catalogClient.hasIndexes(tableName);
+  }
+
+  @Override
+  public IndexDescProto getIndex(String tableName, String[] columnNames) throws SQLException {
+    return catalogClient.getIndex(tableName, columnNames);
+  }
+
+  @Override
+  public boolean existIndex(String tableName, String[] columnName) throws SQLException {
+    return catalogClient.existIndex(tableName, columnName);
+  }
+
+  @Override
+  public boolean dropIndex(String indexName) throws SQLException {
+    return catalogClient.dropIndex(indexName);
+  }
 }
diff --git a/tajo-client/src/main/proto/ClientProtos.proto b/tajo-client/src/main/proto/ClientProtos.proto
index 021cfe7..e77923e 100644
--- a/tajo-client/src/main/proto/ClientProtos.proto
+++ b/tajo-client/src/main/proto/ClientProtos.proto
@@ -253,3 +253,13 @@
   optional QueryInfoProto queryInfo = 2;
 }
 
+message CreateIndexResponse {
+  required ReturnState result = 1;
+  optional IndexDescProto indexDesc = 2;
+}
+
+message GetIndexWithColumnsRequest {
+  required SessionIdProto sessionId = 1;
+  required string tableName = 2;
+  repeated string columnNames = 3;
+}
diff --git a/tajo-client/src/main/proto/TajoMasterClientProtocol.proto b/tajo-client/src/main/proto/TajoMasterClientProtocol.proto
index 57bb2db..78f0f30 100644
--- a/tajo-client/src/main/proto/TajoMasterClientProtocol.proto
+++ b/tajo-client/src/main/proto/TajoMasterClientProtocol.proto
@@ -70,4 +70,13 @@
   rpc getTableList(SessionedStringProto) returns (StringListResponse);
   rpc getTableDesc(SessionedStringProto) returns (TableResponse);
   rpc getFunctionList(SessionedStringProto) returns (FunctionListResponse);
+
+  // Index Management APIs
+  rpc getIndexWithName(SessionedStringProto) returns (IndexResponse);
+  rpc existIndexWithName(SessionedStringProto) returns (ReturnState);
+  rpc getIndexesForTable(SessionedStringProto) returns (IndexListResponse);
+  rpc existIndexesForTable(SessionedStringProto) returns (ReturnState);
+  rpc getIndexWithColumns(GetIndexWithColumnsRequest) returns (IndexResponse);
+  rpc existIndexWithColumns(GetIndexWithColumnsRequest) returns (ReturnState);
+  rpc dropIndex(SessionedStringProto) returns (ReturnState);
 }
diff --git a/tajo-common/src/main/java/org/apache/tajo/OverridableConf.java b/tajo-common/src/main/java/org/apache/tajo/OverridableConf.java
index c22f054..0062c43 100644
--- a/tajo-common/src/main/java/org/apache/tajo/OverridableConf.java
+++ b/tajo-common/src/main/java/org/apache/tajo/OverridableConf.java
@@ -178,7 +178,7 @@
 
   @Override
   public float getFloat(ConfigKey key) {
-    return getLong(key, null);
+    return getFloat(key, null);
   }
 
   public void put(ConfigKey key, String val) {
diff --git a/tajo-common/src/main/java/org/apache/tajo/SessionVars.java b/tajo-common/src/main/java/org/apache/tajo/SessionVars.java
index 832a5b4..6c7d075 100644
--- a/tajo-common/src/main/java/org/apache/tajo/SessionVars.java
+++ b/tajo-common/src/main/java/org/apache/tajo/SessionVars.java
@@ -126,6 +126,11 @@
   NULL_CHAR(ConfVars.$TEXT_NULL, "null char of text file output", DEFAULT),
   CODEGEN(ConfVars.$CODEGEN, "Runtime code generation enabled (experiment)", DEFAULT),
 
+  // for index
+  INDEX_ENABLED(ConfVars.$INDEX_ENABLED, "index scan enabled", DEFAULT),
+  INDEX_SELECTIVITY_THRESHOLD(ConfVars.$INDEX_SELECTIVITY_THRESHOLD, "the selectivity threshold for index scan", DEFAULT),
+
+  // for partition overwrite
   PARTITION_NO_RESULT_OVERWRITE_ENABLED(ConfVars.$PARTITION_NO_RESULT_OVERWRITE_ENABLED,
     "If True, a partitioned table is overwritten even if a sub query leads to no result. "
     + "Otherwise, the table data will be kept if there is no result", DEFAULT),
diff --git a/tajo-common/src/main/java/org/apache/tajo/conf/TajoConf.java b/tajo-common/src/main/java/org/apache/tajo/conf/TajoConf.java
index 910d6bc..e9336f3 100644
--- a/tajo-common/src/main/java/org/apache/tajo/conf/TajoConf.java
+++ b/tajo-common/src/main/java/org/apache/tajo/conf/TajoConf.java
@@ -330,6 +330,10 @@
     $MAX_OUTPUT_FILE_SIZE("tajo.query.max-outfile-size-mb", 0), // zero means infinite
     $CODEGEN("tajo.executor.codegen.enabled", false), // Runtime code generation (todo this is broken)
 
+    // for index
+    $INDEX_ENABLED("tajo.query.index.enabled", false),
+    $INDEX_SELECTIVITY_THRESHOLD("tajo.query.index.selectivity.threshold", 0.05f),
+
     // Client -----------------------------------------------------------------
     $CLIENT_SESSION_EXPIRY_TIME("tajo.client.session.expiry-time-sec", 3600), // default time is one hour.
 
@@ -515,7 +519,7 @@
   }
 
   public static long getLongVar(Configuration conf, ConfVars var) {
-    assert (var.valClass == Long.class || var.valClass == Integer.class);
+    assert (var.valClass == Long.class || var.valClass == Integer.class || var.valClass == Float.class);
     if (var.valClass == Integer.class) {
       return conf.getInt(var.varname, var.defaultIntVal);
     } else {
diff --git a/tajo-common/src/main/java/org/apache/tajo/exception/ReturnStateUtil.java b/tajo-common/src/main/java/org/apache/tajo/exception/ReturnStateUtil.java
index 862889e..c62c647 100644
--- a/tajo-common/src/main/java/org/apache/tajo/exception/ReturnStateUtil.java
+++ b/tajo-common/src/main/java/org/apache/tajo/exception/ReturnStateUtil.java
@@ -30,8 +30,10 @@
 import org.apache.tajo.rpc.protocolrecords.PrimitiveProtos;
 import org.apache.tajo.rpc.protocolrecords.PrimitiveProtos.ReturnState;
 import org.apache.tajo.rpc.protocolrecords.PrimitiveProtos.StringListResponse;
+import org.apache.tajo.util.StringUtils;
 
 import java.util.Collection;
+import java.util.List;
 
 public class ReturnStateUtil {
 
@@ -170,8 +172,13 @@
     return returnError(ResultCode.UNDEFINED_PARTITION_METHOD, tbName);
   }
 
-  public static ReturnState errUndefinedIndex(String tbName, String columnName) {
-    return returnError(ResultCode.UNDEFINED_INDEX, tbName, columnName);
+  public static ReturnState errUndefinedIndex(String tbName) {
+    return returnError(ResultCode.UNDEFINED_INDEX, tbName);
+  }
+
+  public static ReturnState errUndefinedIndex(String tbName, List<String> columnNameList) {
+    String columnNames = StringUtils.join(columnNameList, ",");
+    return returnError(ResultCode.UNDEFINED_INDEX, tbName, columnNames);
   }
 
   public static ReturnState errUndefinedIndexName(String indexName) {
diff --git a/tajo-common/src/main/java/org/apache/tajo/util/TUtil.java b/tajo-common/src/main/java/org/apache/tajo/util/TUtil.java
index 66e8acc..f24be61 100644
--- a/tajo-common/src/main/java/org/apache/tajo/util/TUtil.java
+++ b/tajo-common/src/main/java/org/apache/tajo/util/TUtil.java
@@ -21,6 +21,8 @@
 import com.google.common.base.Objects;
 
 import java.lang.reflect.Array;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -268,6 +270,14 @@
     return element.getClassName() + ":" + element.getMethodName() + "(" + element.getLineNumber() +")";
   }
 
+  public static URI stringToURI(String str) {
+    try {
+      return new URI(str);
+    } catch (URISyntaxException e) {
+      throw new RuntimeException("Cannot convert " + str + " to the URI type", e);
+    }
+  }
+
   public static <T> T checkTypeAndGet(Object instance, Class<T> type) {
     if (!type.isInstance(instance)) {
       throw new IllegalArgumentException(instance.getClass().getSimpleName()
diff --git a/tajo-core/src/main/antlr4/org/apache/tajo/engine/parser/SQLParser.g4 b/tajo-core/src/main/antlr4/org/apache/tajo/engine/parser/SQLParser.g4
index 1137b30..b07fb8f 100644
--- a/tajo-core/src/main/antlr4/org/apache/tajo/engine/parser/SQLParser.g4
+++ b/tajo-core/src/main/antlr4/org/apache/tajo/engine/parser/SQLParser.g4
@@ -77,8 +77,17 @@
   ;
 
 index_statement
-  : CREATE (u=UNIQUE)? INDEX n=identifier ON t=table_name (m=method_specifier)?
-    LEFT_PAREN s=sort_specifier_list RIGHT_PAREN p=param_clause?
+  : create_index_statement
+  | drop_index_statement
+  ;
+
+create_index_statement
+  : CREATE (u=UNIQUE)? INDEX index_name = identifier ON table_name (method_specifier)?
+    LEFT_PAREN sort_specifier_list RIGHT_PAREN param_clause? (where_clause)? (LOCATION path=Character_String_Literal)?
+  ;
+
+drop_index_statement
+  : DROP INDEX index_name = identifier
   ;
 
 database_definition
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/ExecutorPreCompiler.java b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/ExecutorPreCompiler.java
index 87d33de..fbd88ad 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/codegen/ExecutorPreCompiler.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/codegen/ExecutorPreCompiler.java
@@ -214,4 +214,11 @@
 
     return node;
   }
+
+  @Override
+  public LogicalNode visitIndexScan(CompilationContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                    IndexScanNode node, Stack<LogicalNode> stack) throws TajoException {
+    visitScan(context, plan, block, node, stack);
+    return node;
+  }
 }
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/parser/SQLAnalyzer.java b/tajo-core/src/main/java/org/apache/tajo/engine/parser/SQLAnalyzer.java
index d99a5cf..c50d5be 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/parser/SQLAnalyzer.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/parser/SQLAnalyzer.java
@@ -27,7 +27,9 @@
 import org.apache.tajo.SessionVars;
 import org.apache.tajo.algebra.*;
 import org.apache.tajo.algebra.Aggregation.GroupType;
+import org.apache.tajo.algebra.CreateIndex.IndexMethodSpec;
 import org.apache.tajo.algebra.LiteralValue.LiteralType;
+import org.apache.tajo.algebra.Sort.SortSpec;
 import org.apache.tajo.engine.parser.SQLParser.*;
 import org.apache.tajo.storage.StorageConstants;
 import org.apache.tajo.util.StringUtils;
@@ -1221,6 +1223,51 @@
   }
 
   @Override
+  public Expr visitCreate_index_statement(SQLParser.Create_index_statementContext ctx) {
+    String indexName = ctx.index_name.getText();
+    String tableName = ctx.table_name().getText();
+    Relation relation = new Relation(tableName);
+    SortSpec[] sortSpecs = buildSortSpecs(ctx.sort_specifier_list());
+    NamedExpr[] targets = new NamedExpr[sortSpecs.length];
+    Projection projection = new Projection();
+    int i = 0;
+    for (SortSpec sortSpec : sortSpecs) {
+      targets[i++] = new NamedExpr(sortSpec.getKey());
+    }
+    projection.setNamedExprs(targets);
+    projection.setChild(relation);
+
+    CreateIndex createIndex = new CreateIndex(indexName, sortSpecs);
+    if (checkIfExist(ctx.UNIQUE())) {
+      createIndex.setUnique(true);
+    }
+    if (checkIfExist(ctx.method_specifier())) {
+      String methodName = ctx.method_specifier().identifier().getText();
+      createIndex.setMethodSpec(new IndexMethodSpec(methodName));
+    }
+    if (checkIfExist(ctx.param_clause())) {
+      Map<String, String> params = getParams(ctx.param_clause());
+      createIndex.setParams(params);
+    }
+    if (checkIfExist(ctx.where_clause())) {
+      Selection selection = visitWhere_clause(ctx.where_clause());
+      selection.setChild(relation);
+      projection.setChild(selection);
+    }
+    if (checkIfExist(ctx.LOCATION())) {
+      createIndex.setIndexPath(stripQuote(ctx.path.getText()));
+    }
+    createIndex.setChild(projection);
+    return createIndex;
+  }
+
+  @Override
+  public Expr visitDrop_index_statement(SQLParser.Drop_index_statementContext ctx) {
+    String indexName = ctx.identifier().getText();
+    return new DropIndex(indexName);
+  }
+
+  @Override
   public Expr visitDatabase_definition(@NotNull SQLParser.Database_definitionContext ctx) {
     return new CreateDatabase(ctx.identifier().getText(), null, checkIfExist(ctx.if_not_exists()));
   }
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/PhysicalPlannerImpl.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/PhysicalPlannerImpl.java
index 377aebe..ef3d039 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/PhysicalPlannerImpl.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/PhysicalPlannerImpl.java
@@ -29,7 +29,6 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.fs.Path;
 import org.apache.tajo.SessionVars;
-import org.apache.tajo.catalog.CatalogUtil;
 import org.apache.tajo.catalog.Column;
 import org.apache.tajo.catalog.SortSpec;
 import org.apache.tajo.catalog.proto.CatalogProtos;
@@ -48,12 +47,13 @@
 import org.apache.tajo.plan.logical.*;
 import org.apache.tajo.plan.serder.LogicalNodeDeserializer;
 import org.apache.tajo.plan.util.PlannerUtil;
-import org.apache.tajo.storage.*;
+import org.apache.tajo.storage.FileTablespace;
+import org.apache.tajo.storage.StorageConstants;
+import org.apache.tajo.storage.TablespaceManager;
 import org.apache.tajo.storage.fragment.FileFragment;
 import org.apache.tajo.storage.fragment.Fragment;
 import org.apache.tajo.storage.fragment.FragmentConvertor;
 import org.apache.tajo.util.FileUtil;
-import org.apache.tajo.util.IndexUtil;
 import org.apache.tajo.util.StringUtils;
 import org.apache.tajo.util.TUtil;
 import org.apache.tajo.worker.TaskAttemptContext;
@@ -235,11 +235,18 @@
         return new LimitExec(ctx, limitNode.getInSchema(),
             limitNode.getOutSchema(), leftExec, limitNode);
 
-      case BST_INDEX_SCAN:
+      case INDEX_SCAN:
         IndexScanNode indexScanNode = (IndexScanNode) logicalNode;
         leftExec = createIndexScanExec(ctx, indexScanNode);
         return leftExec;
 
+      case CREATE_INDEX:
+        CreateIndexNode createIndexNode = (CreateIndexNode) logicalNode;
+        stack.push(createIndexNode);
+        leftExec = createPlanRecursive(ctx, createIndexNode.getChild(), stack);
+        stack.pop();
+        return new StoreIndexExec(ctx, createIndexNode, leftExec);
+
       default:
         return null;
     }
@@ -1185,21 +1192,10 @@
     Preconditions.checkNotNull(ctx.getTable(annotation.getCanonicalName()),
         "Error: There is no table matched to %s", annotation.getCanonicalName());
 
-    FragmentProto [] fragmentProtos = ctx.getTables(annotation.getTableName());
-    List<FileFragment> fragments =
-        FragmentConvertor.convert(ctx.getConf(), fragmentProtos);
-
-    String indexName = IndexUtil.getIndexNameOfFrag(fragments.get(0), annotation.getSortKeys());
-    FileTablespace sm = (FileTablespace) TablespaceManager.get(fragments.get(0).getPath().toUri()).get();
-    String dbName = CatalogUtil.extractQualifier(annotation.getTableName());
-    String simpleName = CatalogUtil.extractSimpleName(annotation.getTableName());
-    Path indexPath = new Path(new Path(sm.getTableUri(dbName, simpleName)), "index");
-
-    TupleComparator comp = new BaseTupleComparator(annotation.getKeySchema(),
-        annotation.getSortKeys());
-    return new BSTIndexScanExec(ctx, annotation, fragments.get(0), new Path(indexPath, indexName),
-        annotation.getKeySchema(), comp, annotation.getDatum());
-
+    FragmentProto [] fragments = ctx.getTables(annotation.getTableName());
+    Preconditions.checkState(fragments.length == 1);
+    return new BSTIndexScanExec(ctx, annotation, fragments[0], annotation.getIndexPath(),
+        annotation.getKeySchema(), annotation.getPredicates());
   }
 
   public static EnforceProperty getAlgorithmEnforceProperty(Enforcer enforcer, LogicalNode node) {
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/GlobalPlanner.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/GlobalPlanner.java
index d9712c9..67f782a 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/GlobalPlanner.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/global/GlobalPlanner.java
@@ -1362,6 +1362,15 @@
     }
 
     @Override
+    public LogicalNode visitIndexScan(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                      IndexScanNode node, Stack<LogicalNode> stack) throws TajoException {
+      ExecutionBlock newBlock = context.plan.newExecutionBlock();
+      newBlock.setPlan(node);
+      context.execBlockMap.put(node.getPID(), newBlock);
+      return node;
+    }
+
+    @Override
     public LogicalNode visitPartitionedTableScan(GlobalPlanContext context, LogicalPlan plan,
                                                  LogicalPlan.QueryBlock block, PartitionedTableScanNode node,
                                                  Stack<LogicalNode> stack)throws TajoException {
@@ -1407,5 +1416,19 @@
 
       return node;
     }
+
+    @Override
+    public LogicalNode visitCreateIndex(GlobalPlanContext context, LogicalPlan plan, LogicalPlan.QueryBlock queryBlock,
+                                        CreateIndexNode node, Stack<LogicalNode> stack) throws TajoException {
+      LogicalNode child = super.visitCreateIndex(context, plan, queryBlock, node, stack);
+
+      // Don't separate execution block. CreateIndex is pushed to the first execution block.
+      ExecutionBlock childBlock = context.execBlockMap.remove(child.getPID());
+      node.setChild(childBlock.getPlan());
+      childBlock.setPlan(node);
+      context.execBlockMap.put(node.getPID(), childBlock);
+
+      return node;
+    }
   }
 }
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BSTIndexScanExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BSTIndexScanExec.java
index 28622d7..a59960f 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BSTIndexScanExec.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/BSTIndexScanExec.java
@@ -18,23 +18,32 @@
 
 package org.apache.tajo.engine.planner.physical;
 
-import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.io.IOUtils;
+import org.apache.tajo.catalog.Column;
 import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.catalog.SortSpec;
+import org.apache.tajo.catalog.proto.CatalogProtos;
+import org.apache.tajo.catalog.statistics.TableStats;
 import org.apache.tajo.datum.Datum;
 import org.apache.tajo.engine.planner.Projector;
+import org.apache.tajo.plan.Target;
 import org.apache.tajo.plan.expr.EvalNode;
-import org.apache.tajo.plan.logical.ScanNode;
+import org.apache.tajo.plan.expr.EvalTreeUtil;
+import org.apache.tajo.plan.logical.IndexScanNode;
+import org.apache.tajo.plan.rewrite.rules.IndexScanInfo.SimplePredicate;
 import org.apache.tajo.storage.*;
-import org.apache.tajo.storage.fragment.FileFragment;
 import org.apache.tajo.storage.index.bst.BSTIndex;
+import org.apache.tajo.util.TUtil;
 import org.apache.tajo.worker.TaskAttemptContext;
 
 import java.io.IOException;
+import java.net.URI;
+import java.util.HashSet;
+import java.util.Set;
 
 public class BSTIndexScanExec extends PhysicalExec {
-  private ScanNode scanNode;
+  private IndexScanNode plan;
   private SeekableScanner fileScanner;
   
   private EvalNode qual;
@@ -48,31 +57,122 @@
 
   private Tuple indexLookupKey;
 
-  public BSTIndexScanExec(TaskAttemptContext context, ScanNode scanNode ,
-       FileFragment fragment, Path fileName , Schema keySchema,
-       TupleComparator comparator , Datum[] datum) throws IOException {
-    super(context, scanNode.getInSchema(), scanNode.getOutSchema());
-    this.scanNode = scanNode;
-    this.qual = scanNode.getQual();
-    indexLookupKey = new VTuple(datum);
+  private TableStats inputStats;
 
-    this.fileScanner = OldStorageManager.getSeekableScanner(context.getConf(),
-        scanNode.getTableDesc().getMeta(), scanNode.getInSchema(), fragment, outSchema);
-    this.fileScanner.init();
-    this.projector = new Projector(context, inSchema, outSchema, scanNode.getTargets());
+  private CatalogProtos.FragmentProto fragment;
 
-    FileSystem fs = fileName.getFileSystem(context.getConf());
-    this.reader = new BSTIndex(fs.getConf()).
-        getIndexReader(fileName, keySchema, comparator);
+  private Schema keySchema;
+
+  public BSTIndexScanExec(TaskAttemptContext context, IndexScanNode plan,
+                          CatalogProtos.FragmentProto fragment, URI indexPrefix , Schema keySchema,
+                          SimplePredicate [] predicates) throws IOException {
+    super(context, plan.getInSchema(), plan.getOutSchema());
+    this.plan = plan;
+    this.qual = plan.getQual();
+    this.fragment = fragment;
+    this.keySchema = keySchema;
+
+    SortSpec[] keySortSpecs = new SortSpec[predicates.length];
+    Datum[] values = new Datum[predicates.length];
+    for (int i = 0; i < predicates.length; i++) {
+      keySortSpecs[i] = predicates[i].getKeySortSpec();
+      values[i] = predicates[i].getValue();
+    }
+    indexLookupKey = new VTuple(values);
+
+    TupleComparator comparator = new BaseTupleComparator(keySchema,
+        keySortSpecs);
+
+    this.projector = new Projector(context, inSchema, outSchema, plan.getTargets());
+
+    Path indexPath = new Path(indexPrefix.toString(), context.getUniqueKeyFromFragments());
+    this.reader = new BSTIndex(context.getConf()).
+        getIndexReader(indexPath, keySchema, comparator);
     this.reader.open();
   }
 
+  private static Schema mergeSubSchemas(Schema originalSchema, Schema subSchema, Target[] targets, EvalNode qual) {
+    Schema mergedSchema = new Schema();
+    Set<Column> qualAndTargets = TUtil.newHashSet();
+    qualAndTargets.addAll(EvalTreeUtil.findUniqueColumns(qual));
+    for (Target target : targets) {
+      qualAndTargets.addAll(EvalTreeUtil.findUniqueColumns(target.getEvalTree()));
+    }
+    for (Column column : originalSchema.getRootColumns()) {
+      if (subSchema.contains(column)
+          || qualAndTargets.contains(column)
+          || qualAndTargets.contains(column)) {
+        mergedSchema.addColumn(column);
+      }
+    }
+    return mergedSchema;
+  }
+
   @Override
   public void init() throws IOException {
+    Schema projected;
+
+    // in the case where projected column or expression are given
+    // the target can be an empty list.
+    if (plan.hasTargets()) {
+      projected = new Schema();
+      Set<Column> columnSet = new HashSet<Column>();
+
+      if (plan.hasQual()) {
+        columnSet.addAll(EvalTreeUtil.findUniqueColumns(qual));
+      }
+
+      for (Target t : plan.getTargets()) {
+        columnSet.addAll(EvalTreeUtil.findUniqueColumns(t.getEvalTree()));
+      }
+
+      for (Column column : inSchema.getAllColumns()) {
+        if (columnSet.contains(column)) {
+          projected.addColumn(column);
+        }
+      }
+
+    } else {
+      // no any projected columns, meaning that all columns should be projected.
+      // TODO - this implicit rule makes code readability bad. So, we should remove it later
+      projected = outSchema;
+    }
+
+    initScanner(projected);
     super.init();
     progress = 0.0f;
-    if (qual != null) {
-      qual.bind(context.getEvalContext(), inSchema);
+
+    if (plan.hasQual()) {
+      if (fileScanner.isProjectable()) {
+        qual.bind(context.getEvalContext(), projected);
+      } else {
+        qual.bind(context.getEvalContext(), inSchema);
+      }
+    }
+  }
+
+  private void initScanner(Schema projected) throws IOException {
+
+    // Why we should check nullity? See https://issues.apache.org/jira/browse/TAJO-1422
+    if (fragment != null) {
+
+      Schema fileScanOutSchema = mergeSubSchemas(projected, keySchema, plan.getTargets(), qual);
+
+      this.fileScanner = OldStorageManager.getStorageManager(context.getConf(),
+          plan.getTableDesc().getMeta().getStoreType())
+          .getSeekableScanner(plan.getTableDesc().getMeta(), plan.getPhysicalSchema(), fragment, fileScanOutSchema);
+      this.fileScanner.init();
+
+      // See Scanner.isProjectable() method Depending on the result of isProjectable(),
+      // the width of retrieved tuple is changed.
+      //
+      // If TRUE, the retrieved tuple will contain only projected fields.
+      // If FALSE, the retrieved tuple will contain projected fields and NullDatum for non-projected fields.
+      if (fileScanner.isProjectable()) {
+        this.projector = new Projector(context, projected, outSchema, plan.getTargets());
+      } else {
+        this.projector = new Projector(context, inSchema, outSchema, plan.getTargets());
+      }
     }
   }
 
@@ -102,8 +202,9 @@
       fileScanner.seek(offset);
       }
     }
+
     Tuple tuple;
-    if (!scanNode.hasQual()) {
+    if (!plan.hasQual()) {
       if ((tuple = fileScanner.next()) != null) {
         return projector.eval(tuple);
       } else {
@@ -115,8 +216,11 @@
            return projector.eval(tuple);
          } else {
            long offset = reader.next();
-           if (offset == -1) return null;
+           if (offset == -1) {
+             return null;
+           }
            else fileScanner.seek(offset);
+           return null;
          }
        }
      }
@@ -131,9 +235,19 @@
   @Override
   public void close() throws IOException {
     IOUtils.cleanup(null, reader, fileScanner);
+    if (fileScanner != null) {
+      try {
+        TableStats stats = fileScanner.getInputStats();
+        if (stats != null) {
+          inputStats = (TableStats) stats.clone();
+        }
+      } catch (CloneNotSupportedException e) {
+        e.printStackTrace();
+      }
+    }
     reader = null;
     fileScanner = null;
-    scanNode = null;
+    plan = null;
     qual = null;
     projector = null;
     indexLookupKey = null;
@@ -143,4 +257,13 @@
   public float getProgress() {
     return progress;
   }
+
+  @Override
+  public TableStats getInputStats() {
+    if (fileScanner != null) {
+      return fileScanner.getInputStats();
+    } else {
+      return inputStats;
+    }
+  }
 }
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ProjectionExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ProjectionExec.java
index 8a79005..488c3ac 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ProjectionExec.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/ProjectionExec.java
@@ -54,7 +54,9 @@
       return null;
     }
 
-    return projector.eval(tuple);
+    Tuple outTuple = projector.eval(tuple);
+    outTuple.setOffset(tuple.getOffset());
+    return outTuple;
   }
 
   @Override
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/StoreIndexExec.java b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/StoreIndexExec.java
new file mode 100644
index 0000000..f9db842
--- /dev/null
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/planner/physical/StoreIndexExec.java
@@ -0,0 +1,102 @@
+/*
+ * 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.tajo.engine.planner.physical;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.io.IOUtils;
+import org.apache.tajo.catalog.CatalogUtil;
+import org.apache.tajo.catalog.Column;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.catalog.SortSpec;
+import org.apache.tajo.conf.TajoConf;
+import org.apache.tajo.plan.logical.CreateIndexNode;
+import org.apache.tajo.plan.util.PlannerUtil;
+import org.apache.tajo.storage.*;
+import org.apache.tajo.storage.index.bst.BSTIndex;
+import org.apache.tajo.storage.index.bst.BSTIndex.BSTIndexWriter;
+import org.apache.tajo.worker.TaskAttemptContext;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+public class StoreIndexExec extends UnaryPhysicalExec {
+  private static final Log LOG = LogFactory.getLog(StoreIndexExec.class);
+  private BSTIndexWriter indexWriter;
+  private final CreateIndexNode logicalPlan;
+  private int[] indexKeys = null;
+  private Schema keySchema;
+  private TupleComparator comparator;
+
+  public StoreIndexExec(final TaskAttemptContext context, final CreateIndexNode logicalPlan,
+                        final PhysicalExec child) {
+    super(context, logicalPlan.getInSchema(), logicalPlan.getOutSchema(), child);
+    this.logicalPlan = logicalPlan;
+  }
+
+  @Override
+  public void init() throws IOException {
+    super.init();
+
+    SortSpec[] sortSpecs = logicalPlan.getKeySortSpecs();
+    indexKeys = new int[sortSpecs.length];
+    keySchema = PlannerUtil.sortSpecsToSchema(sortSpecs);
+
+    Column col;
+    for (int i = 0 ; i < sortSpecs.length; i++) {
+      col = sortSpecs[i].getSortKey();
+      indexKeys[i] = inSchema.getColumnId(col.getQualifiedName());
+    }
+
+    TajoConf conf = context.getConf();
+    Path indexPath = new Path(logicalPlan.getIndexPath().toString(), context.getUniqueKeyFromFragments());
+    // TODO: Create factory using reflection
+    BSTIndex bst = new BSTIndex(conf);
+    this.comparator = new BaseTupleComparator(keySchema, sortSpecs);
+    this.indexWriter = bst.getIndexWriter(indexPath, BSTIndex.TWO_LEVEL_INDEX, keySchema, comparator);
+    this.indexWriter.setLoadNum(100);
+    this.indexWriter.open();
+  }
+
+  @Override
+  public Tuple next() throws IOException {
+    Tuple tuple;
+    Tuple keyTuple;
+    long offset;
+
+    while((tuple = child.next()) != null) {
+      offset = tuple.getOffset();
+      keyTuple = new VTuple(keySchema.size());
+      RowStoreUtil.project(tuple, keyTuple, indexKeys);
+      indexWriter.write(keyTuple, offset);
+    }
+    return null;
+  }
+
+  @Override
+  public void close() throws IOException {
+    super.close();
+
+    indexWriter.flush();
+    IOUtils.cleanup(LOG, indexWriter);
+
+    indexWriter = null;
+  }
+}
diff --git a/tajo-core/src/main/java/org/apache/tajo/engine/utils/test/ErrorInjectionRewriter.java b/tajo-core/src/main/java/org/apache/tajo/engine/utils/test/ErrorInjectionRewriter.java
index 6582513..54b6c5e 100644
--- a/tajo-core/src/main/java/org/apache/tajo/engine/utils/test/ErrorInjectionRewriter.java
+++ b/tajo-core/src/main/java/org/apache/tajo/engine/utils/test/ErrorInjectionRewriter.java
@@ -18,11 +18,10 @@
 
 package org.apache.tajo.engine.utils.test;
 
-import org.apache.tajo.OverridableConf;
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.plan.LogicalPlan;
-import org.apache.tajo.plan.PlanningException;
 import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRule;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
 
 @SuppressWarnings("unused")
 public class ErrorInjectionRewriter implements LogicalPlanRewriteRule {
@@ -32,12 +31,12 @@
   }
 
   @Override
-  public boolean isEligible(OverridableConf queryContext, LogicalPlan plan) {
+  public boolean isEligible(LogicalPlanRewriteRuleContext context) {
     return true;
   }
 
   @Override
-  public LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException {
+  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException {
     throw new NullPointerException();
   }
 }
diff --git a/tajo-core/src/main/java/org/apache/tajo/master/GlobalEngine.java b/tajo-core/src/main/java/org/apache/tajo/master/GlobalEngine.java
index 8bfe65a..89fcb81 100644
--- a/tajo-core/src/main/java/org/apache/tajo/master/GlobalEngine.java
+++ b/tajo-core/src/main/java/org/apache/tajo/master/GlobalEngine.java
@@ -96,7 +96,8 @@
       analyzer = new SQLAnalyzer();
       preVerifier = new PreLogicalPlanVerifier(context.getCatalog());
       planner = new LogicalPlanner(context.getCatalog(), TablespaceManager.getInstance());
-      optimizer = new LogicalOptimizer(context.getConf());
+      // Access path rewriter is enabled only in QueryMasterTask
+      optimizer = new LogicalOptimizer(context.getConf(), context.getCatalog());
       annotatedPlanVerifier = new LogicalPlanVerifier(context.getConf(), context.getCatalog());
     } catch (Throwable t) {
       LOG.error(t.getMessage(), t);
diff --git a/tajo-core/src/main/java/org/apache/tajo/master/TajoMasterClientService.java b/tajo-core/src/main/java/org/apache/tajo/master/TajoMasterClientService.java
index baf1320..0e9b6e6 100644
--- a/tajo-core/src/main/java/org/apache/tajo/master/TajoMasterClientService.java
+++ b/tajo-core/src/main/java/org/apache/tajo/master/TajoMasterClientService.java
@@ -36,8 +36,7 @@
 import org.apache.tajo.catalog.exception.UndefinedDatabaseException;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
 import org.apache.tajo.catalog.proto.CatalogProtos;
-import org.apache.tajo.catalog.proto.CatalogProtos.FunctionListResponse;
-import org.apache.tajo.catalog.proto.CatalogProtos.TableResponse;
+import org.apache.tajo.catalog.proto.CatalogProtos.*;
 import org.apache.tajo.conf.TajoConf;
 import org.apache.tajo.conf.TajoConf.ConfVars;
 import org.apache.tajo.engine.query.QueryContext;
@@ -65,7 +64,10 @@
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 import static org.apache.tajo.TajoConstants.DEFAULT_DATABASE_NAME;
 import static org.apache.tajo.exception.ExceptionUtil.printStackTraceIfError;
@@ -312,7 +314,6 @@
             .setIsForwarded(true)
             .setUserName(context.getConf().getVar(ConfVars.USERNAME))
             .build();
-
       }
     }
 
@@ -960,5 +961,200 @@
             .build();
       }
     }
+
+    @Override
+    public IndexResponse getIndexWithName(RpcController controller, SessionedStringProto request)
+        throws ServiceException {
+      try {
+        context.getSessionManager().touch(request.getSessionId().getId());
+        Session session = context.getSessionManager().getSession(request.getSessionId().getId());
+
+        String indexName, databaseName;
+        if (CatalogUtil.isFQTableName(request.getValue())) {
+          String [] splitted = CatalogUtil.splitFQTableName(request.getValue());
+          databaseName = splitted[0];
+          indexName = splitted[1];
+        } else {
+          databaseName = session.getCurrentDatabase();
+          indexName = request.getValue();
+        }
+        IndexDescProto indexProto = catalog.getIndexByName(databaseName, indexName).getProto();
+        return IndexResponse.newBuilder()
+            .setState(OK)
+            .setIndexDesc(indexProto)
+            .build();
+
+      } catch (Throwable t) {
+        return IndexResponse.newBuilder()
+            .setState(returnError(t))
+            .build();
+      }
+    }
+
+    @Override
+    public ReturnState existIndexWithName(RpcController controller, SessionedStringProto request)
+        throws ServiceException {
+      try {
+        context.getSessionManager().touch(request.getSessionId().getId());
+        Session session = context.getSessionManager().getSession(request.getSessionId().getId());
+
+        String indexName, databaseName;
+        if (CatalogUtil.isFQTableName(request.getValue())) {
+          String [] splitted = CatalogUtil.splitFQTableName(request.getValue());
+          databaseName = splitted[0];
+          indexName = splitted[1];
+        } else {
+          databaseName = session.getCurrentDatabase();
+          indexName = request.getValue();
+        }
+
+        if (catalog.existIndexByName(databaseName, indexName)) {
+          return OK;
+        } else {
+          return errUndefinedIndexName(indexName);
+        }
+      } catch (Throwable t) {
+        return returnError(t);
+      }
+    }
+
+    @Override
+    public IndexListResponse getIndexesForTable(RpcController controller, SessionedStringProto request)
+        throws ServiceException {
+      try {
+        context.getSessionManager().touch(request.getSessionId().getId());
+        Session session = context.getSessionManager().getSession(request.getSessionId().getId());
+
+        String tableName, databaseName;
+        if (CatalogUtil.isFQTableName(request.getValue())) {
+          String [] splitted = CatalogUtil.splitFQTableName(request.getValue());
+          databaseName = splitted[0];
+          tableName = splitted[1];
+        } else {
+          databaseName = session.getCurrentDatabase();
+          tableName = request.getValue();
+        }
+
+        IndexListResponse.Builder builder = IndexListResponse.newBuilder().setState(OK);
+        for (IndexDesc index : catalog.getAllIndexesByTable(databaseName, tableName)) {
+          builder.addIndexDesc(index.getProto());
+        }
+        return builder.build();
+      } catch (Throwable t) {
+        return IndexListResponse.newBuilder()
+            .setState(returnError(t))
+            .build();
+      }
+    }
+
+    @Override
+    public ReturnState existIndexesForTable(RpcController controller, SessionedStringProto request)
+        throws ServiceException {
+      try {
+        context.getSessionManager().touch(request.getSessionId().getId());
+        Session session = context.getSessionManager().getSession(request.getSessionId().getId());
+
+        String tableName, databaseName;
+        if (CatalogUtil.isFQTableName(request.getValue())) {
+          String [] splitted = CatalogUtil.splitFQTableName(request.getValue());
+          databaseName = splitted[0];
+          tableName = splitted[1];
+        } else {
+          databaseName = session.getCurrentDatabase();
+          tableName = request.getValue();
+        }
+        if (catalog.existIndexesByTable(databaseName, tableName)) {
+          return OK;
+        } else {
+          return errUndefinedIndex(tableName);
+        }
+      } catch (Throwable t) {
+        return returnError(t);
+      }
+    }
+
+    @Override
+    public IndexResponse getIndexWithColumns(RpcController controller, GetIndexWithColumnsRequest request)
+        throws ServiceException {
+      try {
+        context.getSessionManager().touch(request.getSessionId().getId());
+        Session session = context.getSessionManager().getSession(request.getSessionId().getId());
+
+        String tableName, databaseName;
+        if (CatalogUtil.isFQTableName(request.getTableName())) {
+          String [] splitted = CatalogUtil.splitFQTableName(request.getTableName());
+          databaseName = splitted[0];
+          tableName = splitted[1];
+        } else {
+          databaseName = session.getCurrentDatabase();
+          tableName = request.getTableName();
+        }
+        String[] columnNames = new String[request.getColumnNamesCount()];
+        columnNames = request.getColumnNamesList().toArray(columnNames);
+
+        return IndexResponse.newBuilder()
+            .setState(OK)
+            .setIndexDesc(catalog.getIndexByColumnNames(databaseName, tableName, columnNames).getProto())
+            .build();
+
+      } catch (Throwable t) {
+        return IndexResponse.newBuilder()
+            .setState(returnError(t))
+            .build();
+      }
+    }
+
+    @Override
+    public ReturnState existIndexWithColumns(RpcController controller, GetIndexWithColumnsRequest request)
+        throws ServiceException {
+      try {
+        context.getSessionManager().touch(request.getSessionId().getId());
+        Session session = context.getSessionManager().getSession(request.getSessionId().getId());
+
+        String tableName, databaseName;
+        if (CatalogUtil.isFQTableName(request.getTableName())) {
+          String [] splitted = CatalogUtil.splitFQTableName(request.getTableName());
+          databaseName = splitted[0];
+          tableName = splitted[1];
+        } else {
+          databaseName = session.getCurrentDatabase();
+          tableName = request.getTableName();
+        }
+        String[] columnNames = new String[request.getColumnNamesCount()];
+        columnNames = request.getColumnNamesList().toArray(columnNames);
+        if (catalog.existIndexByColumnNames(databaseName, tableName, columnNames)) {
+          return OK;
+        } else {
+          return errUndefinedIndex(tableName, request.getColumnNamesList());
+        }
+      } catch (Throwable t) {
+        return returnError(t);
+      }
+    }
+
+    @Override
+    public ReturnState dropIndex(RpcController controller, SessionedStringProto request)
+        throws ServiceException {
+      try {
+        context.getSessionManager().touch(request.getSessionId().getId());
+        Session session = context.getSessionManager().getSession(request.getSessionId().getId());
+        QueryContext queryContext = new QueryContext(conf, session);
+
+        String indexName, databaseName;
+        if (CatalogUtil.isFQTableName(request.getValue())) {
+          String [] splitted = CatalogUtil.splitFQTableName(request.getValue());
+          databaseName = splitted[0];
+          indexName = splitted[1];
+        } else {
+          databaseName = session.getCurrentDatabase();
+          indexName = request.getValue();
+        }
+        catalog.dropIndex(databaseName, indexName);
+
+        return OK;
+      } catch (Throwable t) {
+        return returnError(t);
+      }
+    }
   }
 }
diff --git a/tajo-core/src/main/java/org/apache/tajo/master/exec/DDLExecutor.java b/tajo-core/src/main/java/org/apache/tajo/master/exec/DDLExecutor.java
index f6bb4f7..a535f94 100644
--- a/tajo-core/src/main/java/org/apache/tajo/master/exec/DDLExecutor.java
+++ b/tajo-core/src/main/java/org/apache/tajo/master/exec/DDLExecutor.java
@@ -72,45 +72,123 @@
 
     switch (root.getType()) {
 
-    case ALTER_TABLESPACE:
-      AlterTablespaceNode alterTablespace = (AlterTablespaceNode) root;
-      alterTablespace(context, queryContext, alterTablespace);
-      return true;
+      case ALTER_TABLESPACE:
+        AlterTablespaceNode alterTablespace = (AlterTablespaceNode) root;
+        alterTablespace(context, queryContext, alterTablespace);
+        return true;
 
 
-    case CREATE_DATABASE:
-      CreateDatabaseNode createDatabase = (CreateDatabaseNode) root;
-      createDatabase(queryContext, createDatabase.getDatabaseName(), null, createDatabase.isIfNotExists());
-      return true;
-    case DROP_DATABASE:
-      DropDatabaseNode dropDatabaseNode = (DropDatabaseNode) root;
-      dropDatabase(queryContext, dropDatabaseNode.getDatabaseName(), dropDatabaseNode.isIfExists());
-      return true;
+      case CREATE_DATABASE:
+        CreateDatabaseNode createDatabase = (CreateDatabaseNode) root;
+        createDatabase(queryContext, createDatabase.getDatabaseName(), null, createDatabase.isIfNotExists());
+        return true;
+      case DROP_DATABASE:
+        DropDatabaseNode dropDatabaseNode = (DropDatabaseNode) root;
+        dropDatabase(queryContext, dropDatabaseNode.getDatabaseName(), dropDatabaseNode.isIfExists());
+        return true;
 
 
-    case CREATE_TABLE:
-      CreateTableNode createTable = (CreateTableNode) root;
-      createTable(queryContext, createTable, createTable.isIfNotExists());
-      return true;
-    case DROP_TABLE:
-      DropTableNode dropTable = (DropTableNode) root;
-      dropTable(queryContext, dropTable.getTableName(), dropTable.isIfExists(), dropTable.isPurge());
-      return true;
-    case TRUNCATE_TABLE:
-      TruncateTableNode truncateTable = (TruncateTableNode) root;
-      truncateTable(queryContext, truncateTable);
-      return true;
+      case CREATE_TABLE:
+        CreateTableNode createTable = (CreateTableNode) root;
+        createTable(queryContext, createTable, createTable.isIfNotExists());
+        return true;
+      case DROP_TABLE:
+        DropTableNode dropTable = (DropTableNode) root;
+        dropTable(queryContext, dropTable.getTableName(), dropTable.isIfExists(), dropTable.isPurge());
+        return true;
+      case TRUNCATE_TABLE:
+        TruncateTableNode truncateTable = (TruncateTableNode) root;
+        truncateTable(queryContext, truncateTable);
+        return true;
 
-    case ALTER_TABLE:
-      AlterTableNode alterTable = (AlterTableNode) root;
-      alterTable(context, queryContext, alterTable);
-      return true;
+      case ALTER_TABLE:
+        AlterTableNode alterTable = (AlterTableNode) root;
+        alterTable(context, queryContext, alterTable);
+        return true;
+
+      case CREATE_INDEX:
+        CreateIndexNode createIndex = (CreateIndexNode) root;
+        createIndex(queryContext, createIndex);
+        return true;
+
+      case DROP_INDEX:
+        DropIndexNode dropIndexNode = (DropIndexNode) root;
+        dropIndex(queryContext, dropIndexNode);
+        return true;
 
     default:
       throw new InternalError("updateQuery cannot handle such query: \n" + root.toJson());
     }
   }
 
+  public void createIndex(final QueryContext queryContext, final CreateIndexNode createIndexNode) {
+    String databaseName, simpleIndexName, qualifiedIndexName;
+    if (CatalogUtil.isFQTableName(createIndexNode.getIndexName())) {
+      String [] splits = CatalogUtil.splitFQTableName(createIndexNode.getIndexName());
+      databaseName = splits[0];
+      simpleIndexName = splits[1];
+      qualifiedIndexName = createIndexNode.getIndexName();
+    } else {
+      databaseName = queryContext.getCurrentDatabase();
+      simpleIndexName = createIndexNode.getIndexName();
+      qualifiedIndexName = CatalogUtil.buildFQName(databaseName, simpleIndexName);
+    }
+
+    if (catalog.existIndexByName(databaseName, simpleIndexName)) {
+      throw new DuplicateIndexException(simpleIndexName);
+    }
+
+    ScanNode scanNode = PlannerUtil.findTopNode(createIndexNode, NodeType.SCAN);
+    if (scanNode == null) {
+      throw new InternalError("Cannot find the table of the relation");
+    }
+
+    IndexDesc indexDesc = new IndexDesc(databaseName, CatalogUtil.extractSimpleName(scanNode.getTableName()),
+        simpleIndexName, createIndexNode.getIndexPath(),
+        createIndexNode.getKeySortSpecs(), createIndexNode.getIndexMethod(),
+        createIndexNode.isUnique(), false, scanNode.getLogicalSchema());
+
+    if (catalog.createIndex(indexDesc)) {
+      LOG.info("Index " + qualifiedIndexName + " is created for the table " + scanNode.getTableName() + ".");
+    } else {
+      LOG.info("Index creation " + qualifiedIndexName + " is failed.");
+      throw new TajoInternalError("Cannot create index \"" + qualifiedIndexName + "\".");
+    }
+  }
+
+  public void dropIndex(final QueryContext queryContext, final DropIndexNode dropIndexNode) {
+    String databaseName, simpleIndexName;
+    if (CatalogUtil.isFQTableName(dropIndexNode.getIndexName())) {
+      String [] splits = CatalogUtil.splitFQTableName(dropIndexNode.getIndexName());
+      databaseName = splits[0];
+      simpleIndexName = splits[1];
+    } else {
+      databaseName = queryContext.getCurrentDatabase();
+      simpleIndexName = dropIndexNode.getIndexName();
+    }
+
+    if (!catalog.existIndexByName(databaseName, simpleIndexName)) {
+      throw new UndefinedIndexException(simpleIndexName);
+    }
+
+    IndexDesc desc = catalog.getIndexByName(databaseName, simpleIndexName);
+
+    if (!catalog.dropIndex(databaseName, simpleIndexName)) {
+      LOG.info("Cannot drop index \"" + simpleIndexName + "\".");
+      throw new TajoInternalError("Cannot drop index \"" + simpleIndexName + "\".");
+    }
+
+    Path indexPath = new Path(desc.getIndexPath());
+    try {
+      FileSystem fs = indexPath.getFileSystem(context.getConf());
+      fs.delete(indexPath, true);
+    } catch (IOException e) {
+      throw new InternalError(e.getMessage());
+    }
+
+    LOG.info("Index " + simpleIndexName + " is dropped.");
+  }
+
   /**
    * Alter a given table
    */
diff --git a/tajo-core/src/main/java/org/apache/tajo/master/exec/NonForwardQueryResultSystemScanner.java b/tajo-core/src/main/java/org/apache/tajo/master/exec/NonForwardQueryResultSystemScanner.java
index 228c0b8..b348265 100644
--- a/tajo-core/src/main/java/org/apache/tajo/master/exec/NonForwardQueryResultSystemScanner.java
+++ b/tajo-core/src/main/java/org/apache/tajo/master/exec/NonForwardQueryResultSystemScanner.java
@@ -25,7 +25,11 @@
 import org.apache.tajo.QueryId;
 import org.apache.tajo.TaskAttemptId;
 import org.apache.tajo.TaskId;
-import org.apache.tajo.catalog.*;
+import org.apache.tajo.catalog.CatalogUtil;
+import org.apache.tajo.catalog.Column;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.catalog.TableDesc;
+import org.apache.tajo.catalog.TableMeta;
 import org.apache.tajo.catalog.proto.CatalogProtos.*;
 import org.apache.tajo.catalog.statistics.TableStats;
 import org.apache.tajo.common.TajoDataTypes.DataType;
@@ -296,43 +300,35 @@
     
     return tuples;
   }
-  
+
   private List<Tuple> getIndexes(Schema outSchema) {
-    List<IndexProto> indexList = masterContext.getCatalog().getAllIndexes();
+    List<IndexDescProto> indexList = masterContext.getCatalog().getAllIndexes();
     List<Tuple> tuples = new ArrayList<Tuple>(indexList.size());
     List<Column> columns = outSchema.getRootColumns();
     Tuple aTuple;
 
-    for (IndexProto index: indexList) {
+    for (IndexDescProto index: indexList) {
       aTuple = new VTuple(outSchema.size());
-      
+
       for (int fieldId = 0; fieldId < columns.size(); fieldId++) {
         Column column = columns.get(fieldId);
-        
+
         if ("db_id".equalsIgnoreCase(column.getSimpleName())) {
-          aTuple.put(fieldId, DatumFactory.createInt4(index.getDbId()));
+          aTuple.put(fieldId, DatumFactory.createInt4(index.getTableIdentifier().getDbId()));
         } else if ("tid".equalsIgnoreCase(column.getSimpleName())) {
-          aTuple.put(fieldId, DatumFactory.createInt4(index.getTId()));
+          aTuple.put(fieldId, DatumFactory.createInt4(index.getTableIdentifier().getTid()));
         } else if ("index_name".equalsIgnoreCase(column.getSimpleName())) {
           aTuple.put(fieldId, DatumFactory.createText(index.getIndexName()));
-        } else if ("column_name".equalsIgnoreCase(column.getSimpleName())) {
-          aTuple.put(fieldId, DatumFactory.createText(index.getColumnName()));
-        } else if ("data_type".equalsIgnoreCase(column.getSimpleName())) {
-          aTuple.put(fieldId, DatumFactory.createText(index.getDataType()));
-        } else if ("index_type".equalsIgnoreCase(column.getSimpleName())) {
-          aTuple.put(fieldId, DatumFactory.createText(index.getIndexType()));
-        } else if ("is_unique".equalsIgnoreCase(column.getSimpleName())) {
-          aTuple.put(fieldId, DatumFactory.createBool(index.getIsUnique()));
-        } else if ("is_clustered".equalsIgnoreCase(column.getSimpleName())) {
-          aTuple.put(fieldId, DatumFactory.createBool(index.getIsClustered()));
-        } else if ("is_ascending".equalsIgnoreCase(column.getSimpleName())) {
-          aTuple.put(fieldId, DatumFactory.createBool(index.getIsAscending()));
+        } else if ("index_method".equalsIgnoreCase(column.getSimpleName())) {
+          aTuple.put(fieldId, DatumFactory.createText(index.getIndexMethod().name()));
+        } else if ("index_path".equalsIgnoreCase(column.getSimpleName())) {
+          aTuple.put(fieldId, DatumFactory.createText(index.getIndexPath()));
         }
       }
-      
+
       tuples.add(aTuple);
     }
-    
+
     return tuples;
   }
   
diff --git a/tajo-core/src/main/java/org/apache/tajo/master/exec/QueryExecutor.java b/tajo-core/src/main/java/org/apache/tajo/master/exec/QueryExecutor.java
index ceb3c4a..149d0b0 100644
--- a/tajo-core/src/main/java/org/apache/tajo/master/exec/QueryExecutor.java
+++ b/tajo-core/src/main/java/org/apache/tajo/master/exec/QueryExecutor.java
@@ -25,10 +25,8 @@
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.tajo.*;
-import org.apache.tajo.catalog.CatalogService;
-import org.apache.tajo.catalog.Schema;
-import org.apache.tajo.catalog.TableDesc;
-import org.apache.tajo.catalog.TableMeta;
+import org.apache.tajo.catalog.*;
+import org.apache.tajo.catalog.exception.DuplicateIndexException;
 import org.apache.tajo.catalog.proto.CatalogProtos;
 import org.apache.tajo.catalog.statistics.TableStats;
 import org.apache.tajo.common.TajoDataTypes;
@@ -69,8 +67,8 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static org.apache.tajo.exception.ReturnStateUtil.errUndefinedDatabase;
 import static org.apache.tajo.exception.ReturnStateUtil.OK;
+import static org.apache.tajo.exception.ReturnStateUtil.errUndefinedDatabase;
 
 public class QueryExecutor {
   private static final Log LOG = LogFactory.getLog(QueryExecutor.class);
@@ -104,10 +102,17 @@
 
 
     } else if (PlannerUtil.checkIfDDLPlan(rootNode)) {
-      ddlExecutor.execute(queryContext, plan);
-      response.setQueryId(QueryIdFactory.NULL_QUERY_ID.getProto());
-      response.setState(OK);
 
+      if (PlannerUtil.isDistExecDDL(rootNode)) {
+        if (rootNode.getChild().getType() == NodeType.CREATE_INDEX) {
+          checkIndexExistence(queryContext, (CreateIndexNode) rootNode.getChild());
+        }
+        executeDistributedQuery(queryContext, session, plan, sql, jsonExpr, response);
+      } else {
+        ddlExecutor.execute(queryContext, plan);
+        response.setQueryId(QueryIdFactory.NULL_QUERY_ID.getProto());
+        response.setState(OK);
+      }
 
     } else if (plan.isExplain()) { // explain query
       execExplain(plan, queryContext, plan.isExplainGlobal(), response);
@@ -134,7 +139,7 @@
 
   public void execSetSession(Session session, LogicalPlan plan,
                              SubmitQueryResponse.Builder response) {
-    SetSessionNode setSessionNode = ((LogicalRootNode)plan.getRootBlock().getRoot()).getChild();
+    SetSessionNode setSessionNode = ((LogicalRootNode) plan.getRootBlock().getRoot()).getChild();
 
     final String varName = setSessionNode.getName();
 
@@ -506,6 +511,25 @@
         " is forwarded to " + queryInfo.getQueryMasterHost() + ":" + queryInfo.getQueryMasterPort());
   }
 
+  private void checkIndexExistence(final QueryContext queryContext, final CreateIndexNode createIndexNode)
+      throws IOException {
+    String databaseName, simpleIndexName, qualifiedIndexName;
+    if (CatalogUtil.isFQTableName(createIndexNode.getIndexName())) {
+      String[] splits = CatalogUtil.splitFQTableName(createIndexNode.getIndexName());
+      databaseName = splits[0];
+      simpleIndexName = splits[1];
+      qualifiedIndexName = createIndexNode.getIndexName();
+    } else {
+      databaseName = queryContext.getCurrentDatabase();
+      simpleIndexName = createIndexNode.getIndexName();
+      qualifiedIndexName = CatalogUtil.buildFQName(databaseName, simpleIndexName);
+    }
+
+    if (catalog.existIndexByName(databaseName, simpleIndexName)) {
+      throw new DuplicateIndexException(qualifiedIndexName);
+    }
+  }
+
   public MasterPlan compileMasterPlan(LogicalPlan plan, QueryContext context, GlobalPlanner planner)
       throws Exception {
 
diff --git a/tajo-core/src/main/java/org/apache/tajo/querymaster/Query.java b/tajo-core/src/main/java/org/apache/tajo/querymaster/Query.java
index 6fc4ea9..e3629c7 100644
--- a/tajo-core/src/main/java/org/apache/tajo/querymaster/Query.java
+++ b/tajo-core/src/main/java/org/apache/tajo/querymaster/Query.java
@@ -33,6 +33,8 @@
 import org.apache.tajo.QueryVars;
 import org.apache.tajo.SessionVars;
 import org.apache.tajo.TajoProtos.QueryState;
+import org.apache.tajo.catalog.*;
+import org.apache.tajo.catalog.exception.CatalogException;
 import org.apache.tajo.catalog.proto.CatalogProtos.UpdateTableStatsProto;
 import org.apache.tajo.catalog.CatalogService;
 import org.apache.tajo.catalog.TableDesc;
@@ -43,6 +45,7 @@
 import org.apache.tajo.engine.planner.global.ExecutionBlockCursor;
 import org.apache.tajo.engine.planner.global.ExecutionQueue;
 import org.apache.tajo.engine.planner.global.MasterPlan;
+import org.apache.tajo.exception.TajoInternalError;
 import org.apache.tajo.plan.logical.*;
 import org.apache.tajo.engine.query.QueryContext;
 import org.apache.tajo.master.event.*;
@@ -513,6 +516,7 @@
         hookList.add(new MaterializedResultHook());
         hookList.add(new CreateTableHook());
         hookList.add(new InsertTableHook());
+        hookList.add(new CreateIndexHook());
       }
 
       public void execute(QueryContext queryContext, Query query,
@@ -526,6 +530,48 @@
       }
     }
 
+    private static class CreateIndexHook implements QueryHook {
+
+      @Override
+      public boolean isEligible(QueryContext queryContext, Query query, ExecutionBlockId finalExecBlockId, Path finalOutputDir) {
+        Stage lastStage = query.getStage(finalExecBlockId);
+        return  lastStage.getBlock().getPlan().getType() == NodeType.CREATE_INDEX;
+      }
+
+      @Override
+      public void execute(QueryMaster.QueryMasterContext context, QueryContext queryContext, Query query, ExecutionBlockId finalExecBlockId, Path finalOutputDir) throws Exception {
+        CatalogService catalog = context.getWorkerContext().getCatalog();
+        Stage lastStage = query.getStage(finalExecBlockId);
+
+        CreateIndexNode createIndexNode = (CreateIndexNode) lastStage.getBlock().getPlan();
+        String databaseName, simpleIndexName, qualifiedIndexName;
+        if (CatalogUtil.isFQTableName(createIndexNode.getIndexName())) {
+          String [] splits = CatalogUtil.splitFQTableName(createIndexNode.getIndexName());
+          databaseName = splits[0];
+          simpleIndexName = splits[1];
+          qualifiedIndexName = createIndexNode.getIndexName();
+        } else {
+          databaseName = queryContext.getCurrentDatabase();
+          simpleIndexName = createIndexNode.getIndexName();
+          qualifiedIndexName = CatalogUtil.buildFQName(databaseName, simpleIndexName);
+        }
+        ScanNode scanNode = PlannerUtil.findTopNode(createIndexNode, NodeType.SCAN);
+        if (scanNode == null) {
+          throw new IOException("Cannot find the table of the relation");
+        }
+        IndexDesc indexDesc = new IndexDesc(databaseName, CatalogUtil.extractSimpleName(scanNode.getTableName()),
+            simpleIndexName, createIndexNode.getIndexPath(),
+            createIndexNode.getKeySortSpecs(), createIndexNode.getIndexMethod(),
+            createIndexNode.isUnique(), false, scanNode.getLogicalSchema());
+        if (catalog.createIndex(indexDesc)) {
+          LOG.info("Index " + qualifiedIndexName + " is created for the table " + scanNode.getTableName() + ".");
+        } else {
+          LOG.info("Index creation " + qualifiedIndexName + " is failed.");
+          throw new TajoInternalError("Cannot create index \"" + qualifiedIndexName + "\".");
+        }
+      }
+    }
+
     private static class MaterializedResultHook implements QueryHook {
 
       @Override
diff --git a/tajo-core/src/main/java/org/apache/tajo/querymaster/QueryMasterTask.java b/tajo-core/src/main/java/org/apache/tajo/querymaster/QueryMasterTask.java
index 611560d..ac1bab5 100644
--- a/tajo-core/src/main/java/org/apache/tajo/querymaster/QueryMasterTask.java
+++ b/tajo-core/src/main/java/org/apache/tajo/querymaster/QueryMasterTask.java
@@ -313,11 +313,10 @@
         LOG.warn("Query already started");
         return;
       }
-
-
+      LOG.info(SessionVars.INDEX_ENABLED.keyname() + " : " + queryContext.getBool(SessionVars.INDEX_ENABLED));
       CatalogService catalog = getQueryTaskContext().getQueryMasterContext().getWorkerContext().getCatalog();
       LogicalPlanner planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-      LogicalOptimizer optimizer = new LogicalOptimizer(systemConf);
+      LogicalOptimizer optimizer = new LogicalOptimizer(systemConf, catalog);
       Expr expr = JsonHelper.fromJson(jsonExpr, Expr.class);
       jsonExpr = null; // remove the possible OOM
 
@@ -346,6 +345,14 @@
             tableDescMap.put(scanNode.getCanonicalName(), scanNode.getTableDesc());
           }
         }
+
+        scanNodes = PlannerUtil.findAllNodes(block.getRoot(), NodeType.INDEX_SCAN);
+        if (scanNodes != null) {
+          for (LogicalNode eachScanNode : scanNodes) {
+            ScanNode scanNode = (ScanNode) eachScanNode;
+            tableDescMap.put(scanNode.getCanonicalName(), scanNode.getTableDesc());
+          }
+        }
       }
       MasterPlan masterPlan = new MasterPlan(queryId, queryContext, plan);
       queryMasterContext.getGlobalPlanner().build(queryContext, masterPlan);
diff --git a/tajo-core/src/main/java/org/apache/tajo/util/IndexUtil.java b/tajo-core/src/main/java/org/apache/tajo/util/IndexUtil.java
deleted file mode 100644
index 23d245b..0000000
--- a/tajo-core/src/main/java/org/apache/tajo/util/IndexUtil.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/**
- * 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.tajo.util;
-
-import com.google.gson.Gson;
-import org.apache.tajo.catalog.Column;
-import org.apache.tajo.catalog.Schema;
-import org.apache.tajo.catalog.SortSpec;
-import org.apache.tajo.datum.Datum;
-import org.apache.tajo.engine.json.CoreGsonHelper;
-import org.apache.tajo.plan.LogicalPlan;
-import org.apache.tajo.plan.expr.*;
-import org.apache.tajo.plan.logical.IndexScanNode;
-import org.apache.tajo.plan.logical.ScanNode;
-import org.apache.tajo.storage.fragment.FileFragment;
-
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.Map.Entry;
-
-public class IndexUtil {
-  public static String getIndexNameOfFrag(FileFragment fragment, SortSpec[] keys) {
-    StringBuilder builder = new StringBuilder(); 
-    builder.append(fragment.getPath().getName() + "_");
-    builder.append(fragment.getStartKey() + "_" + fragment.getLength() + "_");
-    for(int i = 0 ; i < keys.length ; i ++) {
-      builder.append(keys[i].getSortKey().getSimpleName()+"_");
-    }
-    builder.append("_index");
-    return builder.toString();
-       
-  }
-  
-  public static String getIndexName(String indexName , SortSpec[] keys) {
-    StringBuilder builder = new StringBuilder();
-    builder.append(indexName + "_");
-    for(int i = 0 ; i < keys.length ; i ++) {
-      builder.append(keys[i].getSortKey().getSimpleName() + "_");
-    }
-    return builder.toString();
-  }
-  
-  public static IndexScanNode indexEval(LogicalPlan plan, ScanNode scanNode,
-      Iterator<Entry<String, String>> iter ) {
-   
-    EvalNode qual = scanNode.getQual();
-    Gson gson = CoreGsonHelper.getInstance();
-    
-    FieldAndValueFinder nodeFinder = new FieldAndValueFinder();
-    qual.preOrder(nodeFinder);
-    LinkedList<BinaryEval> nodeList = nodeFinder.getNodeList();
-    
-    int maxSize = Integer.MIN_VALUE;
-    SortSpec[] maxIndex = null;
-    
-    String json;
-    while(iter.hasNext()) {
-      Entry<String , String> entry = iter.next();
-      json = entry.getValue();
-      SortSpec[] sortKey = gson.fromJson(json, SortSpec[].class);
-      if(sortKey.length > nodeList.size()) {
-        /* If the number of the sort key is greater than where condition, 
-         * this index cannot be used
-         * */
-        continue; 
-      } else {
-        boolean[] equal = new boolean[sortKey.length];
-        for(int i = 0 ; i < sortKey.length ; i ++) {
-          for(int j = 0 ; j < nodeList.size() ; j ++) {
-            Column col = ((FieldEval)(nodeList.get(j).getLeftExpr())).getColumnRef();
-            if(col.equals(sortKey[i].getSortKey())) {
-              equal[i] = true;
-            }
-          }
-        }
-        boolean chk = true;
-        for(int i = 0 ; i < equal.length ; i ++) {
-          chk = chk && equal[i];
-        }
-        if(chk) {
-          if(maxSize < sortKey.length) {
-            maxSize = sortKey.length;
-            maxIndex = sortKey;
-          }
-        }
-      }
-    }
-    if(maxIndex == null) {
-      return null;
-    } else {
-      Schema keySchema = new Schema();
-      for(int i = 0 ; i < maxIndex.length ; i ++ ) {
-        keySchema.addColumn(maxIndex[i].getSortKey());
-      }
-      Datum[] datum = new Datum[nodeList.size()];
-      for(int i = 0 ; i < nodeList.size() ; i ++ ) {
-        datum[i] = ((ConstEval)(nodeList.get(i).getRightExpr())).getValue();
-      }
-      
-      return new IndexScanNode(plan.newPID(), scanNode, keySchema , datum , maxIndex);
-    }
-
-  }
-  
-  
-  private static class FieldAndValueFinder implements EvalNodeVisitor {
-    private LinkedList<BinaryEval> nodeList = new LinkedList<BinaryEval>();
-    
-    public LinkedList<BinaryEval> getNodeList () {
-      return this.nodeList;
-    }
-    
-    @Override
-    public void visit(EvalNode node) {
-      BinaryEval binaryEval = (BinaryEval) node;
-      switch(node.getType()) {
-      case AND:
-        break;
-      case EQUAL:
-        if( binaryEval.getLeftExpr().getType() == EvalType.FIELD
-          && binaryEval.getRightExpr().getType() == EvalType.CONST ) {
-          nodeList.add(binaryEval);
-        }
-        break;
-      case IS_NULL:
-        if( binaryEval.getLeftExpr().getType() == EvalType.FIELD
-          && binaryEval.getRightExpr().getType() == EvalType.CONST) {
-          nodeList.add(binaryEval);
-        }
-        break;
-      default:
-        break;
-      }
-    }
-  }
-}
diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorkerClientService.java b/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorkerClientService.java
index edb5703..281e23e 100644
--- a/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorkerClientService.java
+++ b/tajo-core/src/main/java/org/apache/tajo/worker/TajoWorkerClientService.java
@@ -26,8 +26,8 @@
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.service.AbstractService;
 import org.apache.tajo.QueryId;
-import org.apache.tajo.exception.ReturnStateUtil;
 import org.apache.tajo.conf.TajoConf;
+import org.apache.tajo.exception.ReturnStateUtil;
 import org.apache.tajo.ipc.ClientProtos.GetQueryHistoryResponse;
 import org.apache.tajo.ipc.ClientProtos.QueryIdRequest;
 import org.apache.tajo.ipc.QueryMasterClientProtocol;
diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/TaskAttemptContext.java b/tajo-core/src/main/java/org/apache/tajo/worker/TaskAttemptContext.java
index d020639..5d7a53a 100644
--- a/tajo-core/src/main/java/org/apache/tajo/worker/TaskAttemptContext.java
+++ b/tajo-core/src/main/java/org/apache/tajo/worker/TaskAttemptContext.java
@@ -367,7 +367,18 @@
     }
     return fragmentMap.get(id).toArray(new FragmentProto[fragmentMap.get(id).size()]);
   }
-  
+
+  public String getUniqueKeyFromFragments() {
+    StringBuilder sb = new StringBuilder();
+    for (List<FragmentProto> fragments : fragmentMap.values()) {
+      for (FragmentProto f : fragments) {
+        FileFragment fileFragment = FragmentConvertor.convert(FileFragment.class, f);
+        sb.append(fileFragment.getPath().getName()).append(fileFragment.getStartKey()).append(fileFragment.getLength());
+      }
+    }
+    return sb.toString();
+  }
+
   public int hashCode() {
     return Objects.hashCode(taskId);
   }
diff --git a/tajo-core/src/main/java/org/apache/tajo/worker/TaskImpl.java b/tajo-core/src/main/java/org/apache/tajo/worker/TaskImpl.java
index 82ea479..92c682c 100644
--- a/tajo-core/src/main/java/org/apache/tajo/worker/TaskImpl.java
+++ b/tajo-core/src/main/java/org/apache/tajo/worker/TaskImpl.java
@@ -125,21 +125,9 @@
 
   public void initPlan() throws IOException {
     plan = LogicalNodeDeserializer.deserialize(queryContext, context.getEvalContext(), request.getPlan());
-    LogicalNode [] scanNode = PlannerUtil.findAllNodes(plan, NodeType.SCAN);
-    if (scanNode != null) {
-      for (LogicalNode node : scanNode) {
-        ScanNode scan = (ScanNode) node;
-        descs.put(scan.getCanonicalName(), scan.getTableDesc());
-      }
-    }
-
-    LogicalNode [] partitionScanNode = PlannerUtil.findAllNodes(plan, NodeType.PARTITIONS_SCAN);
-    if (partitionScanNode != null) {
-      for (LogicalNode node : partitionScanNode) {
-        PartitionedTableScanNode scan = (PartitionedTableScanNode) node;
-        descs.put(scan.getCanonicalName(), scan.getTableDesc());
-      }
-    }
+    updateDescsForScanNodes(NodeType.SCAN);
+    updateDescsForScanNodes(NodeType.PARTITIONS_SCAN);
+    updateDescsForScanNodes(NodeType.INDEX_SCAN);
 
     interQuery = request.getProto().getInterQuery();
     if (interQuery) {
@@ -179,6 +167,17 @@
     LOG.info("==================================");
   }
 
+  private void updateDescsForScanNodes(NodeType nodeType) {
+    assert nodeType == NodeType.SCAN || nodeType == NodeType.PARTITIONS_SCAN || nodeType == NodeType.INDEX_SCAN;
+    LogicalNode[] scanNodes = PlannerUtil.findAllNodes(plan, nodeType);
+    if (scanNodes != null) {
+      for (LogicalNode node : scanNodes) {
+        ScanNode scanNode = (ScanNode) node;
+        descs.put(scanNode.getCanonicalName(), scanNode.getTableDesc());
+      }
+    }
+  }
+
   private void startScriptExecutors() throws IOException {
     for (TajoScriptEngine executor : context.getEvalContext().getAllScriptEngines()) {
       executor.start(systemConf);
diff --git a/tajo-core/src/main/java/org/apache/tajo/ws/rs/resources/QueryResource.java b/tajo-core/src/main/java/org/apache/tajo/ws/rs/resources/QueryResource.java
index f91288d..eb67167 100644
--- a/tajo-core/src/main/java/org/apache/tajo/ws/rs/resources/QueryResource.java
+++ b/tajo-core/src/main/java/org/apache/tajo/ws/rs/resources/QueryResource.java
@@ -18,13 +18,11 @@
 
 package org.apache.tajo.ws.rs.resources;
 
-import org.apache.commons.lang.StringUtils;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.tajo.QueryId;
 import org.apache.tajo.TajoProtos;
 import org.apache.tajo.exception.ReturnStateUtil;
-import org.apache.tajo.ipc.ClientProtos;
 import org.apache.tajo.ipc.ClientProtos.SubmitQueryResponse;
 import org.apache.tajo.master.QueryInProgress;
 import org.apache.tajo.master.QueryInfo;
@@ -263,7 +261,7 @@
         return ResourcesUtil.createBadRequestResponse(LOG, "Provided session id (" + sessionId + ") is invalid.");
       }
       
-      SubmitQueryResponse response = 
+      SubmitQueryResponse response =
         masterContext.getGlobalEngine().executeQuery(session, request.getQuery(), false);
       if (ReturnStateUtil.isError(response.getState())) {
         return ResourcesUtil.createExceptionResponse(LOG, response.getState().getMessage());
diff --git a/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java b/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java
index 3fcb15e..b5e464b 100644
--- a/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java
+++ b/tajo-core/src/test/java/org/apache/tajo/QueryTestCaseBase.java
@@ -901,6 +901,9 @@
         if (isLocalTable) {
           createdTableGlobalSet.remove(tableName);
         }
+      } else if (expr.getType() == OpType.CreateIndex) {
+        // TODO: index existence check
+        client.executeQuery(compiled);
       } else {
         assertTrue(ddlFilePath + " is not a Create or Drop Table statement", false);
       }
diff --git a/tajo-core/src/test/java/org/apache/tajo/cli/tools/TestTajoDump.java b/tajo-core/src/test/java/org/apache/tajo/cli/tools/TestTajoDump.java
index b371be2..aa8070e 100644
--- a/tajo-core/src/test/java/org/apache/tajo/cli/tools/TestTajoDump.java
+++ b/tajo-core/src/test/java/org/apache/tajo/cli/tools/TestTajoDump.java
@@ -18,13 +18,22 @@
 
 package org.apache.tajo.cli.tools;
 
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
 import org.apache.tajo.QueryTestCaseBase;
 import org.apache.tajo.auth.UserRoleInfo;
+import org.apache.tajo.storage.StorageUtil;
+import org.apache.tajo.storage.TablespaceManager;
+import org.apache.tajo.util.FileUtil;
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.PrintWriter;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 public class TestTajoDump extends QueryTestCaseBase {
 
   @Test
@@ -33,16 +42,18 @@
       executeString("CREATE TABLE \"" + getCurrentDatabase() +
           "\".\"TableName1\" (\"Age\" int, \"FirstName\" TEXT, lastname TEXT)");
 
-      UserRoleInfo userInfo = UserRoleInfo.getCurrentUser();
-      ByteArrayOutputStream bos = new ByteArrayOutputStream();
-      PrintWriter printWriter = new PrintWriter(bos);
-      TajoDump.dump(client, userInfo, getCurrentDatabase(), false, false, false, printWriter);
-      printWriter.flush();
-      printWriter.close();
-      assertStrings(new String(bos.toByteArray()));
-      bos.close();
-
-      executeString("DROP TABLE \"" + getCurrentDatabase() + "\".\"TableName1\"");
+      try {
+        UserRoleInfo userInfo = UserRoleInfo.getCurrentUser();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(bos);
+        TajoDump.dump(client, userInfo, getCurrentDatabase(), false, false, false, printWriter);
+        printWriter.flush();
+        printWriter.close();
+        assertStrings(new String(bos.toByteArray()));
+        bos.close();
+      } finally {
+        executeString("DROP TABLE \"" + getCurrentDatabase() + "\".\"TableName1\"");
+      }
     }
   }
 
@@ -52,16 +63,62 @@
       executeString("CREATE TABLE \"" + getCurrentDatabase() +
           "\".\"TableName2\" (\"Age\" int, \"Name\" Record (\"FirstName\" TEXT, lastname TEXT))");
 
-      UserRoleInfo userInfo = UserRoleInfo.getCurrentUser();
-      ByteArrayOutputStream bos = new ByteArrayOutputStream();
-      PrintWriter printWriter = new PrintWriter(bos);
-      TajoDump.dump(client, userInfo, getCurrentDatabase(), false, false, false, printWriter);
-      printWriter.flush();
-      printWriter.close();
-      assertStrings(new String(bos.toByteArray()));
-      bos.close();
-
-      executeString("DROP TABLE \"" + getCurrentDatabase() + "\".\"TableName2\"");
+      try {
+        UserRoleInfo userInfo = UserRoleInfo.getCurrentUser();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(bos);
+        TajoDump.dump(client, userInfo, getCurrentDatabase(), false, false, false, printWriter);
+        printWriter.flush();
+        printWriter.close();
+        assertStrings(new String(bos.toByteArray()));
+        bos.close();
+      } finally {
+        executeString("DROP TABLE \"" + getCurrentDatabase() + "\".\"TableName2\"");
+      }
     }
   }
+
+  @Test
+  public void testDump3() throws Exception {
+    if (!testingCluster.isHiveCatalogStoreRunning()) {
+      executeString("CREATE TABLE \"" + getCurrentDatabase() +
+          "\".\"TableName1\" (\"Age\" int, \"FirstName\" TEXT, lastname TEXT)");
+
+      executeString("CREATE INDEX test_idx on \"" + getCurrentDatabase()
+          + "\".\"TableName1\" ( \"Age\" asc null first, \"FirstName\" desc null last )");
+
+      try {
+        UserRoleInfo userInfo = UserRoleInfo.getCurrentUser();
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        PrintWriter printWriter = new PrintWriter(bos);
+        TajoDump.dump(client, userInfo, getCurrentDatabase(), false, false, false, printWriter);
+        printWriter.flush();
+        printWriter.close();
+        assertOutputResult("testDump3.result", new String(bos.toByteArray()), new String[]{"${index.path}"},
+            new String[]{TablespaceManager.getDefault().getTableUri(getCurrentDatabase(), "test_idx").toString()});
+        bos.close();
+      } finally {
+        executeString("DROP INDEX test_idx");
+        executeString("DROP TABLE \"" + getCurrentDatabase() + "\".\"TableName1\"");
+      }
+    }
+  }
+
+  private void assertOutputResult(String expectedResultFile, String actual, String[] paramKeys, String[] paramValues)
+      throws Exception {
+    FileSystem fs = currentResultPath.getFileSystem(testBase.getTestingCluster().getConfiguration());
+    Path resultFile = StorageUtil.concatPath(currentResultPath, expectedResultFile);
+    assertTrue(resultFile.toString() + " existence check", fs.exists(resultFile));
+
+    String expectedResult = FileUtil.readTextFile(new File(resultFile.toUri()));
+
+    if (paramKeys != null) {
+      for (int i = 0; i < paramKeys.length; i++) {
+        if (i < paramValues.length) {
+          expectedResult = expectedResult.replace(paramKeys[i], paramValues[i]);
+        }
+      }
+    }
+    assertEquals(expectedResult.trim(), actual.trim());
+  }
 }
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/eval/ExprTestBase.java b/tajo-core/src/test/java/org/apache/tajo/engine/eval/ExprTestBase.java
index 7629711..abd0973 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/eval/ExprTestBase.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/eval/ExprTestBase.java
@@ -35,8 +35,8 @@
 import org.apache.tajo.engine.function.FunctionLoader;
 import org.apache.tajo.engine.json.CoreGsonHelper;
 import org.apache.tajo.engine.parser.SQLAnalyzer;
+import org.apache.tajo.engine.query.QueryContext;
 import org.apache.tajo.exception.TajoException;
-import org.apache.tajo.exception.TajoInternalError;
 import org.apache.tajo.function.FunctionSignature;
 import org.apache.tajo.master.exec.QueryExecutor;
 import org.apache.tajo.plan.*;
@@ -44,9 +44,6 @@
 import org.apache.tajo.plan.expr.EvalNode;
 import org.apache.tajo.plan.serder.EvalNodeDeserializer;
 import org.apache.tajo.plan.serder.EvalNodeSerializer;
-import org.apache.tajo.engine.query.QueryContext;
-import org.apache.tajo.catalog.SchemaUtil;
-import org.apache.tajo.plan.serder.PlanProto;
 import org.apache.tajo.plan.verifier.LogicalPlanVerifier;
 import org.apache.tajo.plan.verifier.PreLogicalPlanVerifier;
 import org.apache.tajo.plan.verifier.VerificationState;
@@ -60,6 +57,7 @@
 import org.apache.tajo.util.datetime.DateTimeUtil;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.apache.tajo.plan.serder.PlanProto;
 
 import java.io.IOException;
 import java.util.List;
@@ -68,9 +66,7 @@
 
 import static org.apache.tajo.TajoConstants.DEFAULT_DATABASE_NAME;
 import static org.apache.tajo.TajoConstants.DEFAULT_TABLESPACE_NAME;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.*;
 
 public class ExprTestBase {
   private static TajoTestingCluster util;
@@ -106,7 +102,7 @@
     analyzer = new SQLAnalyzer();
     preLogicalPlanVerifier = new PreLogicalPlanVerifier(cat);
     planner = new LogicalPlanner(cat, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(util.getConfiguration());
+    optimizer = new LogicalOptimizer(util.getConfiguration(), cat);
     annotatedPlanVerifier = new LogicalPlanVerifier(util.getConfiguration(), cat);
   }
 
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestLogicalOptimizer.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestLogicalOptimizer.java
index 10e7e37..640d88b 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestLogicalOptimizer.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestLogicalOptimizer.java
@@ -104,9 +104,10 @@
     catalog.createFunction(funcDesc);
     sqlAnalyzer = new SQLAnalyzer();
     planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(util.getConfiguration());
+    optimizer = new LogicalOptimizer(util.getConfiguration(), catalog);
 
     defaultContext = LocalTajoTestingUtility.createDummyContext(util.getConfiguration());
+    optimizer = new LogicalOptimizer(util.getConfiguration(), catalog);
   }
 
   @AfterClass
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestLogicalPlanner.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestLogicalPlanner.java
index 097e232..02e921a 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestLogicalPlanner.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/TestLogicalPlanner.java
@@ -40,7 +40,6 @@
 import org.apache.tajo.engine.json.CoreGsonHelper;
 import org.apache.tajo.engine.parser.SQLAnalyzer;
 import org.apache.tajo.engine.query.QueryContext;
-import org.apache.tajo.plan.*;
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.plan.LogicalOptimizer;
 import org.apache.tajo.plan.LogicalPlan;
@@ -151,14 +150,14 @@
       "select name, empid, e.deptname, manager from employee as e, dept as dp", // 1
       "select name, empid, e.deptname, manager, score from employee as e, dept, score", // 2
       "select p.deptname, sumtest(score) from dept as p, score group by p.deptName having sumtest(score) > 30", // 3
-      "select p.deptname, score from dept as p, score order by score asc", // 4
+      "select p.deptname, score*200 from dept as p, score order by score*10 asc", // 4
       "select name from employee where empId = 100", // 5
       "select name, score from employee, score", // 6
       "select p.deptName, sumtest(score) from dept as p, score group by p.deptName", // 7
       "create table store1 as select p.deptName, sumtest(score) from dept as p, score group by p.deptName", // 8
       "select deptName, sumtest(score) from score group by deptName having sumtest(score) > 30", // 9
       "select 7 + 8 as res1, 8 * 9 as res2, 10 * 10 as res3", // 10
-      "create index idx_employee on employee using bitmap (name null first, empId desc) with ('fillfactor' = 70)", // 11
+      "create index idx_employee on employee using bitmap_idx (name null first, empId desc) where empid > 100", // 11
       "select name, score from employee, score order by score limit 3", // 12
       "select length(name), length(deptname), *, empid+10 from employee where empId > 500", // 13
   };
@@ -512,7 +511,7 @@
     Schema expected = tpch.getOutSchema("q2");
     assertSchema(expected, node.getOutSchema());
 
-    LogicalOptimizer optimizer = new LogicalOptimizer(util.getConfiguration());
+    LogicalOptimizer optimizer = new LogicalOptimizer(util.getConfiguration(), catalog);
     optimizer.optimize(plan);
 
     LogicalNode[] nodes = PlannerUtil.findAllNodes(node, NodeType.JOIN);
@@ -551,7 +550,7 @@
     LogicalNode node = plan.getRootBlock().getRoot();
     testJsonSerDerObject(node);
 
-    LogicalOptimizer optimizer = new LogicalOptimizer(util.getConfiguration());
+    LogicalOptimizer optimizer = new LogicalOptimizer(util.getConfiguration(), catalog);
     optimizer.optimize(plan);
 
     LogicalNode[] nodes = PlannerUtil.findAllNodes(node, NodeType.SCAN);
@@ -592,7 +591,7 @@
     LogicalNode node = plan.getRootBlock().getRoot();
     testJsonSerDerObject(node);
 
-    LogicalOptimizer optimizer = new LogicalOptimizer(util.getConfiguration());
+    LogicalOptimizer optimizer = new LogicalOptimizer(util.getConfiguration(), catalog);
     optimizer.optimize(plan);
 
     LogicalNode[] nodes = PlannerUtil.findAllNodes(node, NodeType.SCAN);
@@ -639,7 +638,7 @@
     LogicalNode node = plan.getRootBlock().getRoot();
     testJsonSerDerObject(node);
 
-    LogicalOptimizer optimizer = new LogicalOptimizer(util.getConfiguration());
+    LogicalOptimizer optimizer = new LogicalOptimizer(util.getConfiguration(), catalog);
     optimizer.optimize(plan);
 
     Map<BinaryEval, Boolean> scanMap = TUtil.newHashMap();
@@ -896,6 +895,29 @@
   }
 
   @Test
+  public final void testCreateIndexNode() throws TajoException {
+    QueryContext qc = new QueryContext(util.getConfiguration(), session);
+    Expr expr = sqlAnalyzer.parse(QUERIES[11]);
+    LogicalPlan rootNode = planner.createPlan(qc, expr);
+    LogicalNode plan = rootNode.getRootBlock().getRoot();
+    testJsonSerDerObject(plan);
+
+    LogicalRootNode root = (LogicalRootNode) plan;
+    assertEquals(NodeType.CREATE_INDEX, root.getChild().getType());
+    CreateIndexNode createIndexNode = root.getChild();
+
+    assertEquals(NodeType.PROJECTION, createIndexNode.getChild().getType());
+    ProjectionNode projNode = createIndexNode.getChild();
+
+    assertEquals(NodeType.SELECTION, projNode.getChild().getType());
+    SelectionNode selNode = projNode.getChild();
+
+    assertEquals(NodeType.SCAN, selNode.getChild().getType());
+    ScanNode scanNode = selNode.getChild();
+    assertEquals(CatalogUtil.buildFQName(DEFAULT_DATABASE_NAME, "employee"), scanNode.getTableName());
+  }
+
+  @Test
   public final void testAsterisk() throws CloneNotSupportedException, TajoException {
     QueryContext qc = createQueryContext();
 
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestBSTIndexExec.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestBSTIndexExec.java
deleted file mode 100644
index 036ca78..0000000
--- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestBSTIndexExec.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/**
- * 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.tajo.engine.planner.physical;
-
-import com.google.common.base.Preconditions;
-import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.fs.Path;
-import org.apache.tajo.LocalTajoTestingUtility;
-import org.apache.tajo.TajoConstants;
-import org.apache.tajo.TajoTestingCluster;
-import org.apache.tajo.algebra.Expr;
-import org.apache.tajo.catalog.*;
-import org.apache.tajo.common.TajoDataTypes.Type;
-import org.apache.tajo.conf.TajoConf;
-import org.apache.tajo.datum.Datum;
-import org.apache.tajo.datum.DatumFactory;
-import org.apache.tajo.engine.parser.SQLAnalyzer;
-import org.apache.tajo.plan.LogicalOptimizer;
-import org.apache.tajo.plan.LogicalPlan;
-import org.apache.tajo.plan.LogicalPlanner;
-import org.apache.tajo.engine.planner.PhysicalPlannerImpl;
-import org.apache.tajo.plan.logical.LogicalNode;
-import org.apache.tajo.plan.logical.ScanNode;
-import org.apache.tajo.engine.query.QueryContext;
-import org.apache.tajo.storage.*;
-import org.apache.tajo.storage.fragment.FileFragment;
-import org.apache.tajo.storage.fragment.FragmentConvertor;
-import org.apache.tajo.storage.index.bst.BSTIndex;
-import org.apache.tajo.util.CommonTestingUtil;
-import org.apache.tajo.worker.TaskAttemptContext;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Random;
-import java.util.Stack;
-
-import static org.apache.tajo.TajoConstants.DEFAULT_TABLESPACE_NAME;
-import static org.junit.Assert.assertEquals;
-
-public class TestBSTIndexExec {
-
-  private TajoConf conf;
-  private Path idxPath;
-  private CatalogService catalog;
-  private SQLAnalyzer analyzer;
-  private LogicalPlanner planner;
-  private LogicalOptimizer optimizer;
-  private FileTablespace sm;
-  private Schema idxSchema;
-  private BaseTupleComparator comp;
-  private BSTIndex.BSTIndexWriter writer;
-  private HashMap<Integer , Integer> randomValues ;
-  private int rndKey = -1;
-  private FileSystem fs;
-  private TableMeta meta;
-  private Path tablePath;
-
-  private Random rnd = new Random(System.currentTimeMillis());
-
-  private TajoTestingCluster util;
-
-  @Before
-  public void setup() throws Exception {
-    this.randomValues = new HashMap<Integer, Integer>();
-    this.conf = new TajoConf();
-    util = new TajoTestingCluster();
-    util.startCatalogCluster();
-    catalog = util.getMiniCatalogCluster().getCatalog();
-
-    Path workDir = CommonTestingUtil.getTestDir();
-    catalog.createTablespace(DEFAULT_TABLESPACE_NAME, workDir.toUri().toString());
-    catalog.createDatabase(TajoConstants.DEFAULT_DATABASE_NAME, DEFAULT_TABLESPACE_NAME);
-    sm = TablespaceManager.getLocalFs();
-
-    idxPath = new Path(workDir, "test.idx");
-
-    Schema schema = new Schema();
-    schema.addColumn("managerid", Type.INT4);
-    schema.addColumn("empid", Type.INT4);
-    schema.addColumn("deptname", Type.TEXT);
-
-    this.idxSchema = new Schema();
-    idxSchema.addColumn("managerid", Type.INT4);
-    SortSpec[] sortKeys = new SortSpec[1];
-    sortKeys[0] = new SortSpec(idxSchema.getColumn("managerid"), true, false);
-    this.comp = new BaseTupleComparator(idxSchema, sortKeys);
-
-    this.writer = new BSTIndex(conf).getIndexWriter(idxPath,
-        BSTIndex.TWO_LEVEL_INDEX, this.idxSchema, this.comp);
-    writer.setLoadNum(100);
-    writer.open();
-    long offset;
-
-    meta = CatalogUtil.newTableMeta("TEXT");
-    tablePath = StorageUtil.concatPath(workDir, "employee", "table.csv");
-    fs = tablePath.getFileSystem(conf);
-    fs.mkdirs(tablePath.getParent());
-
-    FileAppender appender = (FileAppender)sm.getAppender(meta, schema, tablePath);
-    appender.init();
-    VTuple tuple = new VTuple(schema.size());
-    for (int i = 0; i < 10000; i++) {
-      
-      VTuple key = new VTuple(this.idxSchema.size());
-      int rndKey = rnd.nextInt(250);
-      if(this.randomValues.containsKey(rndKey)) {
-        int t = this.randomValues.remove(rndKey) + 1;
-        this.randomValues.put(rndKey, t);
-      } else {
-        this.randomValues.put(rndKey, 1);
-      }
-      
-      key.put(new Datum[] { DatumFactory.createInt4(rndKey) });
-      tuple.put(new Datum[] { DatumFactory.createInt4(rndKey),
-          DatumFactory.createInt4(rnd.nextInt(10)),
-          DatumFactory.createText("dept_" + rnd.nextInt(10)) });
-      offset = appender.getOffset();
-      appender.addTuple(tuple);
-      writer.write(key, offset);
-    }
-    appender.flush();
-    appender.close();
-    writer.close();
-
-    TableDesc desc = new TableDesc(
-      CatalogUtil.buildFQName(TajoConstants.DEFAULT_DATABASE_NAME, "employee"), schema, meta,
-      tablePath.toUri());
-    catalog.createTable(desc);
-
-    analyzer = new SQLAnalyzer();
-    planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(conf);
-  }
-
-  @After
-  public void tearDown() {
-    util.shutdownCatalogCluster();
-  }
-
-  @Test
-  public void testEqual() throws Exception {
-    this.rndKey = rnd.nextInt(250);
-    final String QUERY = "select * from employee where managerId = " + rndKey;
-    
-    FileFragment[] frags = FileTablespace.splitNG(conf, "default.employee", meta, tablePath, Integer.MAX_VALUE);
-    Path workDir = CommonTestingUtil.getTestDir(TajoTestingCluster.DEFAULT_TEST_DIRECTORY + "/testEqual");
-    TaskAttemptContext ctx = new TaskAttemptContext(new QueryContext(conf),
-        LocalTajoTestingUtility.newTaskAttemptId(), new FileFragment[] { frags[0] }, workDir);
-    Expr expr = analyzer.parse(QUERY);
-    LogicalPlan plan = planner.createPlan(LocalTajoTestingUtility.createDummyContext(conf), expr);
-    LogicalNode rootNode = optimizer.optimize(plan);
-
-    TmpPlanner phyPlanner = new TmpPlanner(conf);
-    PhysicalExec exec = phyPlanner.createPlan(ctx, rootNode);
-
-    int tupleCount = this.randomValues.get(rndKey);
-    int counter = 0;
-    exec.init();
-    while (exec.next() != null) {
-      counter ++;
-    }
-    exec.close();
-    assertEquals(tupleCount , counter);
-  }
-
-  private class TmpPlanner extends PhysicalPlannerImpl {
-    public TmpPlanner(TajoConf conf) {
-      super(conf);
-    }
-
-    @Override
-    public PhysicalExec createScanPlan(TaskAttemptContext ctx, ScanNode scanNode, Stack<LogicalNode> stack)
-        throws IOException {
-      Preconditions.checkNotNull(ctx.getTable(scanNode.getTableName()),
-          "Error: There is no table matched to %s", scanNode.getTableName());
-
-      List<FileFragment> fragments = FragmentConvertor.convert(ctx.getConf(), ctx.getTables(scanNode.getTableName()));
-      
-      Datum[] datum = new Datum[]{DatumFactory.createInt4(rndKey)};
-
-      return new BSTIndexScanExec(ctx, scanNode, fragments.get(0), idxPath, idxSchema, comp , datum);
-
-    }
-  }
-}
\ No newline at end of file
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestHashAntiJoinExec.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestHashAntiJoinExec.java
index 292c414..b9ba2de 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestHashAntiJoinExec.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestHashAntiJoinExec.java
@@ -62,6 +62,7 @@
   private LogicalPlanner planner;
   private LogicalOptimizer optimizer;
   private Path testDir;
+  private QueryContext queryContext;
 
   private TableDesc employee;
   private TableDesc people;
@@ -126,11 +127,12 @@
     appender.flush();
     appender.close();
 
+    queryContext = new QueryContext(conf);
     people = CatalogUtil.newTableDesc("default.people", peopleSchema, peopleMeta, peoplePath);
     catalog.createTable(people);
     analyzer = new SQLAnalyzer();
     planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(conf);
+    optimizer = new LogicalOptimizer(conf, catalog);
   }
 
   @After
@@ -157,7 +159,7 @@
     FileFragment[] merged = TUtil.concat(empFrags, peopleFrags);
 
     Path workDir = CommonTestingUtil.getTestDir(TajoTestingCluster.DEFAULT_TEST_DIRECTORY + "/testHashAntiJoin");
-    TaskAttemptContext ctx = new TaskAttemptContext(new QueryContext(conf),
+    TaskAttemptContext ctx = new TaskAttemptContext(queryContext,
         LocalTajoTestingUtility.newTaskAttemptId(), merged, workDir);
     ctx.setEnforcer(new Enforcer());
     Expr expr = analyzer.parse(QUERIES[0]);
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestHashSemiJoinExec.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestHashSemiJoinExec.java
index 3880585..0d996de 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestHashSemiJoinExec.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestHashSemiJoinExec.java
@@ -62,6 +62,7 @@
   private LogicalPlanner planner;
   private LogicalOptimizer optimizer;
   private Path testDir;
+  private QueryContext queryContext;
 
   private TableDesc employee;
   private TableDesc people;
@@ -130,11 +131,12 @@
     appender.flush();
     appender.close();
 
+    queryContext = new QueryContext(conf);
     people = CatalogUtil.newTableDesc("default.people", peopleSchema, peopleMeta, peoplePath);
     catalog.createTable(people);
     analyzer = new SQLAnalyzer();
     planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(conf);
+    optimizer = new LogicalOptimizer(conf, catalog);
   }
 
   @After
@@ -161,7 +163,7 @@
     FileFragment[] merged = TUtil.concat(empFrags, peopleFrags);
 
     Path workDir = CommonTestingUtil.getTestDir(TajoTestingCluster.DEFAULT_TEST_DIRECTORY + "/testHashSemiJoin");
-    TaskAttemptContext ctx = new TaskAttemptContext(new QueryContext(conf),
+    TaskAttemptContext ctx = new TaskAttemptContext(queryContext,
         LocalTajoTestingUtility.newTaskAttemptId(), merged, workDir);
     ctx.setEnforcer(new Enforcer());
     Expr expr = analyzer.parse(QUERIES[0]);
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestPhysicalPlanner.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestPhysicalPlanner.java
index dae2351..0d4e6a4 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestPhysicalPlanner.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestPhysicalPlanner.java
@@ -162,12 +162,12 @@
     }
     appender.flush();
     appender.close();
+
+    defaultContext = LocalTajoTestingUtility.createDummyContext(conf);
     catalog.createTable(score);
     analyzer = new SQLAnalyzer();
     planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(conf);
-
-    defaultContext = LocalTajoTestingUtility.createDummyContext(conf);
+    optimizer = new LogicalOptimizer(conf, catalog);
     masterPlan = new MasterPlan(LocalTajoTestingUtility.newQueryId(), null, null);
 
     createLargeScoreTable();
@@ -970,17 +970,23 @@
   }
 
   public final String [] createIndexStmt = {
-      "create index idx_employee on employee using bst (name null first, empId desc)"
+      "create index idx_employee on employee using TWO_LEVEL_BIN_TREE (name null first, empId desc)"
   };
 
-  //@Test
+  @Test
   public final void testCreateIndex() throws IOException, TajoException {
     FileFragment[] frags = FileTablespace.splitNG(conf, "default.employee", employee.getMeta(),
         new Path(employee.getUri()), Integer.MAX_VALUE);
     Path workDir = CommonTestingUtil.getTestDir(TajoTestingCluster.DEFAULT_TEST_DIRECTORY + "/testCreateIndex");
+    Path indexPath = StorageUtil.concatPath(TajoConf.getWarehouseDir(conf), "default/idx_employee");
+    if (sm.getFileSystem().exists(indexPath)) {
+      sm.getFileSystem().delete(indexPath, true);
+    }
+
     TaskAttemptContext ctx = new TaskAttemptContext(new QueryContext(conf),
         LocalTajoTestingUtility.newTaskAttemptId(masterPlan),
         new FileFragment[] {frags[0]}, workDir);
+    ctx.setEnforcer(new Enforcer());
     Expr context = analyzer.parse(createIndexStmt[0]);
     LogicalPlan plan = planner.createPlan(defaultContext, context);
     LogicalNode rootNode = optimizer.optimize(plan);
@@ -992,7 +998,7 @@
     }
     exec.close();
 
-    FileStatus [] list = sm.getFileSystem().listStatus(StorageUtil.concatPath(workDir, "index"));
+    FileStatus[] list = sm.getFileSystem().listStatus(indexPath);
     assertEquals(2, list.length);
   }
 
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestSortExec.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestSortExec.java
index 743c51d..4df383e 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestSortExec.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestSortExec.java
@@ -66,6 +66,7 @@
   private static Path workDir;
   private static Path tablePath;
   private static TableMeta employeeMeta;
+  private static QueryContext queryContext;
 
   private static Random rnd = new Random(System.currentTimeMillis());
 
@@ -106,9 +107,10 @@
         tablePath.toUri());
     catalog.createTable(desc);
 
+    queryContext = new QueryContext(conf);
     analyzer = new SQLAnalyzer();
     planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(conf);
+    optimizer = new LogicalOptimizer(conf, catalog);
   }
 
   public static String[] QUERIES = {
@@ -118,7 +120,7 @@
   public final void testNext() throws IOException, TajoException {
     FileFragment[] frags = FileTablespace.splitNG(conf, "default.employee", employeeMeta, tablePath, Integer.MAX_VALUE);
     Path workDir = CommonTestingUtil.getTestDir(TajoTestingCluster.DEFAULT_TEST_DIRECTORY + "/TestSortExec");
-    TaskAttemptContext ctx = new TaskAttemptContext(new QueryContext(conf),
+    TaskAttemptContext ctx = new TaskAttemptContext(queryContext,
         LocalTajoTestingUtility
         .newTaskAttemptId(), new FileFragment[] { frags[0] }, workDir);
     ctx.setEnforcer(new Enforcer());
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestSortIntersectExec.java b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestSortIntersectExec.java
index 440eb31..7289472 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestSortIntersectExec.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/planner/physical/TestSortIntersectExec.java
@@ -140,7 +140,7 @@
 
     analyzer = new SQLAnalyzer();
     planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(conf);
+    optimizer = new LogicalOptimizer(conf, catalog);
   }
 
   @After
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestCreateIndex.java b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestCreateIndex.java
new file mode 100644
index 0000000..1a55870
--- /dev/null
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestCreateIndex.java
@@ -0,0 +1,113 @@
+/*
+ * 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.tajo.engine.query;
+
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+import org.apache.tajo.IntegrationTest;
+import org.apache.tajo.QueryTestCaseBase;
+import org.apache.tajo.TajoConstants;
+import org.apache.tajo.catalog.IndexDesc;
+import org.apache.tajo.conf.TajoConf.ConfVars;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+@Category(IntegrationTest.class)
+public class TestCreateIndex extends QueryTestCaseBase {
+
+  public TestCreateIndex() {
+    super(TajoConstants.DEFAULT_DATABASE_NAME);
+  }
+
+  private static void assertIndexNotExist(String databaseName, String indexName) throws IOException {
+    Path indexPath = new Path(conf.getVar(ConfVars.WAREHOUSE_DIR), databaseName + "/" + indexName);
+    FileSystem fs = indexPath.getFileSystem(conf);
+    if (fs.exists(indexPath)) {
+      fs.deleteOnExit(indexPath);
+      assertFalse("Index is not deleted from the file system.", true);
+    }
+  }
+
+  @Test
+  public final void testCreateIndex() throws Exception {
+    executeQuery();
+    assertTrue(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_idx"));
+    assertTrue(catalog.existIndexByColumnNames(getCurrentDatabase(), "lineitem", new String[]{"l_orderkey"}));
+    executeString("drop index l_orderkey_idx");
+    assertFalse(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_idx"));
+    assertIndexNotExist(getCurrentDatabase(), "l_orderkey_idx");
+  }
+
+  @Test
+  public final void testCreateIndexOnMultiAttrs() throws Exception {
+    executeQuery();
+    assertTrue(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_partkey_idx"));
+    assertTrue(catalog.existIndexByColumnNames(getCurrentDatabase(), "lineitem", new String[]{"l_orderkey", "l_partkey"}));
+    executeString("drop index l_orderkey_partkey_idx");
+    assertFalse(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_partkey_idx"));
+    assertIndexNotExist(getCurrentDatabase(), "l_orderkey_partkey_idx");
+  }
+
+  @Test
+  public final void testCreateIndexWithCondition() throws Exception {
+    executeQuery();
+    assertTrue(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_partkey_lt10_idx"));
+    assertTrue(catalog.existIndexByColumnNames(getCurrentDatabase(), "lineitem", new String[]{"l_orderkey", "l_partkey"}));
+    executeString("drop index l_orderkey_partkey_lt10_idx");
+    assertFalse(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_partkey_lt10_idx"));
+    assertIndexNotExist(getCurrentDatabase(), "l_orderkey_partkey_lt10_idx");
+  }
+
+  @Test
+  public final void testCreateIndexOnExpression() throws Exception {
+    executeQuery();
+    assertTrue(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_100_lt10_idx"));
+    executeString("drop index l_orderkey_100_lt10_idx");
+    assertFalse(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_100_lt10_idx"));
+    assertIndexNotExist(getCurrentDatabase(), "l_orderkey_100_lt10_idx");
+  }
+
+  @Test
+  public final void testCreateIndexOnMultiExprs() throws Exception {
+    executeQuery();
+    assertTrue(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_100_l_linenumber_10_lt10_idx"));
+    executeString("drop index l_orderkey_100_l_linenumber_10_lt10_idx");
+    assertFalse(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_100_l_linenumber_10_lt10_idx"));
+    assertIndexNotExist(getCurrentDatabase(), "l_orderkey_100_l_linenumber_10_lt10_idx");
+  }
+
+  @Test
+  public final void testCreateIndexOnLocation() throws Exception {
+    executeQuery();
+    assertTrue(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_idx"));
+    assertTrue(catalog.existIndexByColumnNames(getCurrentDatabase(), "lineitem", new String[]{"l_orderkey"}));
+    catalog.dropIndex(getCurrentDatabase(), "l_orderkey_idx");
+    assertFalse(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_idx"));
+    executeString("create index l_orderkey_idx on lineitem (l_orderkey asc null first) location '/tajo/warehouse/default/l_orderkey_idx';");
+    assertTrue(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_idx"));
+    assertTrue(catalog.existIndexByColumnNames(getCurrentDatabase(), "lineitem", new String[]{"l_orderkey"}));
+    executeString("drop index l_orderkey_idx");
+    assertFalse(catalog.existIndexByName(getCurrentDatabase(), "l_orderkey_idx"));
+    assertIndexNotExist(getCurrentDatabase(), "l_orderkey_idx");
+  }
+}
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestIndexScan.java b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestIndexScan.java
new file mode 100644
index 0000000..75127d2
--- /dev/null
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestIndexScan.java
@@ -0,0 +1,144 @@
+/*
+ * 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.tajo.engine.query;
+
+import com.google.protobuf.ServiceException;
+import org.apache.tajo.IntegrationTest;
+import org.apache.tajo.QueryTestCaseBase;
+import org.apache.tajo.SessionVars;
+import org.apache.tajo.TajoConstants;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
+
+@Category(IntegrationTest.class)
+public class TestIndexScan extends QueryTestCaseBase {
+
+  public TestIndexScan() throws ServiceException, SQLException {
+    super(TajoConstants.DEFAULT_DATABASE_NAME);
+    Map<String,String> sessionVars = new HashMap<String, String>();
+    sessionVars.put(SessionVars.INDEX_ENABLED.keyname(), "true");
+    sessionVars.put(SessionVars.INDEX_SELECTIVITY_THRESHOLD.keyname(), "0.01f");
+    client.updateSessionVariables(sessionVars);
+  }
+
+  @Test
+  public final void testOnSortedNonUniqueKeys() throws Exception {
+    executeString("create index l_orderkey_idx on lineitem (l_orderkey)");
+    try {
+      ResultSet res = executeString("select * from lineitem where l_orderkey = 1;");
+      assertResultSet(res);
+      cleanupQuery(res);
+    } finally {
+      executeString("drop index l_orderkey_idx");
+    }
+  }
+
+  @Test
+  public final void testOnUnsortedTextKeys() throws Exception {
+    executeString("create index l_shipdate_idx on lineitem (l_shipdate)");
+    try {
+      ResultSet res = executeString("select l_orderkey, l_shipdate, l_comment from lineitem where l_shipdate = '1997-01-28';");
+      assertResultSet(res);
+      cleanupQuery(res);
+    } finally {
+      executeString("drop index l_shipdate_idx");
+    }
+  }
+
+  @Test
+  public final void testOnMultipleKeys() throws Exception {
+    executeString("create index multikey_idx on lineitem (l_shipdate asc null last, l_tax desc null first, l_shipmode, l_linenumber desc null last)");
+    try {
+      ResultSet res = executeString("select l_orderkey, l_shipdate, l_comment from lineitem " +
+          "where l_shipdate = '1997-01-28' and l_tax = 0.05 and l_shipmode = 'RAIL' and l_linenumber = 1;");
+      assertResultSet(res);
+      cleanupQuery(res);
+    } finally {
+      executeString("drop index multikey_idx");
+    }
+  }
+
+  @Test
+  public final void testOnMultipleKeys2() throws Exception {
+    executeString("create index multikey_idx on lineitem (l_shipdate asc null last, l_tax desc null first)");
+    try {
+      ResultSet res = executeString("select l_orderkey, l_shipdate, l_comment from lineitem " +
+          "where l_shipdate = '1997-01-28' and l_tax = 0.05 and l_shipmode = 'RAIL' and l_linenumber = 1;");
+      assertResultSet(res);
+      cleanupQuery(res);
+    } finally {
+      executeString("drop index multikey_idx");
+    }
+  }
+
+  @Test
+  public final void testOnMultipleExprs() throws Exception {
+    executeString("create index l_orderkey_100_l_linenumber_10_idx on lineitem (l_orderkey*100-l_linenumber*10 asc null first);");
+    try {
+      ResultSet res = executeString("select l_orderkey, l_linenumber from lineitem where l_orderkey*100-l_linenumber*10 = 280");
+      assertResultSet(res);
+      cleanupQuery(res);
+    } finally {
+      executeString("drop index l_orderkey_100_l_linenumber_10_idx");
+    }
+  }
+
+  @Test
+  public final void testWithGroupBy() throws Exception {
+    executeString("create index l_shipdate_idx on lineitem (l_shipdate)");
+    try {
+      ResultSet res = executeString("select l_shipdate, count(*) from lineitem where l_shipdate = '1997-01-28' group by l_shipdate;");
+      assertResultSet(res);
+      cleanupQuery(res);
+    } finally {
+      executeString("drop index l_shipdate_idx");
+    }
+  }
+
+  @Test
+  public final void testWithSort() throws Exception {
+    executeString("create index l_orderkey_idx on lineitem (l_orderkey)");
+    try {
+      ResultSet res = executeString("select l_shipdate from lineitem where l_orderkey = 1 order by l_shipdate;");
+      assertResultSet(res);
+      cleanupQuery(res);
+    } finally {
+      executeString("drop index l_orderkey_idx");
+    }
+  }
+
+  @Test
+  public final void testWithJoin() throws Exception {
+    executeString("create index l_orderkey_idx on lineitem (l_orderkey)");
+    executeString("create index o_orderkey_idx on orders (o_orderkey)");
+    try {
+      ResultSet res = executeString("select l_shipdate, o_orderstatus from lineitem, orders where l_orderkey = o_orderkey and l_orderkey = 1 and o_orderkey = 1;");
+      assertResultSet(res);
+      cleanupQuery(res);
+    } finally {
+      executeString("drop index l_orderkey_idx");
+      executeString("drop index o_orderkey_idx");
+    }
+  }
+}
diff --git a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestTablePartitions.java b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestTablePartitions.java
index 265c726..d18db71 100644
--- a/tajo-core/src/test/java/org/apache/tajo/engine/query/TestTablePartitions.java
+++ b/tajo-core/src/test/java/org/apache/tajo/engine/query/TestTablePartitions.java
@@ -19,7 +19,10 @@
 package org.apache.tajo.engine.query;
 
 import com.google.common.collect.Maps;
-import org.apache.hadoop.fs.*;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.io.compress.CompressionCodec;
 import org.apache.hadoop.io.compress.CompressionCodecFactory;
 import org.apache.hadoop.io.compress.DeflateCodec;
@@ -31,12 +34,12 @@
 import org.apache.tajo.catalog.CatalogUtil;
 import org.apache.tajo.catalog.Schema;
 import org.apache.tajo.catalog.TableDesc;
-import org.apache.tajo.exception.ReturnStateUtil;
 import org.apache.tajo.common.TajoDataTypes;
 import org.apache.tajo.conf.TajoConf;
 import org.apache.tajo.engine.planner.global.DataChannel;
 import org.apache.tajo.engine.planner.global.ExecutionBlock;
 import org.apache.tajo.engine.planner.global.MasterPlan;
+import org.apache.tajo.exception.ReturnStateUtil;
 import org.apache.tajo.ipc.ClientProtos;
 import org.apache.tajo.jdbc.FetchResultSet;
 import org.apache.tajo.jdbc.TajoMemoryResultSet;
@@ -57,7 +60,6 @@
 import static org.apache.tajo.TajoConstants.DEFAULT_DATABASE_NAME;
 import static org.apache.tajo.plan.serder.PlanProto.ShuffleType.SCATTERED_HASH_SHUFFLE;
 import static org.junit.Assert.*;
-import static org.junit.Assert.assertEquals;
 
 @RunWith(Parameterized.class)
 public class TestTablePartitions extends QueryTestCaseBase {
diff --git a/tajo-core/src/test/java/org/apache/tajo/master/TestExecutionBlockCursor.java b/tajo-core/src/test/java/org/apache/tajo/master/TestExecutionBlockCursor.java
index 169f375..b139645 100644
--- a/tajo-core/src/test/java/org/apache/tajo/master/TestExecutionBlockCursor.java
+++ b/tajo-core/src/test/java/org/apache/tajo/master/TestExecutionBlockCursor.java
@@ -80,7 +80,7 @@
 
     analyzer = new SQLAnalyzer();
     logicalPlanner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    optimizer = new LogicalOptimizer(conf);
+    optimizer = new LogicalOptimizer(conf, catalog);
 
     dispatcher = new AsyncDispatcher();
     dispatcher.init(conf);
diff --git a/tajo-core/src/test/java/org/apache/tajo/querymaster/TestKillQuery.java b/tajo-core/src/test/java/org/apache/tajo/querymaster/TestKillQuery.java
index 8738dba..0e3e63e 100644
--- a/tajo-core/src/test/java/org/apache/tajo/querymaster/TestKillQuery.java
+++ b/tajo-core/src/test/java/org/apache/tajo/querymaster/TestKillQuery.java
@@ -100,7 +100,7 @@
     CatalogService catalog = cluster.getMaster().getCatalog();
 
     LogicalPlanner planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    LogicalOptimizer optimizer = new LogicalOptimizer(conf);
+    LogicalOptimizer optimizer = new LogicalOptimizer(conf, catalog);
     Expr expr =  analyzer.parse(queryStr);
     LogicalPlan plan = planner.createPlan(defaultContext, expr);
 
@@ -164,7 +164,7 @@
     CatalogService catalog = cluster.getMaster().getCatalog();
 
     LogicalPlanner planner = new LogicalPlanner(catalog, TablespaceManager.getInstance());
-    LogicalOptimizer optimizer = new LogicalOptimizer(conf);
+    LogicalOptimizer optimizer = new LogicalOptimizer(conf, catalog);
     Expr expr =  analyzer.parse(queryStr);
     LogicalPlan plan = planner.createPlan(defaultContext, expr);
 
diff --git a/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndex.sql b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndex.sql
new file mode 100644
index 0000000..1cb8936
--- /dev/null
+++ b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndex.sql
@@ -0,0 +1 @@
+create index l_orderkey_idx on lineitem (l_orderkey asc null first);
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnExpression.sql b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnExpression.sql
new file mode 100644
index 0000000..0d19cb3
--- /dev/null
+++ b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnExpression.sql
@@ -0,0 +1 @@
+create index l_orderkey_100_lt10_idx on lineitem (l_orderkey*100 asc null first) where l_orderkey*100 > 10;
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnLocation.sql b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnLocation.sql
new file mode 100644
index 0000000..1cb8936
--- /dev/null
+++ b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnLocation.sql
@@ -0,0 +1 @@
+create index l_orderkey_idx on lineitem (l_orderkey asc null first);
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnMultiAttrs.sql b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnMultiAttrs.sql
new file mode 100644
index 0000000..3487d2e
--- /dev/null
+++ b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnMultiAttrs.sql
@@ -0,0 +1 @@
+create index l_orderkey_partkey_idx on lineitem (l_orderkey asc, l_partkey desc null last);
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnMultiExprs.sql b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnMultiExprs.sql
new file mode 100644
index 0000000..7938005
--- /dev/null
+++ b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexOnMultiExprs.sql
@@ -0,0 +1 @@
+create index l_orderkey_100_l_linenumber_10_lt10_idx on lineitem (l_orderkey*100-l_linenumber*10 asc null first) where l_orderkey*100 > 10;
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexWithCondition.sql b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexWithCondition.sql
new file mode 100644
index 0000000..ad4ee47
--- /dev/null
+++ b/tajo-core/src/test/resources/queries/TestCreateIndex/testCreateIndexWithCondition.sql
@@ -0,0 +1 @@
+create index l_orderkey_partkey_lt10_idx on lineitem (l_orderkey asc, l_partkey desc) where l_partkey > 10;
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleExprs.result b/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleExprs.result
new file mode 100644
index 0000000..bcb645d
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleExprs.result
@@ -0,0 +1,3 @@
+l_orderkey,l_linenumber
+-------------------------------
+3,2
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleKeys.result b/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleKeys.result
new file mode 100644
index 0000000..86d468e
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleKeys.result
@@ -0,0 +1,3 @@
+l_orderkey,l_shipdate,l_comment
+-------------------------------
+2,1997-01-28,ven requests. deposits breach a
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleKeys2.result b/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleKeys2.result
new file mode 100644
index 0000000..86d468e
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestIndexScan/testOnMultipleKeys2.result
@@ -0,0 +1,3 @@
+l_orderkey,l_shipdate,l_comment
+-------------------------------
+2,1997-01-28,ven requests. deposits breach a
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestIndexScan/testOnSortedNonUniqueKeys.result b/tajo-core/src/test/resources/results/TestIndexScan/testOnSortedNonUniqueKeys.result
new file mode 100644
index 0000000..fb8a4c2
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestIndexScan/testOnSortedNonUniqueKeys.result
@@ -0,0 +1,4 @@
+l_orderkey,l_partkey,l_suppkey,l_linenumber,l_quantity,l_extendedprice,l_discount,l_tax,l_returnflag,l_linestatus,l_shipdate,l_commitdate,l_receiptdate,l_shipinstruct,l_shipmode,l_comment
+-------------------------------
+1,1,7706,1,17.0,21168.23,0.04,0.02,N,O,1996-03-13,1996-02-12,1996-03-22,DELIVER IN PERSON,TRUCK,egular courts above the
+1,1,7311,2,36.0,45983.16,0.09,0.06,N,O,1996-04-12,1996-02-28,1996-04-20,TAKE BACK RETURN,MAIL,ly final dependencies: slyly bold
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestIndexScan/testOnUnsortedTextKeys.result b/tajo-core/src/test/resources/results/TestIndexScan/testOnUnsortedTextKeys.result
new file mode 100644
index 0000000..86d468e
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestIndexScan/testOnUnsortedTextKeys.result
@@ -0,0 +1,3 @@
+l_orderkey,l_shipdate,l_comment
+-------------------------------
+2,1997-01-28,ven requests. deposits breach a
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestIndexScan/testWithGroupBy.result b/tajo-core/src/test/resources/results/TestIndexScan/testWithGroupBy.result
new file mode 100644
index 0000000..2d1d12e
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestIndexScan/testWithGroupBy.result
@@ -0,0 +1,3 @@
+l_shipdate,?count
+-------------------------------
+1997-01-28,1
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestIndexScan/testWithJoin.result b/tajo-core/src/test/resources/results/TestIndexScan/testWithJoin.result
new file mode 100644
index 0000000..d119969
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestIndexScan/testWithJoin.result
@@ -0,0 +1,4 @@
+l_shipdate,o_orderstatus
+-------------------------------
+1996-03-13,O
+1996-04-12,O
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestIndexScan/testWithSort.result b/tajo-core/src/test/resources/results/TestIndexScan/testWithSort.result
new file mode 100644
index 0000000..774a411
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestIndexScan/testWithSort.result
@@ -0,0 +1,4 @@
+l_shipdate
+-------------------------------
+1996-03-13
+1996-04-12
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestTajoCli/testHelpSessionVars.result b/tajo-core/src/test/resources/results/TestTajoCli/testHelpSessionVars.result
index 5c2ffe3..1f878f1 100644
--- a/tajo-core/src/test/resources/results/TestTajoCli/testHelpSessionVars.result
+++ b/tajo-core/src/test/resources/results/TestTajoCli/testHelpSessionVars.result
@@ -35,6 +35,8 @@
 \set MAX_OUTPUT_FILE_SIZE [int value] - Maximum per-output file size (mb). 0 means infinite.
 \set NULL_CHAR [text value] - null char of text file output
 \set CODEGEN [true or false] - Runtime code generation enabled (experiment)
+\set INDEX_ENABLED [true or false] - index scan enabled
+\set INDEX_SELECTIVITY_THRESHOLD [real value] - the selectivity threshold for index scan
 \set PARTITION_NO_RESULT_OVERWRITE_ENABLED [true or false] - If True, a partitioned table is overwritten even if a sub query leads to no result. Otherwise, the table data will be kept if there is no result
 \set ARITHABORT [true or false] - If true, a running query will be terminated when an overflow or divide-by-zero occurs.
 \set FETCH_ROWNUM [int value] - Sets the number of rows at a time from Master
diff --git a/tajo-core/src/test/resources/results/TestTajoDump/testDump1.result b/tajo-core/src/test/resources/results/TestTajoDump/testDump1.result
index 8accece..613093b 100644
--- a/tajo-core/src/test/resources/results/TestTajoDump/testDump1.result
+++ b/tajo-core/src/test/resources/results/TestTajoDump/testDump1.result
@@ -12,5 +12,4 @@
 --
 -- Name: "TestTajoDump"."TableName1"; Type: TABLE; Storage: TEXT
 --
-CREATE TABLE "TestTajoDump"."TableName1" ("Age" INT4, "FirstName" TEXT, lastname TEXT) USING TEXT WITH ('text.delimiter'='|');
-
+CREATE TABLE "TestTajoDump"."TableName1" ("Age" INT4, "FirstName" TEXT, lastname TEXT) USING TEXT WITH ('text.delimiter'='|');
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestTajoDump/testDump2.result b/tajo-core/src/test/resources/results/TestTajoDump/testDump2.result
index 787562e..4c96233 100644
--- a/tajo-core/src/test/resources/results/TestTajoDump/testDump2.result
+++ b/tajo-core/src/test/resources/results/TestTajoDump/testDump2.result
@@ -12,5 +12,4 @@
 --
 -- Name: "TestTajoDump"."TableName2"; Type: TABLE; Storage: TEXT
 --
-CREATE TABLE "TestTajoDump"."TableName2" ("Age" INT4, "Name" RECORD ("FirstName" TEXT, lastname TEXT)) USING TEXT WITH ('text.delimiter'='|');
-
+CREATE TABLE "TestTajoDump"."TableName2" ("Age" INT4, "Name" RECORD ("FirstName" TEXT, lastname TEXT)) USING TEXT WITH ('text.delimiter'='|');
\ No newline at end of file
diff --git a/tajo-core/src/test/resources/results/TestTajoDump/testDump3.result b/tajo-core/src/test/resources/results/TestTajoDump/testDump3.result
new file mode 100644
index 0000000..72a42c7
--- /dev/null
+++ b/tajo-core/src/test/resources/results/TestTajoDump/testDump3.result
@@ -0,0 +1,18 @@
+--
+-- Tajo database dump
+--
+
+
+--
+-- Database name: "TestTajoDump"
+--
+
+CREATE DATABASE IF NOT EXISTS "TestTajoDump";
+
+--
+-- Name: "TestTajoDump"."TableName1"; Type: TABLE; Storage: TEXT
+--
+CREATE TABLE "TestTajoDump"."TableName1" ("Age" INT4, "FirstName" TEXT, lastname TEXT) USING TEXT WITH ('text.delimiter'='|');--
+-- Name: test_idx; Type: INDEX; Index Method: TWO_LEVEL_BIN_TREE
+--
+CREATE INDEX test_idx on "TableName1" ( TestTajoDump.TableName1.Age asc null first, TestTajoDump.TableName1.FirstName desc null last )  location '${index.path}';
\ No newline at end of file
diff --git a/tajo-docs/src/main/sphinx/index/how_to_use.rst b/tajo-docs/src/main/sphinx/index/how_to_use.rst
index 09e1b72..c67af66 100644
--- a/tajo-docs/src/main/sphinx/index/how_to_use.rst
+++ b/tajo-docs/src/main/sphinx/index/how_to_use.rst
@@ -66,4 +66,4 @@
 
 .. note::
 
-     Once the index scan feature is enabled, Tajo currently always performs the index scan regardless of its efficiency. You should set this option when the expected number of retrieved tuples is sufficiently small.
\ No newline at end of file
+     Once the index scan feature is enabled, Tajo currently always performs the index scan regardless of its efficiency. You should set this option when the expected number of retrieved tuples is sufficiently small.
diff --git a/tajo-jdbc/src/main/java/org/apache/tajo/jdbc/TajoStatement.java b/tajo-jdbc/src/main/java/org/apache/tajo/jdbc/TajoStatement.java
index 60f7ca3..644dfc7 100644
--- a/tajo-jdbc/src/main/java/org/apache/tajo/jdbc/TajoStatement.java
+++ b/tajo-jdbc/src/main/java/org/apache/tajo/jdbc/TajoStatement.java
@@ -20,9 +20,9 @@
 import com.google.common.collect.Lists;
 import org.apache.tajo.QueryId;
 import org.apache.tajo.SessionVars;
-import org.apache.tajo.exception.SQLExceptionUtil;
 import org.apache.tajo.client.TajoClient;
 import org.apache.tajo.client.TajoClientUtil;
+import org.apache.tajo.exception.SQLExceptionUtil;
 import org.apache.tajo.ipc.ClientProtos;
 
 import java.sql.*;
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalOptimizer.java b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalOptimizer.java
index 2bfa4a7..451ab5e 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalOptimizer.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalOptimizer.java
@@ -26,6 +26,7 @@
 import org.apache.tajo.OverridableConf;
 import org.apache.tajo.SessionVars;
 import org.apache.tajo.algebra.JoinType;
+import org.apache.tajo.catalog.CatalogService;
 import org.apache.tajo.conf.TajoConf;
 import org.apache.tajo.conf.TajoConf.ConfVars;
 import org.apache.tajo.exception.TajoException;
@@ -36,6 +37,7 @@
 import org.apache.tajo.plan.joinorder.*;
 import org.apache.tajo.plan.logical.*;
 import org.apache.tajo.plan.rewrite.BaseLogicalPlanRewriteEngine;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
 import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleProvider;
 import org.apache.tajo.plan.util.PlannerUtil;
 import org.apache.tajo.plan.visitor.BasicLogicalPlanVisitor;
@@ -55,12 +57,15 @@
 public class LogicalOptimizer {
   private static final Log LOG = LogFactory.getLog(LogicalOptimizer.class.getName());
 
+  private CatalogService catalog;
   private BaseLogicalPlanRewriteEngine rulesBeforeJoinOpt;
   private BaseLogicalPlanRewriteEngine rulesAfterToJoinOpt;
   private JoinOrderAlgorithm joinOrderAlgorithm = new GreedyHeuristicJoinOrderAlgorithm();
 
-  public LogicalOptimizer(TajoConf conf) {
+  public LogicalOptimizer(TajoConf conf, CatalogService catalog) {
 
+    this.catalog = catalog;
+    // TODO: set the catalog instance to FilterPushdownRule
     Class clazz = conf.getClassVar(ConfVars.LOGICAL_PLAN_REWRITE_RULE_PROVIDER_CLASS);
     LogicalPlanRewriteRuleProvider provider = (LogicalPlanRewriteRuleProvider) ReflectionUtil.newInstance(clazz, conf);
 
@@ -78,7 +83,7 @@
   }
 
   public LogicalNode optimize(OverridableConf context, LogicalPlan plan) throws TajoException {
-    rulesBeforeJoinOpt.rewrite(context, plan);
+    rulesBeforeJoinOpt.rewrite(new LogicalPlanRewriteRuleContext(context, plan, catalog));
 
     DirectedGraphCursor<String, BlockEdge> blockCursor =
         new DirectedGraphCursor<String, BlockEdge>(plan.getQueryBlockGraph(), plan.getRootBlock().getName());
@@ -91,7 +96,7 @@
     } else {
       LOG.info("Skip join order optimization");
     }
-    rulesAfterToJoinOpt.rewrite(context, plan);
+    rulesAfterToJoinOpt.rewrite(new LogicalPlanRewriteRuleContext(context, plan, catalog));
     return plan.getRootBlock().getRoot();
   }
 
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlan.java b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlan.java
index 9cc1e78..eab939d 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlan.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlan.java
@@ -27,6 +27,7 @@
 import org.apache.tajo.catalog.Schema;
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.exception.TajoInternalError;
+import org.apache.tajo.plan.expr.AlgebraicUtil.IdentifiableNameBuilder;
 import org.apache.tajo.plan.expr.ConstEval;
 import org.apache.tajo.plan.expr.EvalNode;
 import org.apache.tajo.plan.logical.LogicalNode;
@@ -35,6 +36,7 @@
 import org.apache.tajo.plan.logical.RelationNode;
 import org.apache.tajo.plan.nameresolver.NameResolver;
 import org.apache.tajo.plan.nameresolver.NameResolvingMode;
+import org.apache.tajo.plan.rewrite.rules.AccessPathInfo;
 import org.apache.tajo.plan.visitor.ExplainLogicalPlanVisitor;
 import org.apache.tajo.util.TUtil;
 import org.apache.tajo.util.graph.DirectedGraphCursor;
@@ -168,6 +170,8 @@
   /**
    * It generates an unique column name from Expr. It is usually used for an expression or predicate without
    * a specified name (i.e., alias).
+   * Here, some expressions require to be identified with their names in the future.
+   * For example, expressions must be identifiable with their names when getting targets in {@link LogicalPlanner#visitCreateIndex}.
    */
   public String generateUniqueColumnName(Expr expr) {
     String generatedName;
@@ -179,6 +183,11 @@
     return generatedName;
   }
 
+  private String generateUniqueIdentifiableColumnName(Expr expr) {
+    IdentifiableNameBuilder nameBuilder = new IdentifiableNameBuilder(expr);
+    return nameBuilder.build();
+  }
+
   /**
    * It attaches a generated column name with a sequence id. It always keeps generated names unique.
    */
@@ -434,6 +443,7 @@
     private final Map<String, String> columnAliasMap = TUtil.newHashMap();
     private final Map<OpType, List<Expr>> operatorToExprMap = TUtil.newHashMap();
     private final List<RelationNode> relationList = TUtil.newList();
+    private final Map<Integer, List<AccessPathInfo>> relNodePidAccessPathMap = TUtil.newHashMap();
     private boolean hasWindowFunction = false;
     private final Map<String, ConstEval> constantPoolByRef = Maps.newHashMap();
     private final Map<Expr, String> constantPool = Maps.newHashMap();
@@ -522,12 +532,30 @@
       }
       canonicalNameToRelationMap.put(relation.getCanonicalName(), relation);
       relationList.add(relation);
+      relNodePidAccessPathMap.put(relation.getPID(), new ArrayList<AccessPathInfo>());
+    }
+
+    public void addRelation(RelationNode relation, List<AccessPathInfo> accessPathInfos) {
+      if (relation.hasAlias()) {
+        TUtil.putToNestedList(relationAliasMap, relation.getTableName(), relation.getCanonicalName());
+      }
+      canonicalNameToRelationMap.put(relation.getCanonicalName(), relation);
+      relationList.add(relation);
+      relNodePidAccessPathMap.put(relation.getPID(), new ArrayList<AccessPathInfo>());
+    }
+
+    public void addAccessPath(RelationNode relation, AccessPathInfo accessPathInfo) {
+      relNodePidAccessPathMap.get(relation.getPID()).add(accessPathInfo);
     }
 
     public Collection<RelationNode> getRelations() {
       return Collections.unmodifiableList(relationList);
     }
 
+    public List<AccessPathInfo> getAccessInfos(RelationNode relation) {
+      return Collections.unmodifiableList(relNodePidAccessPathMap.get(relation.getPID()));
+    }
+
     public boolean hasTableExpression() {
       return this.canonicalNameToRelationMap.size() > 0;
     }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanPreprocessor.java b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanPreprocessor.java
index 0a8d1c4..f3743ee 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanPreprocessor.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanPreprocessor.java
@@ -473,6 +473,23 @@
   }
 
   @Override
+  public LogicalNode visitCreateIndex(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, CreateIndex expr)
+      throws TajoException {
+    stack.push(expr);
+    LogicalNode child = visit(ctx, stack, expr.getChild());
+    stack.pop();
+
+    CreateIndexNode createIndex = ctx.plan.createNode(CreateIndexNode.class);
+    createIndex.setInSchema(child.getOutSchema());
+    createIndex.setOutSchema(child.getOutSchema());
+    return createIndex;
+  }
+
+  @Override
+  public LogicalNode visitDropIndex(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, DropIndex expr) {
+    return ctx.plan.createNode(DropIndexNode.class);
+  }
+
   public LogicalNode visitTruncateTable(LogicalPlanner.PlanContext ctx, Stack<Expr> stack, TruncateTable expr)
       throws TajoException {
     TruncateTableNode truncateTableNode = ctx.plan.createNode(TruncateTableNode.class);
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanner.java b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanner.java
index 47ab9b1..b61edd9 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanner.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/LogicalPlanner.java
@@ -39,7 +39,9 @@
 import org.apache.tajo.catalog.exception.UndefinedTableException;
 import org.apache.tajo.catalog.partition.PartitionMethodDesc;
 import org.apache.tajo.catalog.proto.CatalogProtos;
+import org.apache.tajo.catalog.proto.CatalogProtos.IndexMethod;
 import org.apache.tajo.common.TajoDataTypes;
+import org.apache.tajo.conf.TajoConf;
 import org.apache.tajo.datum.NullDatum;
 import org.apache.tajo.exception.ExceptionUtil;
 import org.apache.tajo.exception.TajoException;
@@ -54,6 +56,7 @@
 import org.apache.tajo.plan.rewrite.rules.ProjectionPushDownRule;
 import org.apache.tajo.plan.util.ExprFinder;
 import org.apache.tajo.plan.util.PlannerUtil;
+import org.apache.tajo.plan.verifier.VerifyException;
 import org.apache.tajo.storage.StorageService;
 import org.apache.tajo.util.KeyValueSet;
 import org.apache.tajo.util.Pair;
@@ -879,6 +882,17 @@
 
 
     // Building sort keys
+    SortSpec[] annotatedSortSpecs = annotateSortSpecs(block, referNames, sortSpecs);
+    if (annotatedSortSpecs.length == 0) {
+      return child;
+    } else {
+      sortNode.setSortSpecs(annotatedSortSpecs);
+      return sortNode;
+    }
+  }
+
+  private static SortSpec[] annotateSortSpecs(QueryBlock block, String [] referNames, Sort.SortSpec[] rawSortSpecs) {
+    int sortKeyNum = rawSortSpecs.length;
     Column column;
     List<SortSpec> annotatedSortSpecs = Lists.newArrayList();
     for (int i = 0; i < sortKeyNum; i++) {
@@ -888,17 +902,11 @@
       } else if (block.namedExprsMgr.isEvaluated(refName)) {
         column = block.namedExprsMgr.getTarget(refName).getNamedColumn();
       } else {
-        throw new IllegalStateException("Unexpected State: " + StringUtils.join(sortSpecs));
+        throw new IllegalStateException("Unexpected State: " + StringUtils.join(rawSortSpecs));
       }
-      annotatedSortSpecs.add(new SortSpec(column, sortSpecs[i].isAscending(), sortSpecs[i].isNullFirst()));
+      annotatedSortSpecs.add(new SortSpec(column, rawSortSpecs[i].isAscending(), rawSortSpecs[i].isNullFirst()));
     }
-
-    if (annotatedSortSpecs.size() == 0) {
-      return child;
-    } else {
-      sortNode.setSortSpecs(annotatedSortSpecs.toArray(new SortSpec[annotatedSortSpecs.size()]));
-      return sortNode;
-    }
+    return annotatedSortSpecs.toArray(new SortSpec[annotatedSortSpecs.size()]);
   }
 
   /*===============================================================================================
@@ -2143,6 +2151,73 @@
     return alterTableNode;
   }
 
+  private static URI getIndexPath(PlanContext context, String databaseName, String indexName) {
+    return new Path(TajoConf.getWarehouseDir(context.queryContext.getConf()),
+        databaseName + "/" + indexName + "/").toUri();
+  }
+
+  @Override
+  public LogicalNode visitCreateIndex(PlanContext context, Stack<Expr> stack, CreateIndex createIndex)
+      throws TajoException {
+    stack.push(createIndex);
+    LogicalNode child = visit(context, stack, createIndex.getChild());
+    stack.pop();
+
+    QueryBlock block = context.queryBlock;
+    CreateIndexNode createIndexNode = block.getNodeFromExpr(createIndex);
+    if (CatalogUtil.isFQTableName(createIndex.getIndexName())) {
+      createIndexNode.setIndexName(createIndex.getIndexName());
+    } else {
+      createIndexNode.setIndexName(
+          CatalogUtil.buildFQName(context.queryContext.get(SessionVars.CURRENT_DATABASE), createIndex.getIndexName()));
+    }
+    createIndexNode.setUnique(createIndex.isUnique());
+    Sort.SortSpec[] sortSpecs = createIndex.getSortSpecs();
+    int sortKeyNum = sortSpecs.length;
+    String[] referNames = new String[sortKeyNum];
+
+    ExprNormalizedResult[] normalizedExprList = new ExprNormalizedResult[sortKeyNum];
+    for (int i = 0; i < sortKeyNum; i++) {
+      normalizedExprList[i] = normalizer.normalize(context, sortSpecs[i].getKey());
+    }
+    for (int i = 0; i < sortKeyNum; i++) {
+      // even if base expressions don't have their name,
+      // reference names should be identifiable for the later sort spec creation.
+      referNames[i] = block.namedExprsMgr.addExpr(normalizedExprList[i].baseExpr);
+      block.namedExprsMgr.addNamedExprArray(normalizedExprList[i].aggExprs);
+      block.namedExprsMgr.addNamedExprArray(normalizedExprList[i].scalarExprs);
+    }
+
+    createIndexNode.setExternal(createIndex.isExternal());
+    Collection<RelationNode> relations = block.getRelations();
+    assert relations.size() == 1;
+    createIndexNode.setKeySortSpecs(relations.iterator().next().getLogicalSchema(),
+        annotateSortSpecs(block, referNames, sortSpecs));
+    createIndexNode.setIndexMethod(IndexMethod.valueOf(createIndex.getMethodSpec().getName().toUpperCase()));
+    if (createIndex.isExternal()) {
+      createIndexNode.setIndexPath(new Path(createIndex.getIndexPath()).toUri());
+    } else {
+      createIndexNode.setIndexPath(
+          getIndexPath(context, context.queryContext.get(SessionVars.CURRENT_DATABASE), createIndex.getIndexName()));
+    }
+
+    if (createIndex.getParams() != null) {
+      KeyValueSet keyValueSet = new KeyValueSet();
+      keyValueSet.putAll(createIndex.getParams());
+      createIndexNode.setOptions(keyValueSet);
+    }
+
+    createIndexNode.setChild(child);
+    return createIndexNode;
+  }
+
+  @Override
+  public LogicalNode visitDropIndex(PlanContext context, Stack<Expr> stack, DropIndex dropIndex) {
+    DropIndexNode dropIndexNode = context.queryBlock.getNodeFromExpr(dropIndex);
+    dropIndexNode.setIndexName(dropIndex.getIndexName());
+    return dropIndexNode;
+  }
+
   @Override
   public LogicalNode visitTruncateTable(PlanContext context, Stack<Expr> stack, TruncateTable truncateTable)
       throws TajoException {
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/NamedExprsManager.java b/tajo-plan/src/main/java/org/apache/tajo/plan/NamedExprsManager.java
index 08a9bd1..3ca2fe5 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/NamedExprsManager.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/NamedExprsManager.java
@@ -141,6 +141,7 @@
 
   /**
    * Adds an expression and returns a reference name.
+   * @param expr added expression
    */
   public String addExpr(Expr expr) {
     if (idToExprBiMap.inverse().containsKey(expr)) {
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/algebra/AlgebraVisitor.java b/tajo-plan/src/main/java/org/apache/tajo/plan/algebra/AlgebraVisitor.java
index 942dbb9..6149080 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/algebra/AlgebraVisitor.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/algebra/AlgebraVisitor.java
@@ -53,6 +53,8 @@
   RESULT visitAlterTablespace(CONTEXT ctx, Stack<Expr> stack, AlterTablespace expr) throws TajoException;
   RESULT visitAlterTable(CONTEXT ctx, Stack<Expr> stack, AlterTable expr) throws TajoException;
   RESULT visitTruncateTable(CONTEXT ctx, Stack<Expr> stack, TruncateTable expr) throws TajoException;
+  RESULT visitCreateIndex(CONTEXT ctx, Stack<Expr> stack, CreateIndex expr) throws TajoException;
+  RESULT visitDropIndex(CONTEXT ctx, Stack<Expr> stack, DropIndex expr) throws TajoException;
 
     // Insert or Update
   RESULT visitInsert(CONTEXT ctx, Stack<Expr> stack, Insert expr) throws TajoException;
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/algebra/BaseAlgebraVisitor.java b/tajo-plan/src/main/java/org/apache/tajo/plan/algebra/BaseAlgebraVisitor.java
index e32bd85..2b4fb30 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/algebra/BaseAlgebraVisitor.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/algebra/BaseAlgebraVisitor.java
@@ -123,9 +123,15 @@
     case AlterTable:
       current = visitAlterTable(ctx, stack, (AlterTable) expr);
       break;
+    case CreateIndex:
+      current = visitCreateIndex(ctx, stack, (CreateIndex) expr);
+      break;
     case TruncateTable:
       current = visitTruncateTable(ctx, stack, (TruncateTable)expr);
       break;
+    case DropIndex:
+      current = visitDropIndex(ctx, stack, (DropIndex) expr);
+      break;
 
     case Insert:
       current = visitInsert(ctx, stack, (Insert) expr);
@@ -481,6 +487,17 @@
     return null;
   }
 
+  @Override
+  public RESULT visitCreateIndex(CONTEXT ctx, Stack<Expr> stack, CreateIndex expr) throws TajoException {
+    return null;
+  }
+
+  @Override
+  public RESULT visitDropIndex(CONTEXT ctx, Stack<Expr> stack, DropIndex expr) {
+    return null;
+  }
+
+  @Override
   public RESULT visitTruncateTable(CONTEXT ctx, Stack<Expr> stack, TruncateTable expr) throws TajoException {
     return null;
   }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/expr/AlgebraicUtil.java b/tajo-plan/src/main/java/org/apache/tajo/plan/expr/AlgebraicUtil.java
index c6b7354..cba41c1 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/expr/AlgebraicUtil.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/expr/AlgebraicUtil.java
@@ -18,7 +18,11 @@
 
 package org.apache.tajo.plan.expr;
 
+import org.apache.tajo.algebra.*;
 import org.apache.tajo.catalog.Column;
+import org.apache.tajo.exception.TajoException;
+import org.apache.tajo.plan.PlanningException;
+import org.apache.tajo.plan.visitor.SimpleAlgebraVisitor;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -424,4 +428,70 @@
       found.add(node);
     }
   }
+
+  public static class IdentifiableNameBuilder extends SimpleAlgebraVisitor<Object, Object> {
+    private Expr expr;
+    private StringBuilder nameBuilder = new StringBuilder();
+
+    public IdentifiableNameBuilder(Expr expr) {
+      this.expr = expr;
+    }
+
+    public String build() {
+      Stack<Expr> stack = new Stack<Expr>();
+      stack.push(expr);
+      try {
+        this.visit(null, stack, expr);
+      } catch (TajoException e) {
+
+      }
+      return nameBuilder.deleteCharAt(nameBuilder.length()-1).toString();
+    }
+
+    @Override
+    public Object visitBinaryOperator(Object ctx, Stack<Expr> stack, BinaryOperator expr) throws TajoException {
+      addIntermExpr(expr);
+      return super.visitBinaryOperator(ctx, stack, expr);
+    }
+
+    private void append(String str) {
+      nameBuilder.append(str).append("_");
+    }
+
+    private void addIntermExpr(Expr expr) {
+      this.append(expr.getType().name());
+    }
+
+    @Override
+    public Object visitColumnReference(Object ctx, Stack<Expr> stack, ColumnReferenceExpr expr)
+        throws TajoException {
+      this.append(expr.getName());
+      return super.visitColumnReference(ctx, stack, expr);
+    }
+
+    @Override
+    public Object visitLiteral(Object ctx, Stack<Expr> stack, LiteralValue expr) throws TajoException {
+      this.append(expr.getValue());
+      return super.visitLiteral(ctx, stack, expr);
+    }
+
+    @Override
+    public Object visitNullLiteral(Object ctx, Stack<Expr> stack, NullLiteral expr) throws TajoException {
+      this.append("null");
+      return super.visitNullLiteral(ctx, stack, expr);
+    }
+
+    @Override
+    public Object visitTimestampLiteral(Object ctx, Stack<Expr> stack, TimestampLiteral expr) throws TajoException {
+      this.append(expr.getDate().toString());
+      this.append(expr.getTime().toString());
+      return super.visitTimestampLiteral(ctx, stack, expr);
+    }
+
+    @Override
+    public Object visitTimeLiteral(Object ctx, Stack<Expr> stack, TimeLiteral expr) throws TajoException {
+      this.append(expr.getTime().toString());
+      return super.visitTimeLiteral(ctx, stack, expr);
+    }
+  }
 }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/expr/EvalTreeUtil.java b/tajo-plan/src/main/java/org/apache/tajo/plan/expr/EvalTreeUtil.java
index 1227855..7ff695b 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/expr/EvalTreeUtil.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/expr/EvalTreeUtil.java
@@ -385,7 +385,7 @@
     return !leftQualifier.equals(rightQualifier);
   }
 
-  static boolean isSingleColumn(EvalNode evalNode) {
+  public static boolean isSingleColumn(EvalNode evalNode) {
     return EvalTreeUtil.findUniqueColumns(evalNode).size() == 1;
   }
   
@@ -542,6 +542,10 @@
 
       return evalNode;
     }
+
+    public List<EvalNode> getEvalNodes() {
+      return evalNodes;
+    }
   }
 
   public static class OuterJoinSensitiveEvalFinder extends BasicEvalNodeVisitor<Object, Object> {
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateIndexNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateIndexNode.java
new file mode 100644
index 0000000..df7eb34
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/CreateIndexNode.java
@@ -0,0 +1,161 @@
+/*
+ * 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.tajo.plan.logical;
+
+import com.google.common.base.Objects;
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.catalog.IndexMeta;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.catalog.SortSpec;
+import org.apache.tajo.plan.PlanString;
+import org.apache.tajo.util.KeyValueSet;
+
+import java.net.URI;
+
+import static org.apache.tajo.catalog.proto.CatalogProtos.IndexMethod;
+
+public class CreateIndexNode extends UnaryNode implements Cloneable {
+  @Expose private IndexMeta indexMeta;
+  @Expose private boolean external;
+
+  public CreateIndexNode(int pid) {
+    super(pid, NodeType.CREATE_INDEX);
+    this.indexMeta = new IndexMeta();
+  }
+
+  public void setUnique(boolean unique) {
+    indexMeta.setUnique(unique);
+  }
+
+  public boolean isUnique() {
+    return indexMeta.isUnique();
+  }
+
+  public void setIndexName(String indexName) {
+    indexMeta.setIndexName(indexName);
+  }
+
+  public String getIndexName() {
+    return indexMeta.getIndexName();
+  }
+
+  public void setIndexPath(URI indexPath) {
+    indexMeta.setIndexPath(indexPath);
+  }
+
+  public URI getIndexPath() {
+    return indexMeta.getIndexPath();
+  }
+
+  public void setKeySortSpecs(Schema targetRelationSchema, SortSpec[] sortSpecs) {
+    indexMeta.setKeySortSpecs(targetRelationSchema, sortSpecs);
+  }
+
+  public SortSpec[] getKeySortSpecs() {
+    return indexMeta.getKeySortSpecs();
+  }
+
+  public void setIndexMethod(IndexMethod indexType) {
+    indexMeta.setIndexMethod(indexType);
+  }
+
+  public IndexMethod getIndexMethod() {
+    return indexMeta.getIndexMethod();
+  }
+
+  public void setOptions(KeyValueSet options) {
+    indexMeta.setOptions(options);
+  }
+
+  public KeyValueSet getOptions() {
+    return indexMeta.getOptions();
+  }
+
+  public Schema getTargetRelationSchema() {
+    return indexMeta.getTargetRelationSchema();
+  }
+
+  public boolean hasOptions() {
+    return indexMeta.getOptions() != null;
+  }
+
+  public void setClustered(boolean clustered) {
+    indexMeta.setClustered(clustered);
+  }
+
+  public boolean isClustered() {
+    return indexMeta.isClustered();
+  }
+
+  public void setExternal(boolean external) {
+    this.external = external;
+  }
+
+  public boolean isExternal() {
+    return this.external;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hashCode(indexMeta, external);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof CreateIndexNode) {
+      CreateIndexNode other = (CreateIndexNode) obj;
+      return this.indexMeta.equals(other.indexMeta) && this.external == other.external;
+    }
+    return false;
+  }
+
+  @Override
+  public Object clone() throws CloneNotSupportedException {
+    CreateIndexNode createIndexNode = (CreateIndexNode) super.clone();
+    createIndexNode.indexMeta = (IndexMeta) this.indexMeta.clone();
+    createIndexNode.external = this.external;
+    return createIndexNode;
+  }
+
+  private String getSortSpecString() {
+    StringBuilder sb = new StringBuilder("Column [key= ");
+    SortSpec[] sortSpecs = indexMeta.getKeySortSpecs();
+    for (int i = 0; i < sortSpecs.length; i++) {
+      sb.append(sortSpecs[i].getSortKey().getQualifiedName()).append(" ")
+          .append(sortSpecs[i].isAscending() ? "asc" : "desc");
+      if(i < sortSpecs.length - 1) {
+        sb.append(",");
+      }
+    }
+    sb.append("]");
+    return sb.toString();
+  }
+
+  @Override
+  public String toString() {
+    return "CreateIndex (indexName=" + indexMeta.getIndexName() + ", indexPath=" + indexMeta.getIndexPath() +
+        ", type=" + indexMeta.getIndexMethod().name() +
+        ", isUnique=" + indexMeta.isUnique() + ", " + getSortSpecString() + ", isExternal=" + isExternal() + ")";
+  }
+
+  @Override
+  public PlanString getPlanString() {
+    return new PlanString(this);
+  }
+}
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropIndexNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropIndexNode.java
new file mode 100644
index 0000000..da7018a
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/DropIndexNode.java
@@ -0,0 +1,92 @@
+/*
+ * 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.tajo.plan.logical;
+
+import com.google.common.base.Objects;
+import org.apache.tajo.plan.PlanString;
+
+public class DropIndexNode extends LogicalNode implements Cloneable {
+  private String indexName;
+
+  public DropIndexNode(int pid) {
+    super(pid, NodeType.DROP_INDEX);
+  }
+
+  public void init(String indexName) {
+    this.indexName = indexName;
+  }
+
+  public int hashCode() {
+    return Objects.hashCode(indexName);
+  }
+
+  @Override
+  public int childNum() {
+    return 0;
+  }
+
+  @Override
+  public LogicalNode getChild(int idx) {
+    return null;
+  }
+
+  public boolean equals(Object obj) {
+    if (obj instanceof DropIndexNode) {
+      DropIndexNode other = (DropIndexNode) obj;
+      return super.equals(other) &&
+          this.indexName.equals(other.indexName);
+    }
+    return false;
+  }
+
+  @Override
+  public void preOrder(LogicalNodeVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  @Override
+  public void postOrder(LogicalNodeVisitor visitor) {
+    visitor.visit(this);
+  }
+
+  @Override
+  public PlanString getPlanString() {
+    return new PlanString(this);
+  }
+
+  @Override
+  public Object clone() throws CloneNotSupportedException {
+    DropIndexNode clone = (DropIndexNode) super.clone();
+    clone.indexName = this.indexName;
+    return clone;
+  }
+
+  @Override
+  public String toString() {
+    return "DROP INDEX " + indexName;
+  }
+
+  public void setIndexName(String indexName) {
+    this.indexName = indexName;
+  }
+
+  public String getIndexName() {
+    return indexName;
+  }
+}
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IndexScanNode.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IndexScanNode.java
index 8b73756..ebf6c4b 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IndexScanNode.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/IndexScanNode.java
@@ -24,56 +24,54 @@
 import com.google.gson.annotations.Expose;
 
 import org.apache.tajo.catalog.Schema;
-import org.apache.tajo.catalog.SortSpec;
-import org.apache.tajo.datum.Datum;
+import org.apache.tajo.plan.rewrite.rules.IndexScanInfo.SimplePredicate;
 import org.apache.tajo.plan.serder.PlanGsonHelper;
+import org.apache.tajo.util.TUtil;
+
+import java.net.URI;
 
 public class IndexScanNode extends ScanNode {
-  @Expose private SortSpec [] sortKeys;
   @Expose private Schema keySchema = null;
-  @Expose private Datum[] datum = null;
+  @Expose private URI indexPath = null;
+  @Expose private SimplePredicate[] predicates = null;
+
+  public IndexScanNode(int pid) {
+    super(pid);
+    setType(NodeType.INDEX_SCAN);
+  }
   
   public IndexScanNode(int pid, ScanNode scanNode ,
-      Schema keySchema , Datum[] datum, SortSpec[] sortKeys ) {
-    super(pid);
+      Schema keySchema , SimplePredicate[] predicates, URI indexPath) {
+    this(pid);
     init(scanNode.getTableDesc());
     setQual(scanNode.getQual());
     setInSchema(scanNode.getInSchema());
     setTargets(scanNode.getTargets());
-    setType(NodeType.BST_INDEX_SCAN);
-    this.sortKeys = sortKeys;
-    this.keySchema = keySchema;
-    this.datum = datum;
+    this.set(keySchema, predicates, indexPath);
   }
-  
-  public SortSpec[] getSortKeys() {
-    return this.sortKeys;
+
+  public void set(Schema keySchema, SimplePredicate[] predicates, URI indexPath) {
+    this.keySchema = keySchema;
+    this.indexPath = indexPath;
+    this.predicates = predicates;
   }
   
   public Schema getKeySchema() {
     return this.keySchema;
   }
-  
-  public Datum[] getDatum() {
-    return this.datum;
-  }
-  
-  public void setSortKeys(SortSpec[] sortKeys) {
-    this.sortKeys = sortKeys;
-  }
-  
-  public void setKeySchema( Schema keySchema ) {
-    this.keySchema = keySchema;
-  }
 
+  public SimplePredicate[] getPredicates() {
+    return predicates;
+  }
+  
   @Override
   public String toString() {
     Gson gson = PlanGsonHelper.getInstance();
     StringBuilder builder = new StringBuilder();
     builder.append("IndexScanNode : {\n");
+    builder.append("  \"indexPath\" : \"" + gson.toJson(this.indexPath) + "\"\n");
     builder.append("  \"keySchema\" : \"" + gson.toJson(this.keySchema) + "\"\n");
-    builder.append("  \"sortKeys\" : \"" + gson.toJson(this.sortKeys) + " \"\n");
-    builder.append("  \"datums\" : \"" + gson.toJson(this.datum) + "\"\n");
+    builder.append("  \"keySortSpecs\" : \"" + gson.toJson(predicates) + " \"\n");
     builder.append("      <<\"superClass\" : " + super.toString());
     builder.append(">>}");
     builder.append("}");
@@ -84,9 +82,9 @@
   public int hashCode() {
     final int prime = 31;
     int result = super.hashCode();
-    result = prime * result + Arrays.hashCode(datum);
+    result = prime * result + indexPath.hashCode();
     result = prime * result + ((keySchema == null) ? 0 : keySchema.hashCode());
-    result = prime * result + Arrays.hashCode(sortKeys);
+    result = prime * result + Arrays.hashCode(predicates);
     return result;
   }
 
@@ -94,25 +92,12 @@
   public boolean equals(Object obj) {
     if (obj instanceof IndexScanNode) {
       IndexScanNode other = (IndexScanNode) obj;
-      
       boolean eq = super.equals(other);
-      eq = eq && this.sortKeys.length == other.sortKeys.length;
-      if(eq) {
-        for(int i = 0 ; i < this.sortKeys.length ; i ++) {
-          eq = eq && this.sortKeys[i].getSortKey().equals(
-              other.sortKeys[i].getSortKey());
-          eq = eq && this.sortKeys[i].isAscending()
-              == other.sortKeys[i].isAscending();
-          eq = eq && this.sortKeys[i].isNullFirst()
-              == other.sortKeys[i].isNullFirst();
-        }
-      }
-      if(eq) {
-        for(int i = 0 ; i < this.datum.length ; i ++ ) {
-          eq = eq && this.datum[i].equals(other.datum[i]);
-        }
-      }
-     return eq;
+      eq &= this.indexPath.equals(other.indexPath);
+      eq &= TUtil.checkEquals(this.predicates, other.predicates);
+      eq &= this.keySchema.equals(other.keySchema);
+
+      return eq;
     }   
     return false;
   } 
@@ -121,15 +106,16 @@
   public Object clone() throws CloneNotSupportedException {
     IndexScanNode indexNode = (IndexScanNode) super.clone();
     indexNode.keySchema = (Schema) this.keySchema.clone();
-    indexNode.sortKeys = new SortSpec[this.sortKeys.length];
-    for(int i = 0 ; i < sortKeys.length ; i ++ )
-      indexNode.sortKeys[i] = (SortSpec) this.sortKeys[i].clone();
-    indexNode.datum = new Datum[this.datum.length];
-    for(int i = 0 ; i < datum.length ; i ++ ) {
-      indexNode.datum[i] = this.datum[i];
-    }
+    indexNode.predicates = new SimplePredicate[this.predicates.length];
+    for(int i = 0 ; i < this.predicates.length ; i ++ )
+      indexNode.predicates[i] = (SimplePredicate) this.predicates[i].clone();
+    indexNode.indexPath = this.indexPath;
     return indexNode;
   }
+
+  public URI getIndexPath() {
+    return indexPath;
+  }
 }
 
 
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/NodeType.java b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/NodeType.java
index 75ae3b7..1ebea8b 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/logical/NodeType.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/logical/NodeType.java
@@ -45,7 +45,7 @@
   TABLE_SUBQUERY(TableSubQueryNode.class),
   SCAN(ScanNode.class),
   PARTITIONS_SCAN(PartitionedTableScanNode.class),
-  BST_INDEX_SCAN(IndexScanNode.class),
+  INDEX_SCAN(IndexScanNode.class),
   STORE(StoreTableNode.class),
   INSERT(InsertNode.class),
 
@@ -55,6 +55,8 @@
   DROP_TABLE(DropTableNode.class),
   ALTER_TABLESPACE (AlterTablespaceNode.class),
   ALTER_TABLE (AlterTableNode.class),
+  CREATE_INDEX(CreateIndexNode.class),
+  DROP_INDEX(DropIndexNode.class),
   TRUNCATE_TABLE (TruncateTableNode.class);
 
   private final Class<? extends LogicalNode> baseClass;
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/BaseLogicalPlanRewriteEngine.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/BaseLogicalPlanRewriteEngine.java
index f6f1e63..9a541d9 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/BaseLogicalPlanRewriteEngine.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/BaseLogicalPlanRewriteEngine.java
@@ -69,18 +69,19 @@
   /**
    * Rewrite a logical plan with all query rewrite rules added to this engine.
    *
-   * @param plan The plan to be rewritten with all query rewrite rule.
+   * @param context
    * @return The rewritten plan.
    */
-  public LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException {
+  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException {
     LogicalPlanRewriteRule rule;
+    LogicalPlan plan = null;
     for (Entry<String, LogicalPlanRewriteRule> rewriteRule : rewriteRules.entrySet()) {
       rule = rewriteRule.getValue();
-      if (rule.isEligible(queryContext, plan)) {
-        plan = rule.rewrite(queryContext, plan);
+      if (rule.isEligible(context)) {
         if (LOG.isDebugEnabled()) {
           LOG.debug("The rule \"" + rule.getName() + " \" rewrites the query.");
         }
+        plan = rule.rewrite(context);
       }
     }
 
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/BaseLogicalPlanRewriteRuleProvider.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/BaseLogicalPlanRewriteRuleProvider.java
index eb96149..dcfd6bf 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/BaseLogicalPlanRewriteRuleProvider.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/BaseLogicalPlanRewriteRuleProvider.java
@@ -19,6 +19,7 @@
 package org.apache.tajo.plan.rewrite;
 
 import org.apache.tajo.conf.TajoConf;
+import org.apache.tajo.plan.rewrite.rules.AccessPathRewriter;
 import org.apache.tajo.plan.rewrite.rules.FilterPushDownRule;
 import org.apache.tajo.plan.rewrite.rules.PartitionedTableRewriter;
 import org.apache.tajo.plan.rewrite.rules.ProjectionPushDownRule;
@@ -52,7 +53,8 @@
   public Collection<Class<? extends LogicalPlanRewriteRule>> getPostRules() {
     List<Class<? extends LogicalPlanRewriteRule>> rules = TUtil.newList(
         ProjectionPushDownRule.class,
-        PartitionedTableRewriter.class
+        PartitionedTableRewriter.class,
+        AccessPathRewriter.class
     );
     return rules;
   }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteEngine.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteEngine.java
index 99d8979..a621921 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteEngine.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteEngine.java
@@ -18,7 +18,6 @@
 
 package org.apache.tajo.plan.rewrite;
 
-import org.apache.tajo.OverridableConf;
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.plan.LogicalPlan;
 
@@ -29,5 +28,5 @@
    * @param plan The plan to be rewritten with all query rewrite rule.
    * @return The rewritten plan.
    */
-  LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException;
+  LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException;
 }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteRule.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteRule.java
index d5d02f2..6643d28 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteRule.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteRule.java
@@ -18,10 +18,8 @@
 
 package org.apache.tajo.plan.rewrite;
 
-import org.apache.tajo.OverridableConf;
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.plan.LogicalPlan;
-import org.apache.tajo.plan.PlanningException;
 
 /**
  * An interface for a rewrite rule.
@@ -44,7 +42,7 @@
    * @param plan The plan to be checked
    * @return True if this rule can be applied to a given plan. Otherwise, false.
    */
-  boolean isEligible(OverridableConf queryContext, LogicalPlan plan);
+  boolean isEligible(LogicalPlanRewriteRuleContext context);
 
   /**
    * Updates a logical plan and returns an updated logical plan rewritten by this rule.
@@ -54,5 +52,5 @@
    * @param plan Input logical plan. It will not be modified.
    * @return The rewritten logical plan.
    */
-  LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException;
+  LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException;
 }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteRuleContext.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteRuleContext.java
new file mode 100644
index 0000000..6c43112
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/LogicalPlanRewriteRuleContext.java
@@ -0,0 +1,65 @@
+/**
+ * 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.tajo.plan.rewrite;
+
+import org.apache.tajo.OverridableConf;
+import org.apache.tajo.catalog.CatalogService;
+import org.apache.tajo.plan.LogicalPlan;
+
+public class LogicalPlanRewriteRuleContext {
+
+  private OverridableConf queryContext;
+  private LogicalPlan plan;
+  private CatalogService catalog;
+
+  public LogicalPlanRewriteRuleContext(OverridableConf queryContext, LogicalPlan plan) {
+    setQueryContext(queryContext);
+    setPlan(plan);
+  }
+
+  public LogicalPlanRewriteRuleContext(OverridableConf queryContext, LogicalPlan plan, CatalogService catalog) {
+    setQueryContext(queryContext);
+    setPlan(plan);
+    setCatalog(catalog);
+  }
+
+  public void setCatalog(CatalogService catalog) {
+    this.catalog = catalog;
+  }
+
+  public CatalogService getCatalog() {
+    return catalog;
+  }
+
+  public OverridableConf getQueryContext() {
+    return queryContext;
+  }
+
+  public void setQueryContext(OverridableConf queryContext) {
+    this.queryContext = queryContext;
+  }
+
+  public LogicalPlan getPlan() {
+    return plan;
+  }
+
+  public void setPlan(LogicalPlan plan) {
+    this.plan = plan;
+  }
+}
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/AccessPathInfo.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/AccessPathInfo.java
new file mode 100644
index 0000000..477ccaf
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/AccessPathInfo.java
@@ -0,0 +1,52 @@
+/*
+ * 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.tajo.plan.rewrite.rules;
+
+import org.apache.tajo.catalog.statistics.TableStats;
+
+public abstract class AccessPathInfo {
+  public enum ScanTypeControl {
+    INDEX_SCAN,
+    SEQ_SCAN
+  }
+
+  private ScanTypeControl scanType;
+  private TableStats tableStats;
+
+  public AccessPathInfo(ScanTypeControl scanType, TableStats tableStats) {
+    this.scanType = scanType;
+    this.tableStats = tableStats;
+  }
+
+  public ScanTypeControl getScanType() {
+    return scanType;
+  }
+
+  public TableStats getTableStats() {
+    return tableStats;
+  }
+
+  public void setTableStats(TableStats tableStats) {
+    this.tableStats = tableStats;
+  }
+
+  public boolean hasTableStats() {
+    return this.tableStats != null;
+  }
+}
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/AccessPathRewriter.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/AccessPathRewriter.java
new file mode 100644
index 0000000..afabe7a
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/AccessPathRewriter.java
@@ -0,0 +1,129 @@
+/*
+ * 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.tajo.plan.rewrite.rules;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.tajo.OverridableConf;
+import org.apache.tajo.SessionVars;
+import org.apache.tajo.exception.TajoException;
+import org.apache.tajo.plan.LogicalPlan;
+import org.apache.tajo.plan.logical.IndexScanNode;
+import org.apache.tajo.plan.logical.LogicalNode;
+import org.apache.tajo.plan.logical.RelationNode;
+import org.apache.tajo.plan.logical.ScanNode;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRule;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
+import org.apache.tajo.plan.util.PlannerUtil;
+import org.apache.tajo.plan.visitor.BasicLogicalPlanVisitor;
+
+import java.util.List;
+import java.util.Stack;
+
+public class AccessPathRewriter implements LogicalPlanRewriteRule {
+  private static final Log LOG = LogFactory.getLog(AccessPathRewriter.class);
+
+  private static final String NAME = "Access Path Rewriter";
+  private Rewriter rewriter = new Rewriter();
+
+  @Override
+  public String getName() {
+    return NAME;
+  }
+
+  @Override
+  public boolean isEligible(LogicalPlanRewriteRuleContext context) {
+    if (context.getQueryContext().getBool(SessionVars.INDEX_ENABLED)) {
+      for (LogicalPlan.QueryBlock block : context.getPlan().getQueryBlocks()) {
+        for (RelationNode relationNode : block.getRelations()) {
+          List<AccessPathInfo> accessPathInfos = block.getAccessInfos(relationNode);
+          // If there are any alternative access paths
+          if (accessPathInfos.size() > 1) {
+            for (AccessPathInfo accessPathInfo : accessPathInfos) {
+              if (accessPathInfo.getScanType() == AccessPathInfo.ScanTypeControl.INDEX_SCAN) {
+                return true;
+              }
+            }
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  @Override
+  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException {
+    LogicalPlan plan = context.getPlan();
+    LogicalPlan.QueryBlock rootBlock = plan.getRootBlock();
+    rewriter.init(context.getQueryContext());
+    rewriter.visit(rootBlock, plan, rootBlock, rootBlock.getRoot(), new Stack<LogicalNode>());
+    return plan;
+  }
+
+  private final class Rewriter extends BasicLogicalPlanVisitor<Object, Object> {
+
+    private OverridableConf conf;
+
+    public void init(OverridableConf conf) {
+      this.conf = conf;
+    }
+
+    @Override
+    public Object visitScan(Object object, LogicalPlan plan, LogicalPlan.QueryBlock block, ScanNode scanNode,
+                            Stack<LogicalNode> stack) throws TajoException {
+      List<AccessPathInfo> accessPaths = block.getAccessInfos(scanNode);
+      AccessPathInfo optimalPath = null;
+      // initialize
+      for (AccessPathInfo accessPath : accessPaths) {
+        if (accessPath.getScanType() == AccessPathInfo.ScanTypeControl.SEQ_SCAN) {
+          optimalPath = accessPath;
+          break;
+        }
+      }
+      // find the optimal path
+      for (AccessPathInfo accessPath : accessPaths) {
+        if (accessPath.getScanType() == AccessPathInfo.ScanTypeControl.INDEX_SCAN) {
+          // estimation selectivity and choose the better path
+          // TODO: improve the selectivity estimation
+          double estimateSelectivity = 0.001;
+          double selectivityThreshold = conf.getFloat(SessionVars.INDEX_SELECTIVITY_THRESHOLD);
+          LOG.info("Selectivity threshold: " + selectivityThreshold);
+          LOG.info("Estimated selectivity: " + estimateSelectivity);
+          if (estimateSelectivity < selectivityThreshold) {
+            // if the estimated selectivity is greater than threshold, use the index scan
+            optimalPath = accessPath;
+          }
+        }
+      }
+
+      if (optimalPath != null && optimalPath.getScanType() == AccessPathInfo.ScanTypeControl.INDEX_SCAN) {
+        IndexScanInfo indexScanInfo = (IndexScanInfo) optimalPath;
+        plan.addHistory("AccessPathRewriter chooses the index scan for " + scanNode.getTableName());
+        IndexScanNode indexScanNode = new IndexScanNode(plan.newPID(), scanNode, indexScanInfo.getKeySchema(),
+            indexScanInfo.getPredicates(), indexScanInfo.getIndexPath());
+        if (stack.empty() || block.getRoot().equals(scanNode)) {
+          block.setRoot(indexScanNode);
+        } else {
+          PlannerUtil.replaceNode(plan, stack.peek(), scanNode, indexScanNode);
+        }
+      }
+      return null;
+    }
+  }
+}
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/FilterPushDownRule.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/FilterPushDownRule.java
index 98e674e..4fb8aac 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/FilterPushDownRule.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/FilterPushDownRule.java
@@ -21,10 +21,13 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.tajo.OverridableConf;
 import org.apache.tajo.algebra.JoinType;
+import org.apache.tajo.catalog.*;
+import org.apache.tajo.datum.Datum;
 import org.apache.tajo.catalog.CatalogUtil;
 import org.apache.tajo.catalog.Column;
 import org.apache.tajo.catalog.Schema;
@@ -35,7 +38,10 @@
 import org.apache.tajo.plan.LogicalPlan.QueryBlock;
 import org.apache.tajo.plan.expr.*;
 import org.apache.tajo.plan.logical.*;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
 import org.apache.tajo.plan.rewrite.rules.FilterPushDownRule.FilterPushDownContext;
+import org.apache.tajo.plan.rewrite.rules.IndexScanInfo.SimplePredicate;
+import org.apache.tajo.plan.util.IndexUtil;
 import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRule;
 import org.apache.tajo.plan.util.PlannerUtil;
 import org.apache.tajo.plan.visitor.BasicLogicalPlanVisitor;
@@ -52,6 +58,8 @@
   private final static Log LOG = LogFactory.getLog(FilterPushDownRule.class);
   private static final String NAME = "FilterPushDown";
 
+  private CatalogService catalog;
+
   static class FilterPushDownContext {
     Set<EvalNode> pushingDownFilters = TUtil.newHashSet();
 
@@ -85,8 +93,8 @@
   }
 
   @Override
-  public boolean isEligible(OverridableConf queryContext, LogicalPlan plan) {
-    for (LogicalPlan.QueryBlock block : plan.getQueryBlocks()) {
+  public boolean isEligible(LogicalPlanRewriteRuleContext context) {
+    for (LogicalPlan.QueryBlock block : context.getPlan().getQueryBlocks()) {
       if (block.hasNode(NodeType.SELECTION) || block.hasNode(NodeType.JOIN)) {
         return true;
       }
@@ -95,7 +103,7 @@
   }
 
   @Override
-  public LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException {
+  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext rewriteRuleContext) throws TajoException {
     /*
     FilterPushDown rule: processing when visits each node
       - If a target which is corresponding on a filter EvalNode's column is not FieldEval, do not PushDown.
@@ -107,6 +115,8 @@
         . It not, create new HavingNode and set parent's child.
      */
     FilterPushDownContext context = new FilterPushDownContext();
+    LogicalPlan plan = rewriteRuleContext.getPlan();
+    catalog = rewriteRuleContext.getCatalog();
     for (LogicalPlan.QueryBlock block : plan.getQueryBlocks()) {
       context.clear();
       this.visit(context, plan, block, block.getRoot(), new Stack<LogicalNode>());
@@ -877,7 +887,7 @@
 
   @Override
   public LogicalNode visitScan(FilterPushDownContext context, LogicalPlan plan,
-                               LogicalPlan.QueryBlock block, ScanNode scanNode,
+                               LogicalPlan.QueryBlock block, final ScanNode scanNode,
                                Stack<LogicalNode> stack) throws TajoException {
     List<EvalNode> matched = TUtil.newList();
 
@@ -947,8 +957,42 @@
       qual = matched.iterator().next();
     }
 
+    block.addAccessPath(scanNode, new SeqScanInfo(table));
     if (qual != null) { // if a matched qual exists
       scanNode.setQual(qual);
+
+      // Index path can be identified only after filters are pushed into each scan.
+      String databaseName, tableName;
+      databaseName = CatalogUtil.extractQualifier(table.getName());
+      tableName = CatalogUtil.extractSimpleName(table.getName());
+      Set<Predicate> predicates = TUtil.newHashSet();
+      for (EvalNode eval : IndexUtil.getAllEqualEvals(qual)) {
+        BinaryEval binaryEval = (BinaryEval) eval;
+        // TODO: consider more complex predicates
+        if (binaryEval.getLeftExpr().getType() == EvalType.FIELD &&
+            binaryEval.getRightExpr().getType() == EvalType.CONST) {
+          predicates.add(new Predicate(binaryEval.getType(),
+              ((FieldEval) binaryEval.getLeftExpr()).getColumnRef(),
+              ((ConstEval)binaryEval.getRightExpr()).getValue()));
+        } else if (binaryEval.getLeftExpr().getType() == EvalType.CONST &&
+            binaryEval.getRightExpr().getType() == EvalType.FIELD) {
+          predicates.add(new Predicate(binaryEval.getType(),
+              ((FieldEval) binaryEval.getRightExpr()).getColumnRef(),
+              ((ConstEval)binaryEval.getLeftExpr()).getValue()));
+        }
+      }
+
+      // for every subset of the set of columns, find all matched index paths
+      for (Set<Predicate> subset : Sets.powerSet(predicates)) {
+        if (subset.size() == 0)
+          continue;
+        Column[] columns = extractColumns(subset);
+        if (catalog.existIndexByColumns(databaseName, tableName, columns)) {
+          IndexDesc indexDesc = catalog.getIndexByColumns(databaseName, tableName, columns);
+          block.addAccessPath(scanNode, new IndexScanInfo(
+              table.getStats(), indexDesc, getSimplePredicates(indexDesc, subset)));
+        }
+      }
     }
 
     for (EvalNode matchedEval: matched) {
@@ -961,6 +1005,49 @@
     return scanNode;
   }
 
+  private static class Predicate {
+    Column column;
+    Datum value;
+    EvalType evalType;
+
+    public Predicate(EvalType evalType, Column column, Datum value) {
+      this.evalType = evalType;
+      this.column = column;
+      this.value = value;
+    }
+  }
+
+  private static SimplePredicate[] getSimplePredicates(IndexDesc desc, Set<Predicate> predicates) {
+    SimplePredicate[] simplePredicates = new SimplePredicate[predicates.size()];
+    Map<Column, Datum> colToValue = TUtil.newHashMap();
+    for (Predicate predicate : predicates) {
+      colToValue.put(predicate.column, predicate.value);
+    }
+    SortSpec [] keySortSpecs = desc.getKeySortSpecs();
+    for (int i = 0; i < keySortSpecs.length; i++) {
+      simplePredicates[i] = new SimplePredicate(keySortSpecs[i],
+          colToValue.get(keySortSpecs[i].getSortKey()));
+    }
+    return simplePredicates;
+  }
+
+  private static Datum[] extractPredicateValues(List<Predicate> predicates) {
+    Datum[] values = new Datum[predicates.size()];
+    for (int i = 0; i < values.length; i++) {
+      values[i] = predicates.get(i).value;
+    }
+    return values;
+  }
+
+  private static Column[] extractColumns(Set<Predicate> predicates) {
+    Column[] columns = new Column[predicates.size()];
+    int i = 0;
+    for (Predicate p : predicates) {
+      columns[i++] = p.column;
+    }
+    return columns;
+  }
+
   private void errorFilterPushDown(LogicalPlan plan, LogicalNode node,
                                    FilterPushDownContext context) {
     String prefix = "";
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/IndexScanInfo.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/IndexScanInfo.java
new file mode 100644
index 0000000..9ac8ccf
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/IndexScanInfo.java
@@ -0,0 +1,113 @@
+/*
+ * 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.tajo.plan.rewrite.rules;
+
+import com.google.gson.annotations.Expose;
+import org.apache.tajo.catalog.IndexDesc;
+import org.apache.tajo.catalog.Schema;
+import org.apache.tajo.catalog.SortSpec;
+import org.apache.tajo.catalog.statistics.TableStats;
+import org.apache.tajo.common.ProtoObject;
+import org.apache.tajo.datum.Datum;
+import org.apache.tajo.plan.serder.EvalNodeDeserializer;
+import org.apache.tajo.plan.serder.EvalNodeSerializer;
+import org.apache.tajo.plan.serder.PlanProto.SimplePredicateProto;
+
+import java.net.URI;
+
+public class IndexScanInfo extends AccessPathInfo {
+
+  /**
+   * Simple predicate represents an equal eval expression which consists of
+   * a column and a value.
+   */
+  // TODO: extend to represent more complex expressions
+  public static class SimplePredicate implements ProtoObject<SimplePredicateProto> {
+    @Expose private SortSpec keySortSpec;
+    @Expose private Datum value;
+
+    public SimplePredicate(SortSpec keySortSpec, Datum value) {
+      this.keySortSpec = keySortSpec;
+      this.value = value;
+    }
+
+    public SimplePredicate(SimplePredicateProto proto) {
+      keySortSpec = new SortSpec(proto.getKeySortSpec());
+      value = EvalNodeDeserializer.deserialize(proto.getValue());
+    }
+
+    public SortSpec getKeySortSpec() {
+      return keySortSpec;
+    }
+
+    public Datum getValue() {
+      return value;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      if (o instanceof SimplePredicate) {
+        SimplePredicate other = (SimplePredicate) o;
+        return this.keySortSpec.equals(other.keySortSpec) && this.value.equals(other.value);
+      } else {
+        return false;
+      }
+    }
+
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+      SimplePredicate clone = new SimplePredicate(this.keySortSpec, this.value);
+      return clone;
+    }
+
+    @Override
+    public SimplePredicateProto getProto() {
+      SimplePredicateProto.Builder builder = SimplePredicateProto.newBuilder();
+      builder.setKeySortSpec(keySortSpec.getProto());
+      builder.setValue(EvalNodeSerializer.serialize(value));
+      return builder.build();
+    }
+  }
+
+  private final URI indexPath;
+  private final Schema keySchema;
+  private final SimplePredicate[] predicates;
+
+  public IndexScanInfo(TableStats tableStats, IndexDesc indexDesc, SimplePredicate[] predicates) {
+    super(ScanTypeControl.INDEX_SCAN, tableStats);
+    this.indexPath = indexDesc.getIndexPath();
+    keySchema = new Schema();
+    this.predicates = predicates;
+    for (SimplePredicate predicate : predicates) {
+      keySchema.addColumn(predicate.getKeySortSpec().getSortKey());
+    }
+  }
+
+  public URI getIndexPath() {
+    return indexPath;
+  }
+
+  public Schema getKeySchema() {
+    return keySchema;
+  }
+
+  public SimplePredicate[] getPredicates() {
+    return predicates;
+  }
+}
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/LogicalPlanEqualityTester.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/LogicalPlanEqualityTester.java
index afe0c4d..c35194e 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/LogicalPlanEqualityTester.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/LogicalPlanEqualityTester.java
@@ -18,11 +18,11 @@
 
 package org.apache.tajo.plan.rewrite.rules;
 
-import org.apache.tajo.OverridableConf;
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.plan.LogicalPlan;
 import org.apache.tajo.plan.logical.LogicalNode;
 import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRule;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
 import org.apache.tajo.plan.serder.LogicalNodeDeserializer;
 import org.apache.tajo.plan.serder.LogicalNodeSerializer;
 import org.apache.tajo.plan.serder.PlanProto;
@@ -40,15 +40,16 @@
   }
 
   @Override
-  public boolean isEligible(OverridableConf queryContext, LogicalPlan plan) {
+  public boolean isEligible(LogicalPlanRewriteRuleContext context) {
     return true;
   }
 
   @Override
-  public LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException {
+  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException {
+    LogicalPlan plan = context.getPlan();
     LogicalNode root = plan.getRootBlock().getRoot();
     PlanProto.LogicalNodeTree serialized = LogicalNodeSerializer.serialize(plan.getRootBlock().getRoot());
-    LogicalNode deserialized = LogicalNodeDeserializer.deserialize(queryContext, null, serialized);
+    LogicalNode deserialized = LogicalNodeDeserializer.deserialize(context.getQueryContext(), null, serialized);
     assert root.deepEquals(deserialized);
     return plan;
   }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/PartitionedTableRewriter.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/PartitionedTableRewriter.java
index 71b9fd7..244d385 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/PartitionedTableRewriter.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/PartitionedTableRewriter.java
@@ -36,6 +36,7 @@
 import org.apache.tajo.plan.expr.*;
 import org.apache.tajo.plan.logical.*;
 import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRule;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
 import org.apache.tajo.plan.util.PlannerUtil;
 import org.apache.tajo.plan.visitor.BasicLogicalPlanVisitor;
 import org.apache.tajo.storage.Tuple;
@@ -59,8 +60,8 @@
   }
 
   @Override
-  public boolean isEligible(OverridableConf queryContext, LogicalPlan plan) {
-    for (LogicalPlan.QueryBlock block : plan.getQueryBlocks()) {
+  public boolean isEligible(LogicalPlanRewriteRuleContext context) {
+    for (LogicalPlan.QueryBlock block : context.getPlan().getQueryBlocks()) {
       for (RelationNode relation : block.getRelations()) {
         if (relation.getType() == NodeType.SCAN) {
           TableDesc table = ((ScanNode)relation).getTableDesc();
@@ -74,9 +75,10 @@
   }
 
   @Override
-  public LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException {
+  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException {
+    LogicalPlan plan = context.getPlan();
     LogicalPlan.QueryBlock rootBlock = plan.getRootBlock();
-    rewriter.visit(queryContext, plan, rootBlock, rootBlock.getRoot(), new Stack<LogicalNode>());
+    rewriter.visit(context.getQueryContext(), plan, rootBlock, rootBlock.getRoot(), new Stack<LogicalNode>());
     return plan;
   }
 
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/ProjectionPushDownRule.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/ProjectionPushDownRule.java
index 650a484..78a5dad 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/ProjectionPushDownRule.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/ProjectionPushDownRule.java
@@ -37,6 +37,7 @@
 import org.apache.tajo.plan.expr.*;
 import org.apache.tajo.plan.logical.*;
 import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRule;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
 import org.apache.tajo.plan.util.PlannerUtil;
 import org.apache.tajo.plan.visitor.BasicLogicalPlanVisitor;
 import org.apache.tajo.util.TUtil;
@@ -61,13 +62,13 @@
   }
 
   @Override
-  public boolean isEligible(OverridableConf queryContext, LogicalPlan plan) {
-    LogicalNode toBeOptimized = plan.getRootBlock().getRoot();
+  public boolean isEligible(LogicalPlanRewriteRuleContext context) {
+    LogicalNode toBeOptimized = context.getPlan().getRootBlock().getRoot();
 
     if (PlannerUtil.checkIfDDLPlan(toBeOptimized)) {
       return false;
     }
-    for (QueryBlock eachBlock: plan.getQueryBlocks()) {
+    for (QueryBlock eachBlock: context.getPlan().getQueryBlocks()) {
       if (eachBlock.hasTableExpression()) {
         return true;
       }
@@ -76,7 +77,8 @@
   }
 
   @Override
-  public LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException {
+  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext rewriteRuleContext) throws TajoException {
+    LogicalPlan plan = rewriteRuleContext.getPlan();
     LogicalPlan.QueryBlock rootBlock = plan.getRootBlock();
 
     LogicalPlan.QueryBlock topmostBlock = rootBlock;
@@ -524,6 +526,11 @@
           createTableNode.setChild(child);
           createTableNode.setInSchema(child.getOutSchema());
           break;
+        case CREATE_INDEX:
+          CreateIndexNode createIndexNode = (CreateIndexNode) parentNode;
+          createIndexNode.setChild(child);
+          createIndexNode.setInSchema(child.getOutSchema());
+          break;
         default:
           throw new TajoInternalError("unexpected parent node: " + parentNode.getType());
         }
@@ -1111,6 +1118,12 @@
   }
 
   @Override
+  public LogicalNode visitIndexScan(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                    IndexScanNode node, Stack<LogicalNode> stack) throws TajoException {
+    return visitScan(context, plan, block,node, stack);
+  }
+
+  @Override
   public LogicalNode visitTableSubQuery(Context upperContext, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                    TableSubQueryNode node, Stack<LogicalNode> stack) throws TajoException {
     Context childContext = new Context(plan, upperContext.requiredSet);
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/SeqScanInfo.java b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/SeqScanInfo.java
new file mode 100644
index 0000000..0c054bd
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/rewrite/rules/SeqScanInfo.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.tajo.plan.rewrite.rules;
+
+import org.apache.tajo.catalog.TableDesc;
+import org.apache.tajo.catalog.statistics.TableStats;
+
+public class SeqScanInfo extends AccessPathInfo {
+  private TableDesc tableDesc;
+
+  public SeqScanInfo(TableStats tableStats) {
+    super(ScanTypeControl.SEQ_SCAN, tableStats);
+  }
+
+  public SeqScanInfo(TableDesc tableDesc) {
+    this(tableDesc.getStats());
+    this.setTableDesc(tableDesc);
+  }
+
+  public TableDesc getTableDesc() {
+    return tableDesc;
+  }
+
+  public void setTableDesc(TableDesc tableDesc) {
+    this.tableDesc = tableDesc;
+  }
+}
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/serder/LogicalNodeDeserializer.java b/tajo-plan/src/main/java/org/apache/tajo/plan/serder/LogicalNodeDeserializer.java
index fe900c0..25897f2 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/serder/LogicalNodeDeserializer.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/serder/LogicalNodeDeserializer.java
@@ -34,10 +34,12 @@
 import org.apache.tajo.plan.Target;
 import org.apache.tajo.plan.expr.*;
 import org.apache.tajo.plan.logical.*;
+import org.apache.tajo.plan.rewrite.rules.IndexScanInfo.SimplePredicate;
 import org.apache.tajo.util.KeyValueSet;
 import org.apache.tajo.util.TUtil;
 
 import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.*;
 
 /**
@@ -124,6 +126,9 @@
       case SCAN:
         current = convertScan(context, evalContext, protoNode);
         break;
+      case INDEX_SCAN:
+        current = convertIndexScan(context, evalContext, protoNode);
+        break;
 
       case CREATE_TABLE:
         current = convertCreateTable(nodeMap, protoNode);
@@ -152,6 +157,13 @@
         current = convertTruncateTable(protoNode);
         break;
 
+      case CREATE_INDEX:
+        current = convertCreateIndex(nodeMap, protoNode);
+        break;
+      case DROP_INDEX:
+        current = convertDropIndex(protoNode);
+        break;
+
       default:
         throw new RuntimeException("Unknown NodeType: " + protoNode.getType().name());
       }
@@ -427,6 +439,23 @@
     scan.setOutSchema(convertSchema(protoNode.getOutSchema()));
   }
 
+  private static IndexScanNode convertIndexScan(OverridableConf context, EvalContext evalContext,
+                                                PlanProto.LogicalNode protoNode) {
+    IndexScanNode indexScan = new IndexScanNode(protoNode.getNodeId());
+    fillScanNode(context, evalContext, protoNode, indexScan);
+
+    PlanProto.IndexScanSpec indexScanSpec = protoNode.getIndexScan();
+    SimplePredicate[] predicates = new SimplePredicate[indexScanSpec.getPredicatesCount()];
+    for (int i = 0; i < predicates.length; i++) {
+      predicates[i] = new SimplePredicate(indexScanSpec.getPredicates(i));
+    }
+
+    indexScan.set(new Schema(indexScanSpec.getKeySchema()), predicates,
+        TUtil.stringToURI(indexScanSpec.getIndexPath()));
+
+    return indexScan;
+  }
+
   private static PartitionedTableScanNode convertPartitionScan(OverridableConf context, EvalContext evalContext,
                                                                PlanProto.LogicalNode protoNode) {
     PartitionedTableScanNode partitionedScan = new PartitionedTableScanNode(protoNode.getNodeId());
@@ -628,6 +657,46 @@
     return truncateTable;
   }
 
+  private static CreateIndexNode convertCreateIndex(Map<Integer, LogicalNode> nodeMap,
+                                                    PlanProto.LogicalNode protoNode) {
+    CreateIndexNode createIndex = new CreateIndexNode(protoNode.getNodeId());
+
+    PlanProto.CreateIndexNode createIndexProto = protoNode.getCreateIndex();
+    createIndex.setIndexName(createIndexProto.getIndexName());
+    createIndex.setIndexMethod(createIndexProto.getIndexMethod());
+    try {
+      createIndex.setIndexPath(new URI(createIndexProto.getIndexPath()));
+    } catch (URISyntaxException e) {
+      e.printStackTrace();
+    }
+    SortSpec[] keySortSpecs = new SortSpec[createIndexProto.getKeySortSpecsCount()];
+    for (int i = 0; i < keySortSpecs.length; i++) {
+      keySortSpecs[i] = new SortSpec(createIndexProto.getKeySortSpecs(i));
+    }
+    createIndex.setKeySortSpecs(new Schema(createIndexProto.getTargetRelationSchema()),
+        keySortSpecs);
+    createIndex.setUnique(createIndexProto.getIsUnique());
+    createIndex.setClustered(createIndexProto.getIsClustered());
+    if (createIndexProto.hasIndexProperties()) {
+      createIndex.setOptions(new KeyValueSet(createIndexProto.getIndexProperties()));
+    }
+    createIndex.setChild(nodeMap.get(createIndexProto.getChildSeq()));
+    createIndex.setInSchema(convertSchema(protoNode.getInSchema()));
+    createIndex.setOutSchema(convertSchema(protoNode.getOutSchema()));
+    createIndex.setExternal(createIndexProto.getIsExternal());
+
+    return createIndex;
+  }
+
+  private static DropIndexNode convertDropIndex(PlanProto.LogicalNode protoNode) {
+    DropIndexNode dropIndex = new DropIndexNode(protoNode.getNodeId());
+
+    PlanProto.DropIndexNode dropIndexProto = protoNode.getDropIndex();
+    dropIndex.setIndexName(dropIndexProto.getIndexName());
+
+    return dropIndex;
+  }
+
   private static AggregationFunctionCallEval [] convertAggFuncCallEvals(OverridableConf context, EvalContext evalContext,
                                                                        List<PlanProto.EvalNodeTree> evalTrees) {
     AggregationFunctionCallEval [] aggFuncs = new AggregationFunctionCallEval[evalTrees.size()];
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/serder/LogicalNodeSerializer.java b/tajo-plan/src/main/java/org/apache/tajo/plan/serder/LogicalNodeSerializer.java
index 0207932..fe69fc1 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/serder/LogicalNodeSerializer.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/serder/LogicalNodeSerializer.java
@@ -21,13 +21,16 @@
 import com.google.common.collect.Maps;
 import org.apache.hadoop.fs.Path;
 import org.apache.tajo.algebra.JoinType;
+import org.apache.tajo.catalog.SortSpec;
 import org.apache.tajo.catalog.proto.CatalogProtos;
+import org.apache.tajo.catalog.proto.CatalogProtos.SortSpecProto;
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.exception.TajoInternalError;
 import org.apache.tajo.exception.UnimplementedException;
 import org.apache.tajo.plan.LogicalPlan;
 import org.apache.tajo.plan.Target;
 import org.apache.tajo.plan.logical.*;
+import org.apache.tajo.plan.rewrite.rules.IndexScanInfo.SimplePredicate;
 import org.apache.tajo.plan.serder.PlanProto.AlterTableNode.AddColumn;
 import org.apache.tajo.plan.serder.PlanProto.AlterTableNode.RenameColumn;
 import org.apache.tajo.plan.serder.PlanProto.AlterTableNode.RenameTable;
@@ -105,6 +108,7 @@
     private LogicalNodeTree.Builder treeBuilder = LogicalNodeTree.newBuilder();
   }
 
+  @Override
   public LogicalNode visitRoot(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                LogicalRootNode root, Stack<LogicalNode> stack) throws TajoException {
     super.visitRoot(context, plan, block, root, stack);
@@ -139,6 +143,7 @@
     return node;
   }
 
+  @Override
   public LogicalNode visitEvalExpr(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                    EvalExprNode exprEval, Stack<LogicalNode> stack) throws TajoException {
     PlanProto.EvalExprNode.Builder exprEvalBuilder = PlanProto.EvalExprNode.newBuilder();
@@ -152,6 +157,7 @@
     return exprEval;
   }
 
+  @Override
   public LogicalNode visitProjection(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                      ProjectionNode projection, Stack<LogicalNode> stack) throws TajoException {
     super.visitProjection(context, plan, block, projection, stack);
@@ -189,6 +195,7 @@
     return limit;
   }
 
+  @Override
   public LogicalNode visitWindowAgg(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                     WindowAggNode windowAgg, Stack<LogicalNode> stack) throws TajoException {
     super.visitWindowAgg(context, plan, block, windowAgg, stack);
@@ -263,6 +270,7 @@
     return having;
   }
 
+  @Override
   public LogicalNode visitGroupBy(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                   GroupbyNode node, Stack<LogicalNode> stack) throws TajoException {
     super.visitGroupBy(context, plan, block, node, new Stack<LogicalNode>());
@@ -298,6 +306,7 @@
     return nodeBuilder;
   }
 
+  @Override
   public LogicalNode visitDistinctGroupby(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                           DistinctGroupbyNode node, Stack<LogicalNode> stack) throws TajoException {
     super.visitDistinctGroupby(context, plan, block, node, new Stack<LogicalNode>());
@@ -354,6 +363,7 @@
     return filter;
   }
 
+  @Override
   public LogicalNode visitJoin(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block, JoinNode join,
                           Stack<LogicalNode> stack) throws TajoException {
     super.visitJoin(context, plan, block, join, stack);
@@ -438,6 +448,26 @@
   }
 
   @Override
+  public LogicalNode visitIndexScan(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                    IndexScanNode node, Stack<LogicalNode> stack) throws TajoException {
+
+    PlanProto.ScanNode.Builder scanBuilder = buildScanNode(node);
+
+    PlanProto.IndexScanSpec.Builder indexScanSpecBuilder = PlanProto.IndexScanSpec.newBuilder();
+    indexScanSpecBuilder.setKeySchema(node.getKeySchema().getProto());
+    indexScanSpecBuilder.setIndexPath(node.getIndexPath().toString());
+    for (SimplePredicate predicate : node.getPredicates()) {
+      indexScanSpecBuilder.addPredicates(predicate.getProto());
+    }
+
+    PlanProto.LogicalNode.Builder nodeBuilder = createNodeBuilder(context, node);
+    nodeBuilder.setScan(scanBuilder);
+    nodeBuilder.setIndexScan(indexScanSpecBuilder);
+    context.treeBuilder.addNodes(nodeBuilder);
+    return node;
+  }
+
+  @Override
   public LogicalNode visitPartitionedTableScan(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                           PartitionedTableScanNode node, Stack<LogicalNode> stack)
       throws TajoException {
@@ -461,6 +491,7 @@
     return node;
   }
 
+  @Override
   public LogicalNode visitTableSubQuery(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                    TableSubQueryNode node, Stack<LogicalNode> stack) throws TajoException {
     super.visitTableSubQuery(context, plan, block, node, stack);
@@ -483,6 +514,7 @@
     return node;
   }
 
+  @Override
   public LogicalNode visitCreateTable(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                       CreateTableNode node, Stack<LogicalNode> stack) throws TajoException {
     super.visitCreateTable(context, plan, block, node, stack);
@@ -623,6 +655,7 @@
     return node;
   }
 
+  @Override
   public LogicalNode visitInsert(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                  InsertNode node, Stack<LogicalNode> stack) throws TajoException {
     super.visitInsert(context, plan, block, node, stack);
@@ -711,6 +744,48 @@
     return node;
   }
 
+  @Override
+  public LogicalNode visitCreateIndex(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                      CreateIndexNode node, Stack<LogicalNode> stack) throws TajoException {
+    super.visitCreateIndex(context, plan, block, node, new Stack<LogicalNode>());
+
+    PlanProto.CreateIndexNode.Builder createIndexBuilder = PlanProto.CreateIndexNode.newBuilder();
+    int [] childIds = registerGetChildIds(context, node);
+    createIndexBuilder.setChildSeq(childIds[0]);
+    createIndexBuilder.setIndexName(node.getIndexName());
+    createIndexBuilder.setIndexMethod(node.getIndexMethod());
+    createIndexBuilder.setIndexPath(node.getIndexPath().toString());
+    for (SortSpec sortSpec : node.getKeySortSpecs()) {
+      createIndexBuilder.addKeySortSpecs(sortSpec.getProto());
+    }
+    createIndexBuilder.setTargetRelationSchema(node.getTargetRelationSchema().getProto());
+    createIndexBuilder.setIsUnique(node.isUnique());
+    createIndexBuilder.setIsClustered(node.isClustered());
+    if (node.hasOptions()) {
+      createIndexBuilder.setIndexProperties(node.getOptions().getProto());
+    }
+    createIndexBuilder.setIsExternal(node.isExternal());
+
+    PlanProto.LogicalNode.Builder nodeBuilder = createNodeBuilder(context, node);
+    nodeBuilder.setCreateIndex(createIndexBuilder);
+    context.treeBuilder.addNodes(nodeBuilder);
+
+    return node;
+  }
+
+  @Override
+  public LogicalNode visitDropIndex(SerializeContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                    DropIndexNode node, Stack<LogicalNode> stack) {
+    PlanProto.DropIndexNode.Builder dropIndexBuilder = PlanProto.DropIndexNode.newBuilder();
+    dropIndexBuilder.setIndexName(node.getIndexName());
+
+    PlanProto.LogicalNode.Builder nodeBuilder = createNodeBuilder(context, node);
+    nodeBuilder.setDropIndex(dropIndexBuilder);
+    context.treeBuilder.addNodes(nodeBuilder);
+
+    return node;
+  }
+
   public static PlanProto.NodeType convertType(NodeType type) {
     return PlanProto.NodeType.valueOf(type.name());
   }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/util/IndexUtil.java b/tajo-plan/src/main/java/org/apache/tajo/plan/util/IndexUtil.java
new file mode 100644
index 0000000..73b33d5
--- /dev/null
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/util/IndexUtil.java
@@ -0,0 +1,72 @@
+/**
+ * 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.tajo.plan.util;
+
+import org.apache.tajo.catalog.SortSpec;
+import org.apache.tajo.plan.expr.*;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Stack;
+
+public class IndexUtil {
+  
+  public static String getIndexName(String indexName , SortSpec[] keys) {
+    StringBuilder builder = new StringBuilder();
+    builder.append(indexName + "_");
+    for(int i = 0 ; i < keys.length ; i ++) {
+      builder.append(keys[i].getSortKey().getSimpleName() + "_");
+    }
+    return builder.toString();
+  }
+
+  public static List<EvalNode> getAllEqualEvals(EvalNode qual) {
+    EvalTreeUtil.EvalFinder finder = new EvalTreeUtil.EvalFinder(EvalType.EQUAL);
+    finder.visitChild(null, qual, new Stack<EvalNode>());
+    return finder.getEvalNodes();
+  }
+  
+  private static class FieldAndValueFinder implements EvalNodeVisitor {
+    private LinkedList<BinaryEval> nodeList = new LinkedList<BinaryEval>();
+    
+    public LinkedList<BinaryEval> getNodeList () {
+      return this.nodeList;
+    }
+    
+    @Override
+    public void visit(EvalNode node) {
+      BinaryEval binaryEval = (BinaryEval) node;
+      switch(node.getType()) {
+      case AND:
+        break;
+      case EQUAL:
+        if( binaryEval.getLeftExpr().getType() == EvalType.FIELD
+          && binaryEval.getRightExpr().getType() == EvalType.CONST ) {
+          nodeList.add(binaryEval);
+        }
+        break;
+      case IS_NULL:
+        if( binaryEval.getLeftExpr().getType() == EvalType.FIELD
+          && binaryEval.getRightExpr().getType() == EvalType.CONST) {
+          nodeList.add(binaryEval);
+        }
+      }
+    }
+  }
+}
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/util/PlannerUtil.java b/tajo-plan/src/main/java/org/apache/tajo/plan/util/PlannerUtil.java
index 9c34826..c4a4367 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/util/PlannerUtil.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/util/PlannerUtil.java
@@ -69,10 +69,31 @@
         type == NodeType.CREATE_DATABASE ||
             type == NodeType.DROP_DATABASE ||
             (type == NodeType.CREATE_TABLE && !((CreateTableNode) baseNode).hasSubQuery()) ||
-            baseNode.getType() == NodeType.DROP_TABLE ||
-            baseNode.getType() == NodeType.ALTER_TABLESPACE ||
-            baseNode.getType() == NodeType.ALTER_TABLE ||
-            baseNode.getType() == NodeType.TRUNCATE_TABLE;
+            type == NodeType.DROP_TABLE ||
+            type == NodeType.ALTER_TABLESPACE ||
+            type == NodeType.ALTER_TABLE ||
+            type == NodeType.TRUNCATE_TABLE ||
+            type == NodeType.CREATE_INDEX ||
+            type == NodeType.DROP_INDEX;
+  }
+
+  /**
+   * Most update queries require only the updates to the catalog information,
+   * but some queries such as "CREATE INDEX" or CTAS requires distributed execution on multiple cluster nodes.
+   * This function checks whether the given DDL plan requires distributed execution or not.
+   * @param node the root node of a query plan
+   * @return Return true if the input query plan requires distributed execution. Otherwise, return false.
+   */
+  public static boolean isDistExecDDL(LogicalNode node) {
+    LogicalNode baseNode = node;
+    if (node instanceof LogicalRootNode) {
+      baseNode = ((LogicalRootNode) node).getChild();
+    }
+
+    NodeType type = baseNode.getType();
+
+    return type == NodeType.CREATE_INDEX && !((CreateIndexNode)baseNode).isExternal() ||
+        type == NodeType.CREATE_TABLE && ((CreateTableNode)baseNode).hasSubQuery();
   }
 
   /**
@@ -386,6 +407,12 @@
         QueryBlock block, PartitionedTableScanNode node, Stack<LogicalNode> stack) {
       return node;
     }
+
+    @Override
+    public LogicalNode visitIndexScan(ReplacerContext context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                      IndexScanNode node, Stack<LogicalNode> stack) throws TajoException {
+      return node;
+    }
   }
 
   public static void replaceNode(LogicalNode plan, LogicalNode newNode, NodeType type) {
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/BasicLogicalPlanVisitor.java b/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/BasicLogicalPlanVisitor.java
index ebb5c80..2c8f344 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/BasicLogicalPlanVisitor.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/BasicLogicalPlanVisitor.java
@@ -21,7 +21,6 @@
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.exception.TajoInternalError;
 import org.apache.tajo.plan.LogicalPlan;
-import org.apache.tajo.plan.PlanningException;
 import org.apache.tajo.plan.logical.*;
 
 import java.util.Stack;
@@ -113,6 +112,9 @@
       case PARTITIONS_SCAN:
         current = visitPartitionedTableScan(context, plan, block, (PartitionedTableScanNode) node, stack);
         break;
+      case INDEX_SCAN:
+        current = visitIndexScan(context, plan, block, (IndexScanNode) node, stack);
+        break;
       case STORE:
         current = visitStoreTable(context, plan, block, (StoreTableNode) node, stack);
         break;
@@ -137,9 +139,15 @@
       case ALTER_TABLE:
         current = visitAlterTable(context, plan, block, (AlterTableNode) node, stack);
         break;
+      case CREATE_INDEX:
+        current = visitCreateIndex(context, plan, block, (CreateIndexNode) node, stack);
+        break;
       case TRUNCATE_TABLE:
         current = visitTruncateTable(context, plan, block, (TruncateTableNode) node, stack);
         break;
+      case DROP_INDEX:
+        current = visitDropIndex(context, plan, block, (DropIndexNode) node, stack);
+        break;
       default:
         throw new TajoInternalError("Unknown logical node type: " + node.getType());
     }
@@ -312,6 +320,12 @@
   }
 
   @Override
+  public RESULT visitIndexScan(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, IndexScanNode node,
+                               Stack<LogicalNode> stack) throws TajoException {
+    return null;
+  }
+
+  @Override
   public RESULT visitPartitionedTableScan(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                           PartitionedTableScanNode node, Stack<LogicalNode> stack)
       throws TajoException {
@@ -379,6 +393,22 @@
     }
 
   @Override
+  public RESULT visitCreateIndex(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, CreateIndexNode node,
+                                 Stack<LogicalNode> stack) throws TajoException {
+    RESULT result = null;
+    stack.push(node);
+    result = visit(context, plan, block, node.getChild(), stack);
+    stack.pop();
+    return result;
+  }
+
+  @Override
+  public RESULT visitDropIndex(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, DropIndexNode node,
+                               Stack<LogicalNode> stack) {
+    return null;
+  }
+
+  @Override
   public RESULT visitTruncateTable(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                    TruncateTableNode node, Stack<LogicalNode> stack) throws TajoException {
     return null;
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/ExplainLogicalPlanVisitor.java b/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/ExplainLogicalPlanVisitor.java
index 925742a..f826479 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/ExplainLogicalPlanVisitor.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/ExplainLogicalPlanVisitor.java
@@ -22,7 +22,6 @@
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.plan.LogicalPlan;
 import org.apache.tajo.plan.PlanString;
-import org.apache.tajo.plan.PlanningException;
 import org.apache.tajo.plan.logical.*;
 
 import java.util.Stack;
@@ -235,6 +234,19 @@
     return node;
   }
 
+  @Override
+  public LogicalNode visitCreateIndex(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                     CreateIndexNode node, Stack<LogicalNode> stack) throws TajoException {
+    return visitUnaryNode(context, plan, block, node, stack);
+  }
+
+  @Override
+  public LogicalNode visitDropIndex(Context context, LogicalPlan plan, LogicalPlan.QueryBlock block,
+                                    DropIndexNode node, Stack<LogicalNode> stack) {
+    context.add(context.depth, node.getPlanString());
+    return node;
+  }
+
   public static String printDepthString(int maxDepth, DepthString planStr) {
     StringBuilder output = new StringBuilder();
     String pad = new String(new char[planStr.getDepth() * 3]).replace('\0', ' ');
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/LogicalPlanVisitor.java b/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/LogicalPlanVisitor.java
index dd832ae..9e9389d 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/LogicalPlanVisitor.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/LogicalPlanVisitor.java
@@ -20,7 +20,6 @@
 
 import org.apache.tajo.exception.TajoException;
 import org.apache.tajo.plan.LogicalPlan;
-import org.apache.tajo.plan.PlanningException;
 import org.apache.tajo.plan.logical.*;
 
 import java.util.Stack;
@@ -76,6 +75,9 @@
   RESULT visitScan(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, ScanNode node,
                    Stack<LogicalNode> stack) throws TajoException;
 
+  RESULT visitIndexScan(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, IndexScanNode node,
+                        Stack<LogicalNode> stack) throws TajoException;
+
   RESULT visitPartitionedTableScan(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block,
                                    PartitionedTableScanNode node, Stack<LogicalNode> stack) throws TajoException;
 
@@ -103,6 +105,12 @@
   RESULT visitAlterTable(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, AlterTableNode node,
                          Stack<LogicalNode> stack) throws TajoException;
 
+  RESULT visitCreateIndex(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, CreateIndexNode node,
+                               Stack<LogicalNode> stack) throws TajoException;
+
+  RESULT visitDropIndex(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, DropIndexNode node,
+                        Stack<LogicalNode> stack) throws TajoException;
+
   RESULT visitTruncateTable(CONTEXT context, LogicalPlan plan, LogicalPlan.QueryBlock block, TruncateTableNode node,
                          Stack<LogicalNode> stack) throws TajoException;
 }
diff --git a/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/SimpleAlgebraVisitor.java b/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/SimpleAlgebraVisitor.java
index 989af88..4854d7f 100644
--- a/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/SimpleAlgebraVisitor.java
+++ b/tajo-plan/src/main/java/org/apache/tajo/plan/visitor/SimpleAlgebraVisitor.java
@@ -20,7 +20,6 @@
 
 import org.apache.tajo.algebra.*;
 import org.apache.tajo.exception.TajoException;
-import org.apache.tajo.plan.PlanningException;
 import org.apache.tajo.plan.algebra.BaseAlgebraVisitor;
 
 import java.util.Stack;
diff --git a/tajo-plan/src/main/proto/Plan.proto b/tajo-plan/src/main/proto/Plan.proto
index 537d180..4019322 100644
--- a/tajo-plan/src/main/proto/Plan.proto
+++ b/tajo-plan/src/main/proto/Plan.proto
@@ -46,7 +46,7 @@
   TABLE_SUBQUERY = 15;
   SCAN = 16;
   PARTITIONS_SCAN = 17;
-  BST_INDEX_SCAN = 18;
+  INDEX_SCAN = 18;
   STORE = 19;
   INSERT = 20;
 
@@ -57,6 +57,8 @@
   ALTER_TABLESPACE = 25;
   ALTER_TABLE = 26;
   TRUNCATE_TABLE = 27;
+  CREATE_INDEX = 28;
+  DROP_INDEX = 29;
 }
 
 message LogicalNodeTree {
@@ -72,31 +74,35 @@
 
   optional ScanNode scan = 6;
   optional PartitionScanSpec partitionScan = 7;
-  optional JoinNode join = 8;
-  optional FilterNode filter = 9;
-  optional GroupbyNode groupby = 10;
-  optional DistinctGroupbyNode distinctGroupby = 11;
-  optional SortNode sort = 12;
-  optional LimitNode limit = 13;
-  optional WindowAggNode windowAgg = 14;
-  optional ProjectionNode projection = 15;
-  optional EvalExprNode exprEval = 16;
-  optional UnionNode union = 17;
-  optional TableSubQueryNode tableSubQuery = 18;
-  optional PersistentStoreNode persistentStore = 19;
-  optional StoreTableNodeSpec storeTable = 20;
-  optional InsertNodeSpec insert = 21;
-  optional CreateTableNodeSpec createTable = 22;
-  optional RootNode root = 23;
-  optional SetSessionNode setSession = 24;
+  optional IndexScanSpec indexScan = 8;
+  optional JoinNode join = 9;
+  optional FilterNode filter = 10;
+  optional GroupbyNode groupby = 11;
+  optional DistinctGroupbyNode distinctGroupby = 12;
+  optional SortNode sort = 13;
+  optional LimitNode limit = 14;
+  optional WindowAggNode windowAgg = 15;
+  optional ProjectionNode projection = 16;
+  optional EvalExprNode exprEval = 17;
+  optional UnionNode union = 18;
+  optional TableSubQueryNode tableSubQuery = 19;
+  optional PersistentStoreNode persistentStore = 20;
+  optional StoreTableNodeSpec storeTable = 21;
+  optional InsertNodeSpec insert = 22;
+  optional CreateTableNodeSpec createTable = 23;
+  optional RootNode root = 24;
+  optional SetSessionNode setSession = 25;
 
-  optional CreateDatabaseNode createDatabase = 25;
-  optional DropDatabaseNode dropDatabase = 26;
-  optional DropTableNode dropTable = 27;
+  optional CreateDatabaseNode createDatabase = 26;
+  optional DropDatabaseNode dropDatabase = 27;
+  optional DropTableNode dropTable = 28;
 
-  optional AlterTablespaceNode alterTablespace = 28;
-  optional AlterTableNode alterTable = 29;
-  optional TruncateTableNode truncateTableNode = 30;
+  optional AlterTablespaceNode alterTablespace = 29;
+  optional AlterTableNode alterTable = 30;
+  optional TruncateTableNode truncateTableNode = 31;
+
+  optional CreateIndexNode createIndex = 32;
+  optional DropIndexNode dropIndex = 33;
 }
 
 message ScanNode {
@@ -112,6 +118,17 @@
   repeated string paths = 1;
 }
 
+message IndexScanSpec {
+  required SchemaProto keySchema = 1;
+  required string indexPath = 2;
+  repeated SimplePredicateProto predicates = 3;
+}
+
+message SimplePredicateProto {
+  required SortSpecProto keySortSpec = 1;
+  required Datum value = 2;
+}
+
 message FilterNode {
   required int32 childSeq = 1;
   required EvalNodeTree qual = 2;
@@ -314,6 +331,23 @@
   optional AlterPartition alterPartition = 7;
 }
 
+message CreateIndexNode {
+  required int32 childSeq = 1;
+  required string indexName = 2;
+  required IndexMethod indexMethod = 3;
+  required string indexPath = 4;
+  repeated SortSpecProto keySortSpecs = 5;
+  required SchemaProto targetRelationSchema = 6;
+  optional bool isUnique = 7 [default = false];
+  optional bool isClustered = 8 [default = false];
+  optional KeyValueSetProto indexProperties = 9;
+  optional bool isExternal = 10;
+}
+
+message DropIndexNode {
+  required string indexName = 1;
+}
+
 enum EvalType {
   NOT = 0;
   AND = 1;
diff --git a/tajo-storage/tajo-storage-common/src/main/java/org/apache/tajo/storage/OldStorageManager.java b/tajo-storage/tajo-storage-common/src/main/java/org/apache/tajo/storage/OldStorageManager.java
index ef33a8e..d12c6bd 100644
--- a/tajo-storage/tajo-storage-common/src/main/java/org/apache/tajo/storage/OldStorageManager.java
+++ b/tajo-storage/tajo-storage-common/src/main/java/org/apache/tajo/storage/OldStorageManager.java
@@ -174,22 +174,6 @@
   }
 
   /**
-   * Returns Scanner instance.
-   *
-   * @param conf The system property
-   * @param meta The table meta
-   * @param schema The input schema
-   * @param fragment The fragment for scanning
-   * @param target The output schema
-   * @return Scanner instance
-   * @throws IOException
-   */
-  public static synchronized SeekableScanner getSeekableScanner(
-      TajoConf conf, TableMeta meta, Schema schema, Fragment fragment, Schema target) throws IOException {
-    return (SeekableScanner)getStorageManager(conf, meta.getStoreType()).getScanner(meta, schema, fragment, target);
-  }
-
-  /**
    * Creates a scanner instance.
    *
    * @param theClass Concrete class of scanner
diff --git a/tajo-storage/tajo-storage-common/src/main/java/org/apache/tajo/storage/Tablespace.java b/tajo-storage/tajo-storage-common/src/main/java/org/apache/tajo/storage/Tablespace.java
index f8883b0..4d7fac4 100644
--- a/tajo-storage/tajo-storage-common/src/main/java/org/apache/tajo/storage/Tablespace.java
+++ b/tajo-storage/tajo-storage-common/src/main/java/org/apache/tajo/storage/Tablespace.java
@@ -268,6 +268,21 @@
   }
 
   /**
+   * Returns Scanner instance.
+   *
+   * @param meta The table meta
+   * @param schema The input schema
+   * @param fragment The fragment for scanning
+   * @param target The output schema
+   * @return Scanner instance
+   * @throws IOException
+   */
+  public synchronized SeekableScanner getSeekableScanner(TableMeta meta, Schema schema, FragmentProto fragment,
+                                                         Schema target) throws IOException {
+    return (SeekableScanner)this.getScanner(meta, schema, fragment, target);
+  }
+
+  /**
    * Returns Appender instance.
    * @param queryContext Query property.
    * @param taskAttemptId Task id.
diff --git a/tajo-storage/tajo-storage-hbase/src/main/java/org/apache/tajo/storage/hbase/HBaseTablespace.java b/tajo-storage/tajo-storage-hbase/src/main/java/org/apache/tajo/storage/hbase/HBaseTablespace.java
index 00cac77..3060d53 100644
--- a/tajo-storage/tajo-storage-hbase/src/main/java/org/apache/tajo/storage/hbase/HBaseTablespace.java
+++ b/tajo-storage/tajo-storage-hbase/src/main/java/org/apache/tajo/storage/hbase/HBaseTablespace.java
@@ -49,6 +49,7 @@
 import org.apache.tajo.plan.logical.LogicalNode;
 import org.apache.tajo.plan.logical.NodeType;
 import org.apache.tajo.plan.logical.ScanNode;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
 import org.apache.tajo.storage.*;
 import org.apache.tajo.storage.fragment.Fragment;
 import org.apache.tajo.util.*;
@@ -1091,8 +1092,8 @@
 
   @Override
   public void rewritePlan(OverridableConf context, LogicalPlan plan) throws TajoException {
-    if (REWRITE_RULE.isEligible(context, plan)) {
-      REWRITE_RULE.rewrite(context, plan);
+    if (REWRITE_RULE.isEligible(new LogicalPlanRewriteRuleContext(context, plan))) {
+      REWRITE_RULE.rewrite(new LogicalPlanRewriteRuleContext(context, plan));
     }
   }
 
diff --git a/tajo-storage/tajo-storage-hbase/src/main/java/org/apache/tajo/storage/hbase/SortedInsertRewriter.java b/tajo-storage/tajo-storage-hbase/src/main/java/org/apache/tajo/storage/hbase/SortedInsertRewriter.java
index db9f3c8..40789ac 100644
--- a/tajo-storage/tajo-storage-hbase/src/main/java/org/apache/tajo/storage/hbase/SortedInsertRewriter.java
+++ b/tajo-storage/tajo-storage-hbase/src/main/java/org/apache/tajo/storage/hbase/SortedInsertRewriter.java
@@ -18,7 +18,6 @@
 
 package org.apache.tajo.storage.hbase;
 
-import org.apache.tajo.OverridableConf;
 import org.apache.tajo.catalog.Column;
 import org.apache.tajo.catalog.Schema;
 import org.apache.tajo.catalog.SortSpec;
@@ -28,6 +27,7 @@
 import org.apache.tajo.plan.logical.*;
 import org.apache.tajo.plan.logical.SortNode.SortPurpose;
 import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRule;
+import org.apache.tajo.plan.rewrite.LogicalPlanRewriteRuleContext;
 import org.apache.tajo.util.KeyValueSet;
 
 import java.io.IOException;
@@ -46,9 +46,9 @@
   }
 
   @Override
-  public boolean isEligible(OverridableConf queryContext, LogicalPlan plan) {
-    boolean hbaseMode = "false".equalsIgnoreCase(queryContext.get(HBaseStorageConstants.INSERT_PUT_MODE, "false"));
-    LogicalRootNode rootNode = plan.getRootBlock().getRoot();
+  public boolean isEligible(LogicalPlanRewriteRuleContext context) {
+    boolean hbaseMode = "false".equalsIgnoreCase(context.getQueryContext().get(HBaseStorageConstants.INSERT_PUT_MODE, "false"));
+    LogicalRootNode rootNode = context.getPlan().getRootBlock().getRoot();
     LogicalNode node = rootNode.getChild();
     return hbaseMode && node.getType() == NodeType.CREATE_TABLE || node.getType() == NodeType.INSERT;
   }
@@ -69,7 +69,8 @@
   }
 
   @Override
-  public LogicalPlan rewrite(OverridableConf queryContext, LogicalPlan plan) throws TajoException {
+  public LogicalPlan rewrite(LogicalPlanRewriteRuleContext context) throws TajoException {
+    LogicalPlan plan = context.getPlan();
     LogicalRootNode rootNode = plan.getRootBlock().getRoot();
 
     StoreTableNode storeTable = rootNode.getChild();
diff --git a/tajo-storage/tajo-storage-hdfs/src/test/java/org/apache/tajo/storage/index/TestBSTIndex.java b/tajo-storage/tajo-storage-hdfs/src/test/java/org/apache/tajo/storage/index/TestBSTIndex.java
index ed010c0..5437b0d 100644
--- a/tajo-storage/tajo-storage-hdfs/src/test/java/org/apache/tajo/storage/index/TestBSTIndex.java
+++ b/tajo-storage/tajo-storage-hdfs/src/test/java/org/apache/tajo/storage/index/TestBSTIndex.java
@@ -124,7 +124,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -147,7 +148,8 @@
     tuple = new VTuple(keySchema.size());
     BSTIndexReader reader = bst.getIndexReader(new Path(testDir, "testFindValue_" + storeType + ".idx"), keySchema, comp);
     reader.open();
-    scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     for (int i = 0; i < TUPLE_NUM - 1; i++) {
@@ -226,7 +228,8 @@
     BSTIndexReader reader = bst.getIndexReader(new Path(testDir, "testBuildIndexWithAppender_" + storeType + ".idx"),
         keySchema, comp);
     reader.open();
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     for (int i = 0; i < TUPLE_NUM - 1; i++) {
@@ -289,7 +292,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -361,7 +365,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -384,7 +389,8 @@
     BSTIndexReader reader = bst.getIndexReader(new Path(testDir, "testFindNextKeyValue_" + storeType + ".idx"),
         keySchema, comp);
     reader.open();
-    scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple result;
@@ -452,7 +458,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -475,7 +482,8 @@
     BSTIndexReader reader = bst.getIndexReader(new Path(testDir, "testFindNextKeyOmittedValue_" + storeType + ".idx"),
         keySchema, comp);
     reader.open();
-    scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple result;
@@ -532,7 +540,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -557,7 +566,8 @@
     BSTIndexReader reader = bst.getIndexReader(new Path(testDir, "testFindMinValue_" + storeType + ".idx"),
         keySchema, comp);
     reader.open();
-    scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     tuple.put(0, DatumFactory.createInt8(0));
@@ -616,7 +626,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -722,7 +733,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -804,7 +816,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -829,7 +842,8 @@
     BSTIndexReader reader = bst.getIndexReader(new Path(testDir, "testFindValueDescOrder_" + storeType + ".idx"),
         keySchema, comp);
     reader.open();
-    scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     for (int i = (TUPLE_NUM - 1); i > 0; i--) {
@@ -894,7 +908,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple keyTuple;
@@ -922,7 +937,8 @@
     assertEquals(keySchema, reader.getKeySchema());
     assertEquals(comp, reader.getComparator());
 
-    scanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    scanner = OldStorageManager.getStorageManager(conf, meta.getStoreType()).
+        getSeekableScanner(meta, schema, tablet.getProto(), schema);
     scanner.init();
 
     Tuple result;
diff --git a/tajo-storage/tajo-storage-hdfs/src/test/java/org/apache/tajo/storage/index/TestSingleCSVFileBSTIndex.java b/tajo-storage/tajo-storage-hdfs/src/test/java/org/apache/tajo/storage/index/TestSingleCSVFileBSTIndex.java
index c198965..a001492 100644
--- a/tajo-storage/tajo-storage-hdfs/src/test/java/org/apache/tajo/storage/index/TestSingleCSVFileBSTIndex.java
+++ b/tajo-storage/tajo-storage-hdfs/src/test/java/org/apache/tajo/storage/index/TestSingleCSVFileBSTIndex.java
@@ -111,7 +111,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
 
-    SeekableScanner fileScanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner fileScanner = OldStorageManager.getStorageManager(conf, meta.getStoreType())
+        .getSeekableScanner(meta, schema, tablet.getProto(), schema);
     fileScanner.init();
     Tuple keyTuple;
     long offset;
@@ -135,7 +136,8 @@
     BSTIndexReader reader = bst.getIndexReader(new Path(testDir,
         "FindValueInCSV.idx"), keySchema, comp);
     reader.open();
-    fileScanner = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    fileScanner = OldStorageManager.getStorageManager(conf, meta.getStoreType())
+        .getSeekableScanner(meta, schema, tablet.getProto(), schema);
     fileScanner.init();
     for (int i = 0; i < TUPLE_NUM - 1; i++) {
       tuple.put(0, DatumFactory.createInt8(i));
@@ -200,7 +202,8 @@
     creater.setLoadNum(LOAD_NUM);
     creater.open();
     
-    SeekableScanner fileScanner  = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    SeekableScanner fileScanner  = OldStorageManager.getStorageManager(conf, meta.getStoreType())
+        .getSeekableScanner(meta, schema, tablet.getProto(), schema);
     fileScanner.init();
     Tuple keyTuple;
     long offset;
@@ -221,7 +224,8 @@
     
     BSTIndexReader reader = bst.getIndexReader(new Path(testDir, "FindNextKeyValueInCSV.idx"), keySchema, comp);
     reader.open();
-    fileScanner  = OldStorageManager.getSeekableScanner(conf, meta, schema, tablet, schema);
+    fileScanner  = OldStorageManager.getStorageManager(conf, meta.getStoreType())
+        .getSeekableScanner(meta, schema, tablet.getProto(), schema);
     fileScanner.init();
     Tuple result;
     for(int i = 0 ; i < TUPLE_NUM -1 ; i ++) {