You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by ja...@apache.org on 2015/02/04 21:34:39 UTC

[4/4] phoenix git commit: PHOENIX-514 Support functional indexes (Thomas D'Silva)

PHOENIX-514 Support functional indexes (Thomas D'Silva)


Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/8c340f5a
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/8c340f5a
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/8c340f5a

Branch: refs/heads/master
Commit: 8c340f5a6c030d009a4fa75444096a1999aea5c5
Parents: d6e7846
Author: James Taylor <ja...@apache.org>
Authored: Wed Feb 4 12:30:51 2015 -0800
Committer: James Taylor <ja...@apache.org>
Committed: Wed Feb 4 12:30:51 2015 -0800

----------------------------------------------------------------------
 .../org/apache/phoenix/end2end/HashJoinIT.java  |  12 +-
 .../apache/phoenix/end2end/PercentileIT.java    |   7 +-
 .../java/org/apache/phoenix/end2end/ViewIT.java |   2 +-
 .../phoenix/end2end/index/ImmutableIndexIT.java |  76 +-
 .../end2end/index/IndexExpressionIT.java        | 866 +++++++++++++++++++
 .../phoenix/end2end/index/IndexMetadataIT.java  |  20 +-
 phoenix-core/src/main/antlr3/PhoenixSQL.g       |  32 +-
 .../IndexHalfStoreFileReaderGenerator.java      |   4 +-
 .../apache/phoenix/compile/DeleteCompiler.java  |   4 +-
 .../phoenix/compile/ExpressionCompiler.java     |   9 +-
 .../apache/phoenix/compile/FromCompiler.java    |   6 +-
 .../apache/phoenix/compile/HavingCompiler.java  |   4 +-
 .../compile/IndexExpressionCompiler.java        |  53 ++
 .../apache/phoenix/compile/JoinCompiler.java    |   8 +-
 .../phoenix/compile/PostIndexDDLCompiler.java   |  47 +-
 .../apache/phoenix/compile/UpsertCompiler.java  |   2 +-
 .../apache/phoenix/compile/WhereCompiler.java   |   4 +-
 .../coprocessor/MetaDataEndpointImpl.java       |  44 +-
 .../UngroupedAggregateRegionObserver.java       |   3 +-
 .../coprocessor/generated/PTableProtos.java     | 215 ++++-
 .../phoenix/exception/SQLExceptionCode.java     |   6 +
 .../apache/phoenix/execute/BaseQueryPlan.java   |   2 +-
 .../apache/phoenix/execute/MutationState.java   |   4 +-
 .../phoenix/expression/CoerceExpression.java    |  10 +-
 .../expression/RowKeyColumnExpression.java      |   6 +-
 .../apache/phoenix/hbase/index/ValueGetter.java |   2 +
 .../index/covered/data/LazyValueGetter.java     |   5 +
 .../hbase/index/util/IndexManagementUtil.java   |  18 -
 .../apache/phoenix/index/IndexMaintainer.java   | 333 ++++---
 .../apache/phoenix/index/PhoenixIndexCodec.java |   2 +-
 .../index/PhoenixIndexFailurePolicy.java        |  21 +-
 .../phoenix/jdbc/PhoenixDatabaseMetaData.java   |   1 +
 .../apache/phoenix/jdbc/PhoenixStatement.java   |   9 +-
 .../apache/phoenix/optimize/QueryOptimizer.java |  12 +-
 .../apache/phoenix/parse/BetweenParseNode.java  |  22 +
 .../org/apache/phoenix/parse/BindParseNode.java |  22 +
 .../org/apache/phoenix/parse/CastParseNode.java |  38 +
 .../org/apache/phoenix/parse/ColumnDef.java     |  14 +-
 .../org/apache/phoenix/parse/ColumnName.java    |   2 +-
 .../apache/phoenix/parse/ColumnParseNode.java   |   1 +
 .../apache/phoenix/parse/CompoundParseNode.java |  32 +-
 .../phoenix/parse/CreateIndexStatement.java     |  10 +-
 .../apache/phoenix/parse/ExistsParseNode.java   |  22 +
 .../phoenix/parse/FamilyWildcardParseNode.java  |  22 +
 .../apache/phoenix/parse/FunctionParseNode.java |  31 +
 .../apache/phoenix/parse/InListParseNode.java   |  22 +
 .../org/apache/phoenix/parse/InParseNode.java   |  25 +
 .../parse/IndexExpressionParseNodeRewriter.java | 104 +++
 .../phoenix/parse/IndexKeyConstraint.java       |  12 +-
 .../apache/phoenix/parse/IsNullParseNode.java   |  22 +
 .../org/apache/phoenix/parse/LikeParseNode.java |  26 +
 .../apache/phoenix/parse/LiteralParseNode.java  |  21 +
 .../org/apache/phoenix/parse/NamedNode.java     |   2 +-
 .../apache/phoenix/parse/NamedParseNode.java    |  30 +
 .../apache/phoenix/parse/ParseNodeFactory.java  |  34 +-
 .../phoenix/parse/SequenceValueParseNode.java   |  29 +
 .../apache/phoenix/parse/SubqueryParseNode.java |  28 +
 .../org/apache/phoenix/parse/TableName.java     |   3 +-
 .../phoenix/parse/TableWildcardParseNode.java   |  29 +
 .../apache/phoenix/parse/WildcardParseNode.java |  24 +-
 .../apache/phoenix/schema/DelegateColumn.java   |   6 +
 .../apache/phoenix/schema/DelegateTable.java    |   9 +-
 .../apache/phoenix/schema/MetaDataClient.java   | 173 ++--
 .../java/org/apache/phoenix/schema/PColumn.java |   3 +
 .../org/apache/phoenix/schema/PColumnImpl.java  |  26 +-
 .../apache/phoenix/schema/PMetaDataImpl.java    |   2 +-
 .../java/org/apache/phoenix/schema/PTable.java  |   7 +-
 .../org/apache/phoenix/schema/PTableImpl.java   |  27 +-
 .../org/apache/phoenix/schema/SaltingUtil.java  |   2 +-
 .../phoenix/schema/tuple/ValueGetterTuple.java  |  93 ++
 .../java/org/apache/phoenix/util/IndexUtil.java |  15 +-
 .../expression/ColumnExpressionTest.java        |   8 +-
 .../phoenix/index/IndexMaintainerTest.java      |  14 +-
 .../iterate/AggregateResultScannerTest.java     |   5 +
 .../java/org/apache/phoenix/query/BaseTest.java |  26 +-
 phoenix-protocol/src/main/PTable.proto          |   1 +
 76 files changed, 2475 insertions(+), 418 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java
index 781bfea..76eab22 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/HashJoinIT.java
@@ -289,7 +289,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "CLIENT MERGE SORT\n" +
                 "    PARALLEL INNER-JOIN TABLE 0\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
-                "            SERVER FILTER BY order_id != '000000000000003'\n" +
+                "            SERVER FILTER BY \"order_id\" != '000000000000003'\n" +
                 "            PARALLEL INNER-JOIN TABLE 0\n" +
                 "                CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME + "\n" +
                 "                    SERVER FILTER BY NAME != 'T3'\n" +
@@ -380,7 +380,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "CLIENT MERGE SORT\n" +
                 "    PARALLEL INNER-JOIN TABLE 0\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
-                "            SERVER FILTER BY order_id != '000000000000003'\n" +
+                "            SERVER FILTER BY \"order_id\" != '000000000000003'\n" +
                 "            PARALLEL INNER-JOIN TABLE 0\n" +
                 "                CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ITEM_TABLE_DISPLAY_NAME + "\n" +
                 "                    SERVER FILTER BY NAME != 'T3'\n" +
@@ -655,7 +655,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "CLIENT MERGE SORT\n" +
                 "    PARALLEL INNER-JOIN TABLE 0\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
-                "            SERVER FILTER BY order_id != '000000000000003'\n" +
+                "            SERVER FILTER BY \"order_id\" != '000000000000003'\n" +
                 "            PARALLEL INNER-JOIN TABLE 0\n" +
                 "                CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" +
                 "                    SERVER FILTER BY NAME != 'T3'\n" +
@@ -747,7 +747,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "CLIENT MERGE SORT\n" +
                 "    PARALLEL INNER-JOIN TABLE 0\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
-                "            SERVER FILTER BY order_id != '000000000000003'\n" +
+                "            SERVER FILTER BY \"order_id\" != '000000000000003'\n" +
                 "            PARALLEL INNER-JOIN TABLE 0\n" +
                 "                CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_SCHEMA + ".idx_item\n" +
                 "                    SERVER FILTER BY NAME != 'T3'\n" +
@@ -1039,7 +1039,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "CLIENT MERGE SORT\n" +
                 "    PARALLEL INNER-JOIN TABLE 0\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
-                "            SERVER FILTER BY order_id != '000000000000003'\n" +
+                "            SERVER FILTER BY \"order_id\" != '000000000000003'\n" +
                 "            PARALLEL INNER-JOIN TABLE 0\n" +
                 "                CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + MetaDataUtil.LOCAL_INDEX_TABLE_PREFIX +""+ JOIN_ITEM_TABLE_DISPLAY_NAME +" [-32768]\n" +
                 "                    SERVER FILTER BY NAME != 'T3'\n" +
@@ -1134,7 +1134,7 @@ public class HashJoinIT extends BaseHBaseManagedTimeIT {
                 "CLIENT MERGE SORT\n" +
                 "    PARALLEL INNER-JOIN TABLE 0\n" +
                 "        CLIENT PARALLEL 1-WAY FULL SCAN OVER " + JOIN_ORDER_TABLE_DISPLAY_NAME + "\n" +
-                "            SERVER FILTER BY order_id != '000000000000003'\n" +
+                "            SERVER FILTER BY \"order_id\" != '000000000000003'\n" +
                 "            PARALLEL INNER-JOIN TABLE 0\n" +
                 "                CLIENT PARALLEL 1-WAY RANGE SCAN OVER " +  MetaDataUtil.LOCAL_INDEX_TABLE_PREFIX +""+JOIN_ITEM_TABLE_DISPLAY_NAME + " [-32768]\n" +
                 "                    SERVER FILTER BY NAME != 'T3'\n" +

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java
index 685daeb..8109694 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/PercentileIT.java
@@ -50,6 +50,7 @@ import java.sql.Types;
 import java.util.Properties;
 
 import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.PropertiesUtil;
 import org.junit.Test;
@@ -516,15 +517,17 @@ public class PercentileIT extends BaseClientManagedTimeIT {
     private static void populateINDEX_DATA_TABLETable() throws SQLException {
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
         Connection conn = DriverManager.getConnection(getUrl(), props);
+        Date date = DateUtil.parseDate("2015-01-01 00:00:00");
         try {
             String upsert = "UPSERT INTO " + INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + INDEX_DATA_TABLE
-                    + " VALUES(?, ?, ?, ?, ?)";
+                    + " VALUES(?, ?, ?, ?, ?, ?)";
             PreparedStatement stmt = conn.prepareStatement(upsert);
             stmt.setString(1, "varchar1");
             stmt.setString(2, "char1");
             stmt.setInt(3, 1);
             stmt.setLong(4, 1L);
             stmt.setBigDecimal(5, new BigDecimal(1.0));
+            stmt.setDate(6, date);
             stmt.executeUpdate();
             
             stmt.setString(1, "varchar2");
@@ -532,6 +535,7 @@ public class PercentileIT extends BaseClientManagedTimeIT {
             stmt.setInt(3, 2);
             stmt.setLong(4, 2L);
             stmt.setBigDecimal(5, new BigDecimal(2.0));
+            stmt.setDate(6, date);
             stmt.executeUpdate();
             
             stmt.setString(1, "varchar3");
@@ -539,6 +543,7 @@ public class PercentileIT extends BaseClientManagedTimeIT {
             stmt.setInt(3, 3);
             stmt.setLong(4, 3L);
             stmt.setBigDecimal(5, new BigDecimal(3.0));
+            stmt.setDate(6, date);
             stmt.executeUpdate();
             
             conn.commit();

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
index aa26f9b..9a89531 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
@@ -424,6 +424,6 @@ public class ViewIT extends BaseViewIT {
         String queryPlan = QueryUtil.getExplainPlan(rs);
         assertEquals(
                 "CLIENT PARALLEL 1-WAY SKIP SCAN ON 4 KEYS OVER I1 [1,100] - [2,109]\n" + 
-                "    SERVER FILTER BY (S2 = 'bas' AND S1 = 'foo')", queryPlan);
+                "    SERVER FILTER BY (S2 = 'bas' AND \"S1\" = 'foo')", queryPlan);
     }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ImmutableIndexIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ImmutableIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ImmutableIndexIT.java
index 55b38a5..9eb9a57 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ImmutableIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ImmutableIndexIT.java
@@ -27,6 +27,7 @@ import static org.junit.Assert.fail;
 
 import java.math.BigDecimal;
 import java.sql.Connection;
+import java.sql.Date;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
@@ -38,6 +39,7 @@ import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.schema.PTableKey;
+import org.apache.phoenix.util.DateUtil;
 import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.QueryUtil;
 import org.junit.Test;
@@ -50,23 +52,27 @@ public class ImmutableIndexIT extends BaseHBaseManagedTimeIT {
         Connection conn = DriverManager.getConnection(getUrl(), props);
         try {
             String upsert = "UPSERT INTO " + INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + INDEX_DATA_TABLE
-                    + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+                    + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
             PreparedStatement stmt = conn.prepareStatement(upsert);
             stmt.setString(1, "varchar1");
             stmt.setString(2, "char1");
             stmt.setInt(3, 1);
             stmt.setLong(4, 1L);
             stmt.setBigDecimal(5, new BigDecimal(1.0));
-            stmt.setString(6, "varchar_a");
-            stmt.setString(7, "chara");
-            stmt.setInt(8, 2);
-            stmt.setLong(9, 2L);
-            stmt.setBigDecimal(10, new BigDecimal(2.0));
-            stmt.setString(11, "varchar_b");
-            stmt.setString(12, "charb");
-            stmt.setInt(13, 3);
-            stmt.setLong(14, 3L);
-            stmt.setBigDecimal(15, new BigDecimal(3.0));
+            Date date = DateUtil.parseDate("2015-01-01 00:00:00");
+            stmt.setDate(6, date);
+            stmt.setString(7, "varchar_a");
+            stmt.setString(8, "chara");
+            stmt.setInt(9, 2);
+            stmt.setLong(10, 2L);
+            stmt.setBigDecimal(11, new BigDecimal(2.0));
+            stmt.setDate(12, date);
+            stmt.setString(13, "varchar_b");
+            stmt.setString(14, "charb");
+            stmt.setInt(15, 3);
+            stmt.setLong(16, 3L);
+            stmt.setBigDecimal(17, new BigDecimal(3.0));
+            stmt.setDate(18, date);
             stmt.executeUpdate();
             
             stmt.setString(1, "varchar2");
@@ -74,16 +80,20 @@ public class ImmutableIndexIT extends BaseHBaseManagedTimeIT {
             stmt.setInt(3, 2);
             stmt.setLong(4, 2L);
             stmt.setBigDecimal(5, new BigDecimal(2.0));
-            stmt.setString(6, "varchar_a");
-            stmt.setString(7, "chara");
-            stmt.setInt(8, 3);
-            stmt.setLong(9, 3L);
-            stmt.setBigDecimal(10, new BigDecimal(3.0));
-            stmt.setString(11, "varchar_b");
-            stmt.setString(12, "charb");
-            stmt.setInt(13, 4);
-            stmt.setLong(14, 4L);
-            stmt.setBigDecimal(15, new BigDecimal(4.0));
+            date = DateUtil.parseDate("2015-01-02 00:00:00");
+            stmt.setDate(6, date);
+            stmt.setString(7, "varchar_a");
+            stmt.setString(8, "chara");
+            stmt.setInt(9, 3);
+            stmt.setLong(10, 3L);
+            stmt.setBigDecimal(11, new BigDecimal(3.0));
+            stmt.setDate(12, date);
+            stmt.setString(13, "varchar_b");
+            stmt.setString(14, "charb");
+            stmt.setInt(15, 4);
+            stmt.setLong(16, 4L);
+            stmt.setBigDecimal(17, new BigDecimal(4.0));
+            stmt.setDate(18, date);
             stmt.executeUpdate();
             
             stmt.setString(1, "varchar3");
@@ -91,16 +101,20 @@ public class ImmutableIndexIT extends BaseHBaseManagedTimeIT {
             stmt.setInt(3, 3);
             stmt.setLong(4, 3L);
             stmt.setBigDecimal(5, new BigDecimal(3.0));
-            stmt.setString(6, "varchar_a");
-            stmt.setString(7, "chara");
-            stmt.setInt(8, 4);
-            stmt.setLong(9, 4L);
-            stmt.setBigDecimal(10, new BigDecimal(4.0));
-            stmt.setString(11, "varchar_b");
-            stmt.setString(12, "charb");
-            stmt.setInt(13, 5);
-            stmt.setLong(14, 5L);
-            stmt.setBigDecimal(15, new BigDecimal(5.0));
+            date = DateUtil.parseDate("2015-01-03 00:00:00");
+            stmt.setDate(6, date);
+            stmt.setString(7, "varchar_a");
+            stmt.setString(8, "chara");
+            stmt.setInt(9, 4);
+            stmt.setLong(10, 4L);
+            stmt.setBigDecimal(11, new BigDecimal(4.0));
+            stmt.setDate(12, date);
+            stmt.setString(13, "varchar_b");
+            stmt.setString(14, "charb");
+            stmt.setInt(15, 5);
+            stmt.setLong(16, 5L);
+            stmt.setBigDecimal(17, new BigDecimal(5.0));
+            stmt.setDate(18, date);
             stmt.executeUpdate();
             
             conn.commit();

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java
new file mode 100644
index 0000000..28124b6
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexExpressionIT.java
@@ -0,0 +1,866 @@
+/*
+ * 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.phoenix.end2end.index;
+
+import static org.apache.phoenix.util.TestUtil.INDEX_DATA_SCHEMA;
+import static org.apache.phoenix.util.TestUtil.INDEX_DATA_TABLE;
+import static org.apache.phoenix.util.TestUtil.MUTABLE_INDEX_DATA_TABLE;
+import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.math.BigDecimal;
+import java.sql.Connection;
+import java.sql.Date;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Properties;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.phoenix.end2end.BaseHBaseManagedTimeIT;
+import org.apache.phoenix.exception.SQLExceptionCode;
+import org.apache.phoenix.query.QueryConstants;
+import org.apache.phoenix.util.DateUtil;
+import org.apache.phoenix.util.PropertiesUtil;
+import org.apache.phoenix.util.QueryUtil;
+import org.junit.Test;
+
+public class IndexExpressionIT extends BaseHBaseManagedTimeIT {
+
+    private static final int NUM_MILLIS_IN_DAY = 86400000;
+
+    @Test
+    public void testImmutableIndexCreationAndUpdate() throws Exception {
+        helpTestCreateAndUpdate(false, false);
+    }
+
+    @Test
+    public void testImmutableLocalIndexCreationAndUpdate() throws Exception {
+        helpTestCreateAndUpdate(false, true);
+    }
+
+    @Test
+    public void testMutableIndexCreationAndUpdate() throws Exception {
+        helpTestCreateAndUpdate(true, false);
+    }
+
+    @Test
+    public void testMutableLocalIndexCreationAndUpdate() throws Exception {
+        helpTestCreateAndUpdate(true, true);
+    }
+
+    /**
+     * Adds a row to the index data table
+     * 
+     * @param i
+     *            row number
+     */
+    private void insertRow(PreparedStatement stmt, int i) throws SQLException {
+        // insert row
+        stmt.setString(1, "varchar" + String.valueOf(i));
+        stmt.setString(2, "char" + String.valueOf(i));
+        stmt.setInt(3, i);
+        stmt.setLong(4, i);
+        stmt.setBigDecimal(5, new BigDecimal(Double.valueOf(i)));
+        Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i - 1) * NUM_MILLIS_IN_DAY);
+        stmt.setDate(6, date);
+        stmt.setString(7, "a.varchar" + String.valueOf(i));
+        stmt.setString(8, "a.char" + String.valueOf(i));
+        stmt.setInt(9, i);
+        stmt.setLong(10, i);
+        stmt.setBigDecimal(11, new BigDecimal((double)i));
+        stmt.setDate(12, date);
+        stmt.setString(13, "b.varchar" + String.valueOf(i));
+        stmt.setString(14, "b.char" + String.valueOf(i));
+        stmt.setInt(15, i);
+        stmt.setLong(16, i);
+        stmt.setBigDecimal(17, new BigDecimal((double)i));
+        stmt.setDate(18, date);
+        stmt.executeUpdate();
+    }
+
+    private void verifyResult(ResultSet rs, int i) throws SQLException {
+        assertTrue(rs.next());
+        assertEquals("VARCHAR" + String.valueOf(i) + "_" + StringUtils.rightPad("CHAR" + String.valueOf(i), 6, ' ')
+                + "_A.VARCHAR" + String.valueOf(i) + "_" + StringUtils.rightPad("B.CHAR" + String.valueOf(i), 10, ' '),
+                rs.getString(1));
+        assertEquals(i * 4, rs.getInt(2));
+        Date date = new Date(DateUtil.parseDate("2015-01-01 00:00:00").getTime() + (i) * NUM_MILLIS_IN_DAY);
+        assertEquals(date, rs.getDate(3));
+        assertEquals(date, rs.getDate(4));
+        assertEquals(date, rs.getDate(5));
+        assertEquals("varchar" + String.valueOf(i), rs.getString(6));
+        assertEquals("char" + String.valueOf(i), rs.getString(7));
+        assertEquals(i, rs.getInt(8));
+        assertEquals(i, rs.getLong(9));
+        assertEquals(i, rs.getDouble(10), 0.000001);
+        assertEquals(i, rs.getLong(11));
+        assertEquals(i, rs.getLong(12));
+    }
+
+    protected void helpTestCreateAndUpdate(boolean mutable, boolean localIndex) throws Exception {
+        String dataTableName = mutable ? MUTABLE_INDEX_DATA_TABLE : INDEX_DATA_TABLE;
+        String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName;
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.setAutoCommit(false);
+            populateDataTable(conn, dataTableName);
+
+            // create an expression index
+            String ddl = "CREATE "
+                    + (localIndex ? "LOCAL" : "")
+                    + " INDEX IDX ON "
+                    + fullDataTableName
+                    + " ((UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(char_col2)),"
+                    + " (decimal_pk+int_pk+decimal_col2+int_col1)," + " date_pk+1, date1+1, date2+1 )"
+                    + " INCLUDE (long_col1, long_col2)";
+            PreparedStatement stmt = conn.prepareStatement(ddl);
+            stmt.execute();
+
+            // run select query with expression in WHERE clause
+            String whereSql = "SELECT long_col1, long_col2 from "
+                    + fullDataTableName
+                    + " WHERE UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(char_col2) = ?"
+                    + " AND decimal_pk+int_pk+decimal_col2+int_col1=?"
+                    // since a.date1 and b.date2 are NULLABLE and date is fixed width, these expressions are stored as
+                    // DECIMAL in the index (which is not fixed width)
+                    + " AND date_pk+1=? AND date1+1=? AND date2+1=?";
+            stmt = conn.prepareStatement(whereSql);
+            stmt.setString(1, "VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1   ");
+            stmt.setInt(2, 4);
+            Date date = DateUtil.parseDate("2015-01-02 00:00:00");
+            stmt.setDate(3, date);
+            stmt.setDate(4, date);
+            stmt.setDate(5, date);
+
+            // verify that the query does a range scan on the index table
+            ResultSet rs = stmt.executeQuery("EXPLAIN " + whereSql);
+            assertEquals(
+                    localIndex ? "CLIENT PARALLEL 1-WAY RANGE SCAN OVER _LOCAL_IDX_INDEX_TEST."
+                            + dataTableName
+                            + " [-32768,'VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1   ',4,'2015-01-02 00:00:00.000',1,420,156,800,000,1,420,156,800,000]\nCLIENT MERGE SORT"
+                            : "CLIENT PARALLEL 1-WAY RANGE SCAN OVER INDEX_TEST.IDX ['VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1   ',4,'2015-01-02 00:00:00.000',1,420,156,800,000,1,420,156,800,000]",
+                    QueryUtil.getExplainPlan(rs));
+
+            // verify that the correct results are returned
+            rs = stmt.executeQuery();
+            assertTrue(rs.next());
+            assertEquals(1, rs.getInt(1));
+            assertEquals(1, rs.getInt(2));
+            assertFalse(rs.next());
+
+            // verify all rows in data table are present in index table
+            String indexSelectSql = "SELECT UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(char_col2), "
+                    + "decimal_pk+int_pk+decimal_col2+int_col1, "
+                    + "date_pk+1, date1+1, date2+1, "
+                    + "varchar_pk, char_pk, int_pk, long_pk, decimal_pk, "
+                    + "long_col1, long_col2 "
+                    + "from "
+                    + fullDataTableName;
+            rs = conn.createStatement().executeQuery("EXPLAIN " + indexSelectSql);
+            assertEquals(localIndex ? "CLIENT PARALLEL 1-WAY RANGE SCAN OVER _LOCAL_IDX_" + fullDataTableName
+                    + " [-32768]\nCLIENT MERGE SORT" : "CLIENT PARALLEL 1-WAY FULL SCAN OVER INDEX_TEST.IDX",
+                    QueryUtil.getExplainPlan(rs));
+            rs = conn.createStatement().executeQuery(indexSelectSql);
+            verifyResult(rs, 1);
+            verifyResult(rs, 2);
+
+            // Insert two more rows to the index data table
+            String upsert = "UPSERT INTO " + fullDataTableName
+                    + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+            stmt = conn.prepareStatement(upsert);
+            insertRow(stmt, 3);
+            insertRow(stmt, 4);
+            conn.commit();
+
+            rs = conn.createStatement().executeQuery(indexSelectSql);
+            verifyResult(rs, 1);
+            verifyResult(rs, 2);
+            // verify that two rows added after index was created were also added to
+            // the index table
+            verifyResult(rs, 3);
+            verifyResult(rs, 4);
+
+            // update the first row
+            upsert = "UPSERT INTO "
+                    + fullDataTableName
+                    + "(varchar_pk, char_pk, int_pk, long_pk, decimal_pk, date_pk, a.varchar_col1) VALUES(?, ?, ?, ?, ?, ?, ?)";
+
+            stmt = conn.prepareStatement(upsert);
+            stmt.setString(1, "varchar1");
+            stmt.setString(2, "char1");
+            stmt.setInt(3, 1);
+            stmt.setLong(4, 1l);
+            stmt.setBigDecimal(5, new BigDecimal(1.0));
+            stmt.setDate(6, DateUtil.parseDate("2015-01-01 00:00:00"));
+            stmt.setString(7, "a.varchar_updated");
+            stmt.executeUpdate();
+            conn.commit();
+
+            // verify only one row was updated in the data table
+            String selectSql = "UPPER(varchar_pk) || '_' || UPPER(char_pk) || '_' || UPPER(varchar_col1) || '_' || UPPER(char_col2) from "
+                    + fullDataTableName;
+            rs = conn.createStatement().executeQuery("SELECT /*+ NO_INDEX */ " + selectSql);
+            assertTrue(rs.next());
+            assertEquals("VARCHAR1_CHAR1 _A.VARCHAR_UPDATED_B.CHAR1   ", rs.getString(1));
+            assertTrue(rs.next());
+            assertEquals("VARCHAR2_CHAR2 _A.VARCHAR2_B.CHAR2   ", rs.getString(1));
+            assertTrue(rs.next());
+            assertEquals("VARCHAR3_CHAR3 _A.VARCHAR3_B.CHAR3   ", rs.getString(1));
+            assertTrue(rs.next());
+            assertEquals("VARCHAR4_CHAR4 _A.VARCHAR4_B.CHAR4   ", rs.getString(1));
+            assertFalse(rs.next());
+
+            // verify that the rows in the index table are also updated
+            rs = conn.createStatement().executeQuery("SELECT " + selectSql);
+            assertTrue(rs.next());
+            // if the data table is immutable, the index table will have one more
+            // row
+            if (!mutable) {
+                assertEquals("VARCHAR1_CHAR1 _A.VARCHAR1_B.CHAR1   ", rs.getString(1));
+                assertTrue(rs.next());
+            }
+            assertEquals("VARCHAR1_CHAR1 _A.VARCHAR_UPDATED_" + (mutable ? "B.CHAR1   " : ""), rs.getString(1));
+            assertTrue(rs.next());
+            assertEquals("VARCHAR2_CHAR2 _A.VARCHAR2_B.CHAR2   ", rs.getString(1));
+            assertTrue(rs.next());
+            assertEquals("VARCHAR3_CHAR3 _A.VARCHAR3_B.CHAR3   ", rs.getString(1));
+            assertTrue(rs.next());
+            assertEquals("VARCHAR4_CHAR4 _A.VARCHAR4_B.CHAR4   ", rs.getString(1));
+            assertFalse(rs.next());
+            conn.createStatement().execute("DROP INDEX IDX ON " + fullDataTableName);
+        } finally {
+            conn.close();
+        }
+    }
+
+    private void populateDataTable(Connection conn, String dataTable) throws SQLException {
+        ensureTableCreated(getUrl(), dataTable);
+        String upsert = "UPSERT INTO " + INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTable
+                + " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
+        PreparedStatement stmt1 = conn.prepareStatement(upsert);
+        // insert two rows
+        insertRow(stmt1, 1);
+        insertRow(stmt1, 2);
+        conn.commit();
+    }
+
+    @Test
+    public void testDeleteIndexedExpressionImmutableIndex() throws Exception {
+        helpTestDeleteIndexedExpression(false, false);
+    }
+
+    @Test
+    public void testDeleteIndexedExpressionImmutableLocalIndex() throws Exception {
+        helpTestDeleteIndexedExpression(false, true);
+    }
+
+    @Test
+    public void testDeleteIndexedExpressionMutableIndex() throws Exception {
+        helpTestDeleteIndexedExpression(true, false);
+    }
+
+    @Test
+    public void testDeleteIndexedExpressionMutableLocalIndex() throws Exception {
+        helpTestDeleteIndexedExpression(true, true);
+    }
+
+    protected void helpTestDeleteIndexedExpression(boolean mutable, boolean localIndex) throws Exception {
+        String dataTableName = mutable ? MUTABLE_INDEX_DATA_TABLE : INDEX_DATA_TABLE;
+        String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName;
+        String fullIndexTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + "IDX";
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.setAutoCommit(false);
+            ensureTableCreated(getUrl(), dataTableName);
+            populateDataTable(conn, dataTableName);
+            String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX IDX ON " + fullDataTableName
+                    + " (2*long_col2)";
+            PreparedStatement stmt = conn.prepareStatement(ddl);
+            stmt.execute();
+
+            ResultSet rs;
+            rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullDataTableName);
+            assertTrue(rs.next());
+            assertEquals(2, rs.getInt(1));
+            rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullIndexTableName);
+            assertTrue(rs.next());
+            assertEquals(2, rs.getInt(1));
+
+            conn.setAutoCommit(true);
+            String dml = "DELETE from " + fullDataTableName + " WHERE long_col2 = 2";
+            try {
+                conn.createStatement().execute(dml);
+                if (!mutable) {
+                    fail();
+                }
+            } catch (SQLException e) {
+                if (!mutable) {
+                    assertEquals(SQLExceptionCode.INVALID_FILTER_ON_IMMUTABLE_ROWS.getErrorCode(), e.getErrorCode());
+                }
+            }
+
+            if (!mutable) {
+                dml = "DELETE from " + fullDataTableName + " WHERE 2*long_col2 = 4";
+                conn.createStatement().execute(dml);
+            }
+
+            rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullDataTableName);
+            assertTrue(rs.next());
+            assertEquals(1, rs.getInt(1));
+            rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullIndexTableName);
+            assertTrue(rs.next());
+            assertEquals(1, rs.getInt(1));
+            conn.createStatement().execute("DROP INDEX IDX ON " + fullDataTableName);
+        } finally {
+            conn.close();
+        }
+    }
+
+    @Test
+    public void testDeleteCoveredColImmutableIndex() throws Exception {
+        helpTestDeleteCoveredCol(false, false);
+    }
+
+    @Test
+    public void testDeleteCoveredColImmutableLocalIndex() throws Exception {
+        helpTestDeleteCoveredCol(false, true);
+    }
+
+    @Test
+    public void testDeleteCoveredColMutableIndex() throws Exception {
+        helpTestDeleteCoveredCol(true, false);
+    }
+
+    @Test
+    public void testDeleteCoveredColMutableLocalIndex() throws Exception {
+        helpTestDeleteCoveredCol(true, true);
+    }
+
+    protected void helpTestDeleteCoveredCol(boolean mutable, boolean localIndex) throws Exception {
+        String dataTableName = mutable ? MUTABLE_INDEX_DATA_TABLE : INDEX_DATA_TABLE;
+        String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName;
+        String fullIndexTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + "IDX";
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.setAutoCommit(false);
+            ensureTableCreated(getUrl(), dataTableName);
+            populateDataTable(conn, dataTableName);
+            String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX IDX ON " + fullDataTableName
+                    + " (long_pk, varchar_pk, 1+long_pk, UPPER(varchar_pk) )" + " INCLUDE (long_col1, long_col2)";
+            PreparedStatement stmt = conn.prepareStatement(ddl);
+            stmt.execute();
+
+            ResultSet rs;
+            rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullDataTableName);
+            assertTrue(rs.next());
+            assertEquals(2, rs.getInt(1));
+            rs = conn.createStatement().executeQuery("SELECT COUNT(*) FROM " + fullIndexTableName);
+            assertTrue(rs.next());
+            assertEquals(2, rs.getInt(1));
+
+            String dml = "DELETE from " + fullDataTableName + " WHERE long_col2 = 2";
+            assertEquals(1, conn.createStatement().executeUpdate(dml));
+            conn.commit();
+
+            String query = "SELECT /*+ NO_INDEX */ long_pk, varchar_pk, 1+long_pk, UPPER(varchar_pk) FROM "
+                    + fullDataTableName;
+            rs = conn.createStatement().executeQuery(query);
+            assertTrue(rs.next());
+            assertEquals(1L, rs.getLong(1));
+            assertEquals("varchar1", rs.getString(2));
+            assertEquals(2L, rs.getLong(3));
+            assertEquals("VARCHAR1", rs.getString(4));
+            assertFalse(rs.next());
+
+            query = "SELECT long_pk, varchar_pk, 1+long_pk, UPPER(varchar_pk) FROM " + fullDataTableName;
+            rs = conn.createStatement().executeQuery(query);
+            assertTrue(rs.next());
+            assertEquals(1L, rs.getLong(1));
+            assertEquals("varchar1", rs.getString(2));
+            assertEquals(2L, rs.getLong(3));
+            assertEquals("VARCHAR1", rs.getString(4));
+            assertFalse(rs.next());
+
+            query = "SELECT * FROM " + fullIndexTableName;
+            rs = conn.createStatement().executeQuery(query);
+            assertTrue(rs.next());
+
+            assertEquals(1L, rs.getLong(1));
+            assertEquals("varchar1", rs.getString(2));
+            assertEquals(2L, rs.getLong(3));
+            assertEquals("VARCHAR1", rs.getString(4));
+            assertFalse(rs.next());
+            conn.createStatement().execute("DROP INDEX IDX ON " + fullDataTableName);
+        } finally {
+            conn.close();
+        }
+    }
+
+    @Test
+    public void testGroupByCountImmutableIndex() throws Exception {
+        helpTestGroupByCount(false, false);
+    }
+
+    @Test
+    public void testGroupByCountImmutableLocalIndex() throws Exception {
+        helpTestGroupByCount(false, true);
+    }
+
+    @Test
+    public void testGroupByCountMutableIndex() throws Exception {
+        helpTestGroupByCount(true, false);
+    }
+
+    @Test
+    public void testGroupByCountMutableLocalIndex() throws Exception {
+        helpTestGroupByCount(true, true);
+    }
+
+    protected void helpTestGroupByCount(boolean mutable, boolean localIndex) throws Exception {
+        String dataTableName = mutable ? MUTABLE_INDEX_DATA_TABLE : INDEX_DATA_TABLE;
+        String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName;
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.setAutoCommit(false);
+            populateDataTable(conn, dataTableName);
+            String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX IDX ON " + fullDataTableName
+                    + " (int_col1+int_col2)";
+            PreparedStatement stmt = conn.prepareStatement(ddl);
+            stmt.execute();
+
+            String groupBySql = "SELECT (int_col1+int_col2), COUNT(*) FROM " + fullDataTableName
+                    + " GROUP BY (int_col1+int_col2)";
+            ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + groupBySql);
+            String expectedPlan = "CLIENT PARALLEL 1-WAY "
+                    + (localIndex ? "RANGE SCAN OVER _LOCAL_IDX_" + fullDataTableName + " [-32768]"
+                            : "FULL SCAN OVER INDEX_TEST.IDX")
+                    + "\n    SERVER FILTER BY FIRST KEY ONLY\n    SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [TO_BIGINT((A.INT_COL1 + B.INT_COL2))]\nCLIENT MERGE SORT";
+            assertEquals(expectedPlan, QueryUtil.getExplainPlan(rs));
+            rs = conn.createStatement().executeQuery(groupBySql);
+            assertTrue(rs.next());
+            assertEquals(1, rs.getInt(2));
+            assertTrue(rs.next());
+            assertEquals(1, rs.getInt(2));
+            assertFalse(rs.next());
+            conn.createStatement().execute("DROP INDEX IDX ON " + fullDataTableName);
+        } finally {
+            conn.close();
+        }
+    }
+
+    @Test
+    public void testSelectDistinctImmutableIndex() throws Exception {
+        helpTestSelectDistinct(false, false);
+    }
+
+    @Test
+    public void testSelectDistinctImmutableIndexLocal() throws Exception {
+        helpTestSelectDistinct(false, true);
+    }
+
+    @Test
+    public void testSelectDistinctMutableIndex() throws Exception {
+        helpTestSelectDistinct(true, false);
+    }
+
+    @Test
+    public void testSelectDistinctMutableLocalIndex() throws Exception {
+        helpTestSelectDistinct(true, true);
+    }
+
+    protected void helpTestSelectDistinct(boolean mutable, boolean localIndex) throws Exception {
+        String dataTableName = mutable ? MUTABLE_INDEX_DATA_TABLE : INDEX_DATA_TABLE;
+        String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName;
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.setAutoCommit(false);
+            populateDataTable(conn, dataTableName);
+            String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX IDX ON " + fullDataTableName
+                    + " (int_col1+1)";
+            PreparedStatement stmt = conn.prepareStatement(ddl);
+            stmt.execute();
+            String sql = "SELECT distinct int_col1+1 FROM " + fullDataTableName + " where int_col1+1 > 0";
+            ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql);
+            String expectedPlan = "CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
+                    + (localIndex ? "_LOCAL_IDX_" + fullDataTableName + " [-32768,0] - [-32768,*]"
+                            : "INDEX_TEST.IDX [0] - [*]")
+                    + "\n    SERVER FILTER BY FIRST KEY ONLY\n    SERVER AGGREGATE INTO ORDERED DISTINCT ROWS BY [TO_BIGINT((A.INT_COL1 + 1))]\nCLIENT MERGE SORT";
+            assertEquals(expectedPlan, QueryUtil.getExplainPlan(rs));
+            rs = conn.createStatement().executeQuery(sql);
+            assertTrue(rs.next());
+            assertEquals(2, rs.getInt(1));
+            assertTrue(rs.next());
+            assertEquals(3, rs.getInt(1));
+            assertFalse(rs.next());
+            conn.createStatement().execute("DROP INDEX IDX ON " + fullDataTableName);
+        } finally {
+            conn.close();
+        }
+    }
+
+    @Test
+    public void testInClauseWithImmutableIndex() throws Exception {
+        helpTestInClauseWithIndex(false, false);
+    }
+
+    @Test
+    public void testInClauseWithImmutableLocalIndex() throws Exception {
+        helpTestInClauseWithIndex(false, true);
+    }
+
+    @Test
+    public void testInClauseWithMutableIndex() throws Exception {
+        helpTestInClauseWithIndex(true, false);
+    }
+
+    @Test
+    public void testInClauseWithMutableLocalIndex() throws Exception {
+        helpTestInClauseWithIndex(true, false);
+    }
+
+    protected void helpTestInClauseWithIndex(boolean mutable, boolean localIndex) throws Exception {
+        String dataTableName = mutable ? MUTABLE_INDEX_DATA_TABLE : INDEX_DATA_TABLE;
+        String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName;
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.setAutoCommit(false);
+            populateDataTable(conn, dataTableName);
+            String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX IDX ON " + fullDataTableName
+                    + " (int_col1+1)";
+
+            conn = DriverManager.getConnection(getUrl(), props);
+            conn.setAutoCommit(false);
+            PreparedStatement stmt = conn.prepareStatement(ddl);
+            stmt.execute();
+            String sql = "SELECT int_col1+1 FROM " + fullDataTableName + " where int_col1+1 IN (2)";
+            ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql);
+            assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER "
+                    + (localIndex ? "_LOCAL_IDX_" + fullDataTableName + " [-32768,2]\n    SERVER FILTER BY FIRST KEY ONLY\nCLIENT MERGE SORT"
+                            : "INDEX_TEST.IDX [2]\n    SERVER FILTER BY FIRST KEY ONLY"), QueryUtil.getExplainPlan(rs));
+            rs = conn.createStatement().executeQuery(sql);
+            assertTrue(rs.next());
+            assertEquals(2, rs.getInt(1));
+            assertFalse(rs.next());
+            conn.createStatement().execute("DROP INDEX IDX ON " + fullDataTableName);
+        } finally {
+            conn.close();
+        }
+    }
+
+    @Test
+    public void testOrderByWithImmutableIndex() throws Exception {
+        helpTestOrderByWithIndex(false, false);
+    }
+
+    @Test
+    public void testOrderByWithImmutableLocalIndex() throws Exception {
+        helpTestOrderByWithIndex(false, true);
+    }
+
+    @Test
+    public void testOrderByWithMutableIndex() throws Exception {
+        helpTestOrderByWithIndex(true, false);
+    }
+
+    @Test
+    public void testOrderByWithMutableLocalIndex() throws Exception {
+        helpTestOrderByWithIndex(true, false);
+    }
+
+    protected void helpTestOrderByWithIndex(boolean mutable, boolean localIndex) throws Exception {
+        String dataTableName = mutable ? MUTABLE_INDEX_DATA_TABLE : INDEX_DATA_TABLE;
+        String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName;
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.setAutoCommit(false);
+            populateDataTable(conn, dataTableName);
+            String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX IDX ON " + fullDataTableName
+                    + " (int_col1+1)";
+
+            conn = DriverManager.getConnection(getUrl(), props);
+            conn.setAutoCommit(false);
+            PreparedStatement stmt = conn.prepareStatement(ddl);
+            stmt.execute();
+            String sql = "SELECT int_col1+1 FROM " + fullDataTableName + " ORDER BY int_col1+1";
+            ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql);
+            assertEquals("CLIENT PARALLEL 1-WAY "
+                    + (localIndex ? "RANGE SCAN OVER _LOCAL_IDX_" + fullDataTableName
+                            + " [-32768]\n    SERVER FILTER BY FIRST KEY ONLY\nCLIENT MERGE SORT"
+                            : "FULL SCAN OVER INDEX_TEST.IDX\n    SERVER FILTER BY FIRST KEY ONLY"),
+                    QueryUtil.getExplainPlan(rs));
+            rs = conn.createStatement().executeQuery(sql);
+            assertTrue(rs.next());
+            assertEquals(2, rs.getInt(1));
+            assertTrue(rs.next());
+            assertEquals(3, rs.getInt(1));
+            assertFalse(rs.next());
+            conn.createStatement().execute("DROP INDEX IDX ON " + fullDataTableName);
+        } finally {
+            conn.close();
+        }
+    }
+
+    @Test
+    public void testSelectColOnlyInDataTableImmutableIndex() throws Exception {
+        helpTestSelectColOnlyInDataTable(false, false);
+    }
+
+    @Test
+    public void testSelectColOnlyInDataTableImmutableLocalIndex() throws Exception {
+        helpTestSelectColOnlyInDataTable(false, true);
+    }
+
+    @Test
+    public void testSelectColOnlyInDataTableMutableIndex() throws Exception {
+        helpTestSelectColOnlyInDataTable(true, false);
+    }
+
+    @Test
+    public void testSelectColOnlyInDataTableMutableLocalIndex() throws Exception {
+        helpTestSelectColOnlyInDataTable(true, false);
+    }
+
+    protected void helpTestSelectColOnlyInDataTable(boolean mutable, boolean localIndex) throws Exception {
+        String dataTableName = mutable ? MUTABLE_INDEX_DATA_TABLE : INDEX_DATA_TABLE;
+        String fullDataTableName = INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + dataTableName;
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.setAutoCommit(false);
+            populateDataTable(conn, dataTableName);
+            String ddl = "CREATE " + (localIndex ? "LOCAL" : "") + " INDEX IDX ON " + fullDataTableName
+                    + " (int_col1+1)";
+
+            conn = DriverManager.getConnection(getUrl(), props);
+            conn.setAutoCommit(false);
+            PreparedStatement stmt = conn.prepareStatement(ddl);
+            stmt.execute();
+            String sql = "SELECT int_col1+1, int_col2 FROM " + fullDataTableName + " WHERE int_col1+1=2";
+            ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + sql);
+            assertEquals("CLIENT PARALLEL 1-WAY "
+                    + (localIndex ? "RANGE SCAN OVER _LOCAL_IDX_" + fullDataTableName
+                            + " [-32768,2]\n    SERVER FILTER BY FIRST KEY ONLY\nCLIENT MERGE SORT" : "FULL SCAN OVER "
+                            + fullDataTableName + "\n    SERVER FILTER BY (A.INT_COL1 + 1) = 2"),
+                    QueryUtil.getExplainPlan(rs));
+            rs = conn.createStatement().executeQuery(sql);
+            assertTrue(rs.next());
+            assertEquals(2, rs.getInt(1));
+            assertEquals(1, rs.getInt(2));
+            assertFalse(rs.next());
+            conn.createStatement().execute("DROP INDEX IDX ON " + fullDataTableName);
+        } finally {
+            conn.close();
+        }
+    }
+    
+    @Test
+    public void testImmutableIndexWithCaseSensitiveCols() throws Exception {
+        helpTestIndexWithCaseSensitiveCols(false, false);
+    }
+    
+    @Test
+    public void testImmutableLocalIndexWithCaseSensitiveCols() throws Exception {
+        helpTestIndexWithCaseSensitiveCols(true, false);
+    }
+    
+    @Test
+    public void testMutableIndexWithCaseSensitiveCols() throws Exception {
+        helpTestIndexWithCaseSensitiveCols(true, false);
+    }
+    
+    @Test
+    public void testMutableLocalIndexWithCaseSensitiveCols() throws Exception {
+        helpTestIndexWithCaseSensitiveCols(true, false);
+    }
+    
+    protected void helpTestIndexWithCaseSensitiveCols(boolean mutable, boolean localIndex) throws Exception {
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        try {
+            conn.createStatement().execute("CREATE TABLE cs (k VARCHAR NOT NULL PRIMARY KEY, \"V1\" VARCHAR, \"v2\" VARCHAR) "+ (mutable ? "IMMUTABLE_ROWS=true" : ""));
+            String query = "SELECT * FROM cs";
+            ResultSet rs = conn.createStatement().executeQuery(query);
+            assertFalse(rs.next());
+            if (localIndex) {
+                conn.createStatement().execute("CREATE LOCAL INDEX ics ON cs (\"v2\" || '_modified') INCLUDE (\"V1\",\"v2\")");
+            } else {
+                conn.createStatement().execute("CREATE INDEX ics ON cs (\"V1\" || '_' || \"v2\") INCLUDE (\"V1\",\"v2\")");
+            }
+            query = "SELECT * FROM ics";
+            rs = conn.createStatement().executeQuery(query);
+            assertFalse(rs.next());
+
+            PreparedStatement stmt = conn.prepareStatement("UPSERT INTO cs VALUES(?,?,?)");
+            stmt.setString(1,"a");
+            stmt.setString(2, "x");
+            stmt.setString(3, "1");
+            stmt.execute();
+            stmt.setString(1,"b");
+            stmt.setString(2, "y");
+            stmt.setString(3, "2");
+            stmt.execute();
+            conn.commit();
+
+            //TODO FIX THIS change this to *
+            query = "SELECT (\"V1\" || '_' || \"v2\"), k, \"V1\", \"v2\"  FROM cs WHERE (\"V1\" || '_' || \"v2\") = 'x_1'";
+            rs = conn.createStatement().executeQuery("EXPLAIN " + query);
+            if(localIndex){
+                assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER _LOCAL_IDX_CS [-32768,'x_1']\n"
+                           + "CLIENT MERGE SORT", QueryUtil.getExplainPlan(rs));
+            } else {
+                assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER ICS ['x_1']", QueryUtil.getExplainPlan(rs));
+            }
+
+            rs = conn.createStatement().executeQuery(query);
+            assertTrue(rs.next());
+            assertEquals("x_1",rs.getString(1));
+            assertEquals("a",rs.getString(2));
+            assertEquals("x",rs.getString(3));
+            assertEquals("1",rs.getString(4));
+            //TODO figure out why this " " is needed
+            assertEquals("x_1",rs.getString("\"('V1' || '_' || 'v2')\""));
+            assertEquals("a",rs.getString("k"));
+            assertEquals("x",rs.getString("V1"));
+            assertEquals("1",rs.getString("v2"));
+            assertFalse(rs.next());
+
+            query = "SELECT \"V1\", \"V1\" as foo1, (\"V1\" || '_' || \"v2\") as foo, (\"V1\" || '_' || \"v2\") as \"Foo1\", (\"V1\" || '_' || \"v2\") FROM cs ORDER BY foo";
+            rs = conn.createStatement().executeQuery("EXPLAIN " + query);
+            if(localIndex){
+                assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER _LOCAL_IDX_CS [-32768]\nCLIENT MERGE SORT",
+                    QueryUtil.getExplainPlan(rs));
+            } else {
+                assertEquals("CLIENT PARALLEL 1-WAY FULL SCAN OVER ICS", QueryUtil.getExplainPlan(rs));
+            }
+
+            rs = conn.createStatement().executeQuery(query);
+            assertTrue(rs.next());
+            assertEquals("x",rs.getString(1));
+            assertEquals("x",rs.getString("V1"));
+            assertEquals("x",rs.getString(2));
+            assertEquals("x",rs.getString("foo1"));
+            assertEquals("x_1",rs.getString(3));
+            assertEquals("x_1",rs.getString("Foo"));
+            assertEquals("x_1",rs.getString(4));
+            assertEquals("x_1",rs.getString("Foo1"));
+            assertEquals("x_1",rs.getString(5));
+            assertEquals("x_1",rs.getString("\"('V1' || '_' || 'v2')\""));
+            assertTrue(rs.next());
+            assertEquals("y",rs.getString(1));
+            assertEquals("y",rs.getString("V1"));
+            assertEquals("y",rs.getString(2));
+            assertEquals("y",rs.getString("foo1"));
+            assertEquals("y_2",rs.getString(3));
+            assertEquals("y_2",rs.getString("Foo"));
+            assertEquals("y_2",rs.getString(4));
+            assertEquals("y_2",rs.getString("Foo1"));
+            assertEquals("y_2",rs.getString(5));
+            assertEquals("y_2",rs.getString("\"('V1' || '_' || 'v2')\""));
+            assertFalse(rs.next());
+            conn.createStatement().execute("DROP INDEX ICS ON CS");
+        } finally {
+            conn.close();
+        }
+    }
+    
+    @Test
+    public void testImmutableIndexDropIndexedColumn() throws Exception {
+        helpTestDropIndexedColumn(false, false);
+    }
+    
+    @Test
+    public void testImmutableLocalIndexDropIndexedColumn() throws Exception {
+        helpTestDropIndexedColumn(false, true);
+    }
+    
+    @Test
+    public void testMutableIndexDropIndexedColumn() throws Exception {
+        helpTestDropIndexedColumn(true, false);
+    }
+    
+    @Test
+    public void testMutableLocalIndexDropIndexedColumn() throws Exception {
+        helpTestDropIndexedColumn(true, true);
+    }
+    
+    public void helpTestDropIndexedColumn(boolean mutable, boolean local) throws Exception {
+        String query;
+        ResultSet rs;
+        PreparedStatement stmt;
+
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        Connection conn = DriverManager.getConnection(getUrl(), props);
+        conn.setAutoCommit(false);
+
+        // make sure that the tables are empty, but reachable
+        conn.createStatement().execute(
+          "CREATE TABLE t (k VARCHAR NOT NULL PRIMARY KEY, v1 VARCHAR, v2 VARCHAR)");
+        query = "SELECT * FROM t" ;
+        rs = conn.createStatement().executeQuery(query);
+        assertFalse(rs.next());
+        String indexName = "it_" + (mutable ? "m" : "im") + "_" + (local ? "l" : "h");
+        conn.createStatement().execute("CREATE " + ( local ? "LOCAL" : "") + " INDEX " + indexName + " ON t (v1 || '_' || v2)");
+
+        query = "SELECT * FROM t";
+        rs = conn.createStatement().executeQuery(query);
+        assertFalse(rs.next());
+
+        // load some data into the table
+        stmt = conn.prepareStatement("UPSERT INTO t VALUES(?,?,?)");
+        stmt.setString(1, "a");
+        stmt.setString(2, "x");
+        stmt.setString(3, "1");
+        stmt.execute();
+        conn.commit();
+
+        assertIndexExists(conn,true);
+        conn.createStatement().execute("ALTER TABLE t DROP COLUMN v1");
+        assertIndexExists(conn,false);
+
+        query = "SELECT * FROM t";
+        rs = conn.createStatement().executeQuery(query);
+        assertTrue(rs.next());
+        assertEquals("a",rs.getString(1));
+        assertEquals("1",rs.getString(2));
+        assertFalse(rs.next());
+
+        // load some data into the table
+        stmt = conn.prepareStatement("UPSERT INTO t VALUES(?,?)");
+        stmt.setString(1, "a");
+        stmt.setString(2, "2");
+        stmt.execute();
+        conn.commit();
+
+        query = "SELECT * FROM t";
+        rs = conn.createStatement().executeQuery(query);
+        assertTrue(rs.next());
+        assertEquals("a",rs.getString(1));
+        assertEquals("2",rs.getString(2));
+        assertFalse(rs.next());
+    }
+    
+    private static void assertIndexExists(Connection conn, boolean exists) throws SQLException {
+        ResultSet rs = conn.getMetaData().getIndexInfo(null, null, "T", false, false);
+        assertEquals(exists, rs.next());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMetadataIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMetadataIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMetadataIT.java
index 88e7340..d6ced3c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMetadataIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/IndexMetadataIT.java
@@ -100,7 +100,7 @@ public class IndexMetadataIT extends BaseHBaseManagedTimeIT {
         String fullTableName = SchemaUtil.getTableName(schemaName, tableName);
         conn.createStatement().executeQuery("SELECT count(*) FROM " + fullTableName).next(); // client side cache will update
         PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
-        pconn.getMetaDataCache().getTable(new PTableKey(pconn.getTenantId(), fullTableName)).getIndexMaintainers(ptr);
+        pconn.getMetaDataCache().getTable(new PTableKey(pconn.getTenantId(), fullTableName)).getIndexMaintainers(ptr, pconn);
         assertTrue(ptr.getLength() > 0);
     }
     
@@ -109,7 +109,7 @@ public class IndexMetadataIT extends BaseHBaseManagedTimeIT {
         String fullTableName = SchemaUtil.getTableName(schemaName, tableName);
         conn.createStatement().executeQuery("SELECT count(*) FROM " + fullTableName).next(); // client side cache will update
         PhoenixConnection pconn = conn.unwrap(PhoenixConnection.class);
-        pconn.getMetaDataCache().getTable(new PTableKey(pconn.getTenantId(), fullTableName)).getIndexMaintainers(ptr);
+        pconn.getMetaDataCache().getTable(new PTableKey(pconn.getTenantId(), fullTableName)).getIndexMaintainers(ptr, pconn);
         assertTrue(ptr.getLength() == 0);
     }
     
@@ -135,8 +135,9 @@ public class IndexMetadataIT extends BaseHBaseManagedTimeIT {
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX", 5, ":CHAR_PK", Order.ASC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX", 6, ":LONG_PK", Order.DESC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX", 7, ":DECIMAL_PK", Order.ASC);
-            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX", 8, "A:INT_COL1", null);
-            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX", 9, "B:INT_COL2", null);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX", 8, ":DATE_PK", Order.ASC);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX", 9, "A:INT_COL1", null);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX", 10, "B:INT_COL2", null);
             assertFalse(rs.next());
             
             rs = conn.getMetaData().getTables(null, StringUtil.escapeLike(INDEX_DATA_SCHEMA), StringUtil.escapeLike("IDX"), new String[] {PTableType.INDEX.getValue().getString() });
@@ -245,8 +246,9 @@ public class IndexMetadataIT extends BaseHBaseManagedTimeIT {
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX1", 5, ":CHAR_PK", Order.ASC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX1", 6, ":LONG_PK", Order.DESC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX1", 7, ":DECIMAL_PK", Order.ASC);
-            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX1", 8, "A:INT_COL1", null);
-            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX1", 9, "B:INT_COL2", null);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX1", 8, ":DATE_PK", Order.ASC);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX1", 9, "A:INT_COL1", null);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX1", 10, "B:INT_COL2", null);
 
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX2", 1, "A:VARCHAR_COL1", Order.ASC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX2", 2, "B:VARCHAR_COL2", Order.ASC);
@@ -255,7 +257,8 @@ public class IndexMetadataIT extends BaseHBaseManagedTimeIT {
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX2", 5, ":CHAR_PK", Order.ASC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX2", 6, ":LONG_PK", Order.DESC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX2", 7, ":DECIMAL_PK", Order.ASC);
-            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX2", 8, "B:INT_COL2", null);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX2", 8, ":DATE_PK", Order.ASC);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, MUTABLE_INDEX_DATA_TABLE, "IDX2", 9, "B:INT_COL2", null);
             assertFalse(rs.next());
             
             // Create another table in the same schema
@@ -307,7 +310,8 @@ public class IndexMetadataIT extends BaseHBaseManagedTimeIT {
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, INDEX_DATA_TABLE, "IDX", 6, ":INT_PK", Order.ASC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, INDEX_DATA_TABLE, "IDX", 7, ":LONG_PK", Order.DESC);
             assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, INDEX_DATA_TABLE, "IDX", 8, ":DECIMAL_PK", Order.ASC);
-            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, INDEX_DATA_TABLE, "IDX", 9, "A:INT_COL1", null);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, INDEX_DATA_TABLE, "IDX", 9, ":DATE_PK", Order.ASC);
+            assertIndexInfoMetadata(rs, INDEX_DATA_SCHEMA, INDEX_DATA_TABLE, "IDX", 10, "A:INT_COL1", null);
             assertFalse(rs.next());
             
             rs = IndexTestUtil.readDataTableIndexRow(conn, INDEX_DATA_SCHEMA, INDEX_DATA_TABLE, "IDX");

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/main/antlr3/PhoenixSQL.g
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/antlr3/PhoenixSQL.g b/phoenix-core/src/main/antlr3/PhoenixSQL.g
index bcf26be..fad5fb3 100644
--- a/phoenix-core/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core/src/main/antlr3/PhoenixSQL.g
@@ -393,11 +393,11 @@ create_view_node returns [CreateTableStatement ret]
 // Parse a create index statement.
 create_index_node returns [CreateIndexStatement ret]
     :   CREATE l=LOCAL? INDEX (IF NOT ex=EXISTS)? i=index_name ON t=from_table_name
-        (LPAREN pk=index_pk_constraint RPAREN)
+        (LPAREN ik=ik_constraint RPAREN)
         (INCLUDE (LPAREN icrefs=column_names RPAREN))?
         (p=fam_properties)?
         (SPLIT ON v=value_expression_list)?
-        {ret = factory.createIndex(i, factory.namedTable(null,t), pk, icrefs, v, p, ex!=null, l==null ? IndexType.getDefault() : IndexType.LOCAL, getBindCount()); }
+        {ret = factory.createIndex(i, factory.namedTable(null,t), ik, icrefs, v, p, ex!=null, l==null ? IndexType.getDefault() : IndexType.LOCAL, getBindCount()); }
     ;
 
 // Parse a create sequence statement.
@@ -436,17 +436,17 @@ col_name_with_sort_order returns [Pair<ColumnName, SortOrder> ret]
     :   f=identifier (order=ASC|order=DESC)? {$ret = Pair.newPair(factory.columnName(f), order == null ? SortOrder.getDefault() : SortOrder.fromDDLValue(order.getText()));}
 ;
 
-index_pk_constraint returns [PrimaryKeyConstraint ret]
-    :   cols = col_def_name_with_sort_order_list {$ret = factory.primaryKey(null, cols); }
-    ;
+ik_constraint returns [IndexKeyConstraint ret]
+    :   x = expression_with_sort_order_list {$ret = factory.indexKey(x); }
+;
 
-col_def_name_with_sort_order_list returns [List<Pair<ColumnName, SortOrder>> ret]
-@init{ret = new ArrayList<Pair<ColumnName, SortOrder>>(); }
-    :   p=col_def_name_with_sort_order {$ret.add(p);}  (COMMA p = col_def_name_with_sort_order {$ret.add(p);} )*
+expression_with_sort_order_list returns [List<Pair<ParseNode, SortOrder>> ret]
+@init{ret = new ArrayList<Pair<ParseNode, SortOrder>>(); }
+    :   p=expression_with_sort_order {$ret.add(p);}  (COMMA p = expression_with_sort_order {$ret.add(p);} )*
 ;
 
-col_def_name_with_sort_order returns [Pair<ColumnName, SortOrder> ret]
-    :   c=column_name (order=ASC|order=DESC)? {$ret = Pair.newPair(c, order == null ? SortOrder.getDefault() : SortOrder.fromDDLValue(order.getText()));}
+expression_with_sort_order returns [Pair<ParseNode, SortOrder> ret]
+    :   (x=expression) (order=ASC|order=DESC)? {$ret = Pair.newPair(x, order == null ? SortOrder.getDefault() : SortOrder.fromDDLValue(order.getText()));}
 ;
 
 fam_properties returns [ListMultimap<String,Pair<String,Object>> ret]
@@ -780,7 +780,9 @@ term returns [ParseNode ret]
     |   field=identifier LPAREN l=zero_or_more_expressions RPAREN wg=(WITHIN GROUP LPAREN ORDER BY l2=one_or_more_expressions (a=ASC | DESC) RPAREN)?
         {
             FunctionParseNode f = wg==null ? factory.function(field, l) : factory.function(field,l,l2,a!=null);
-            contextStack.peek().setAggregate(f.isAggregate());
+            if (!contextStack.isEmpty()) {
+            	contextStack.peek().setAggregate(f.isAggregate());
+            }
             $ret = f;
         } 
     |   field=identifier LPAREN t=ASTERISK RPAREN 
@@ -789,13 +791,17 @@ term returns [ParseNode ret]
                 throwRecognitionException(t); 
             }
             FunctionParseNode f = factory.function(field, LiteralParseNode.STAR);
-            contextStack.peek().setAggregate(f.isAggregate()); 
+            if (!contextStack.isEmpty()) {
+            	contextStack.peek().setAggregate(f.isAggregate());
+            }
             $ret = f;
         } 
     |   field=identifier LPAREN t=DISTINCT l=zero_or_more_expressions RPAREN 
         {
             FunctionParseNode f = factory.functionDistinct(field, l);
-            contextStack.peek().setAggregate(f.isAggregate());
+            if (!contextStack.isEmpty()) {
+            	contextStack.peek().setAggregate(f.isAggregate());
+            }
             $ret = f;
         }
     |   e=case_statement { $ret = e; }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/main/java/org/apache/hadoop/hbase/regionserver/IndexHalfStoreFileReaderGenerator.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/hadoop/hbase/regionserver/IndexHalfStoreFileReaderGenerator.java b/phoenix-core/src/main/java/org/apache/hadoop/hbase/regionserver/IndexHalfStoreFileReaderGenerator.java
index f213d2d..718f820 100644
--- a/phoenix-core/src/main/java/org/apache/hadoop/hbase/regionserver/IndexHalfStoreFileReaderGenerator.java
+++ b/phoenix-core/src/main/java/org/apache/hadoop/hbase/regionserver/IndexHalfStoreFileReaderGenerator.java
@@ -57,8 +57,8 @@ import org.apache.phoenix.schema.MetaDataClient;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PIndexState;
 import org.apache.phoenix.schema.PTable;
-import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.PTable.IndexType;
+import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.util.ByteUtil;
 import org.apache.phoenix.util.IndexUtil;
 import org.apache.phoenix.util.MetaDataUtil;
@@ -144,7 +144,7 @@ public class IndexHalfStoreFileReaderGenerator extends BaseRegionObserver {
                         new HashMap<ImmutableBytesWritable, IndexMaintainer>();
                 for (PTable index : indexes) {
                     if (index.getIndexType() == IndexType.LOCAL) {
-                        IndexMaintainer indexMaintainer = index.getIndexMaintainer(dataTable);
+                        IndexMaintainer indexMaintainer = index.getIndexMaintainer(dataTable, conn);
                         indexMaintainers.put(new ImmutableBytesWritable(MetaDataUtil
                                 .getViewIndexIdDataType().toBytes(index.getViewIndexId())),
                             indexMaintainer);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
index 0a2ee38..322d24a 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
@@ -65,7 +65,6 @@ import org.apache.phoenix.schema.MetaDataClient;
 import org.apache.phoenix.schema.MetaDataEntityNotFoundException;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PIndexState;
-import org.apache.phoenix.schema.types.PLong;
 import org.apache.phoenix.schema.PName;
 import org.apache.phoenix.schema.PRow;
 import org.apache.phoenix.schema.PTable;
@@ -75,6 +74,7 @@ import org.apache.phoenix.schema.ReadOnlyTableException;
 import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.schema.TableRef;
 import org.apache.phoenix.schema.tuple.Tuple;
+import org.apache.phoenix.schema.types.PLong;
 import org.apache.phoenix.util.MetaDataUtil;
 import org.apache.phoenix.util.ScanUtil;
 
@@ -483,7 +483,7 @@ public class DeleteCompiler {
                     public MutationState execute() throws SQLException {
                         // TODO: share this block of code with UPSERT SELECT
                         ImmutableBytesWritable ptr = context.getTempPtr();
-                        tableRef.getTable().getIndexMaintainers(ptr);
+                        tableRef.getTable().getIndexMaintainers(ptr, context.getConnection());
                         ServerCache cache = null;
                         try {
                             if (ptr.getLength() > 0) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
index 95e145c..97818e6 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
@@ -135,6 +135,7 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
     protected final StatementContext context;
     protected final GroupBy groupBy;
     private int nodeCount;
+    private int totalNodeCount;
     private final boolean resolveViewConstants;
 
     public ExpressionCompiler(StatementContext context) {
@@ -166,6 +167,7 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
     public void reset() {
         this.isAggregate = false;
         this.nodeCount = 0;
+        this.totalNodeCount = 0;
     }
 
     @Override
@@ -420,6 +422,7 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
     @Override
     public void addElement(List<Expression> l, Expression element) {
         nodeCount--;
+        totalNodeCount++;
         l.add(element);
     }
 
@@ -553,7 +556,7 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
                 expr =  CastParseNode.convertToRoundExpressionIfNeeded(fromDataType, targetDataType, children);
             }
         }
-        return CoerceExpression.create(expr, targetDataType, SortOrder.getDefault(), expr.getMaxLength());  
+        return wrapGroupByExpression(CoerceExpression.create(expr, targetDataType, SortOrder.getDefault(), expr.getMaxLength()));  
     }
     
    @Override
@@ -1254,4 +1257,8 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
         Object result = context.getSubqueryResult(node.getSelectNode());
         return LiteralExpression.newConstant(result);
     }
+    
+    public int getTotalNodeCount() {
+        return totalNodeCount;
+    }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
index 0163082..1a605b7 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/FromCompiler.java
@@ -184,7 +184,7 @@ public class FromCompiler {
             Expression sourceExpression = projector.getColumnProjector(column.getPosition()).getExpression();
             PColumnImpl projectedColumn = new PColumnImpl(column.getName(), column.getFamilyName(),
                     sourceExpression.getDataType(), sourceExpression.getMaxLength(), sourceExpression.getScale(), sourceExpression.isNullable(),
-                    column.getPosition(), sourceExpression.getSortOrder(), column.getArraySize(), column.getViewConstant(), column.isViewReferenced());
+                    column.getPosition(), sourceExpression.getSortOrder(), column.getArraySize(), column.getViewConstant(), column.isViewReferenced(), column.getExpressionStr());
             projectedColumns.add(projectedColumn);
             sourceExpressions.add(sourceExpression);
         }
@@ -391,7 +391,7 @@ public class FromCompiler {
                         familyName = PNameFactory.newName(family);
                     }
                     allcolumns.add(new PColumnImpl(name, familyName, dynColumn.getDataType(), dynColumn.getMaxLength(),
-                            dynColumn.getScale(), dynColumn.isNull(), position, dynColumn.getSortOrder(), dynColumn.getArraySize(), null, false));
+                            dynColumn.getScale(), dynColumn.isNull(), position, dynColumn.getSortOrder(), dynColumn.getArraySize(), null, false, dynColumn.getExpression()));
                     position++;
                 }
                 theTable = PTableImpl.makePTable(theTable, allcolumns);
@@ -469,7 +469,7 @@ public class FromCompiler {
                 }
                 PColumnImpl column = new PColumnImpl(PNameFactory.newName(alias),
                         PNameFactory.newName(QueryConstants.DEFAULT_COLUMN_FAMILY),
-                        null, 0, 0, true, position++, SortOrder.ASC, null, null, false);
+                        null, 0, 0, true, position++, SortOrder.ASC, null, null, false, null);
                 columns.add(column);
             }
             PTable t = PTableImpl.makePTable(null, PName.EMPTY_NAME, PName.EMPTY_NAME,

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/main/java/org/apache/phoenix/compile/HavingCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/HavingCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/HavingCompiler.java
index 0cd6ecf..224a9b4 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/HavingCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/HavingCompiler.java
@@ -35,8 +35,8 @@ import org.apache.phoenix.parse.ParseNode;
 import org.apache.phoenix.parse.SelectStatement;
 import org.apache.phoenix.parse.SelectStatementRewriter;
 import org.apache.phoenix.schema.ColumnRef;
-import org.apache.phoenix.schema.types.PBoolean;
 import org.apache.phoenix.schema.TypeMismatchException;
+import org.apache.phoenix.schema.types.PBoolean;
 
 
 public class HavingCompiler {
@@ -171,7 +171,7 @@ public class HavingCompiler {
         @Override
         public Void visit(ColumnParseNode node) throws SQLException {
             ColumnRef ref = context.getResolver().resolveColumn(node.getSchemaName(), node.getTableName(), node.getName());
-            boolean isAggregateColumn = groupBy.getExpressions().indexOf(ref.newColumnExpression()) >= 0;
+            boolean isAggregateColumn = groupBy.getExpressions().indexOf(ref.newColumnExpression(node.isTableNameCaseSensitive(), node.isCaseSensitive())) >= 0;
             if (hasOnlyAggregateColumns == null) {
                 hasOnlyAggregateColumns = isAggregateColumn;
             } else {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/main/java/org/apache/phoenix/compile/IndexExpressionCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/IndexExpressionCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/IndexExpressionCompiler.java
new file mode 100644
index 0000000..b4a4168
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/IndexExpressionCompiler.java
@@ -0,0 +1,53 @@
+/*
+ * 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.phoenix.compile;
+
+import java.sql.SQLException;
+
+import org.apache.phoenix.expression.ColumnExpression;
+import org.apache.phoenix.parse.ColumnParseNode;
+import org.apache.phoenix.schema.ColumnRef;
+
+/**
+ * Used to check if the expression being compiled is a {@link ColumnExpression}
+ */
+public class IndexExpressionCompiler extends ExpressionCompiler {
+
+    //
+    private ColumnRef columnRef;
+
+    public IndexExpressionCompiler(StatementContext context) {
+        super(context);
+        this.columnRef = null;
+    }
+
+    @Override
+    public void reset() {
+        super.reset();
+        this.columnRef = null;
+    }
+
+    @Override
+    protected ColumnRef resolveColumn(ColumnParseNode node) throws SQLException {
+        ColumnRef columnRef = super.resolveColumn(node);
+        if (isTopLevel()) {
+            this.columnRef = columnRef;
+        }
+        return columnRef;
+    }
+
+    /**
+     * @return if the expression being compiled is a regular column the column ref, else is null
+     */
+    public ColumnRef getColumnRef() {
+        return columnRef;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/8c340f5a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
index 445edd8..c29ea23 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/JoinCompiler.java
@@ -788,7 +788,7 @@ public class JoinCompiler {
             PName name = PNameFactory.newName(aliasedName);
             PColumnImpl column = new PColumnImpl(name, familyName, sourceColumn.getDataType(),
                     sourceColumn.getMaxLength(), sourceColumn.getScale(), sourceColumn.isNullable(),
-                    position, sourceColumn.getSortOrder(), sourceColumn.getArraySize(), sourceColumn.getViewConstant(), sourceColumn.isViewReferenced());
+                    position, sourceColumn.getSortOrder(), sourceColumn.getArraySize(), sourceColumn.getViewConstant(), sourceColumn.isViewReferenced(), sourceColumn.getExpressionStr());
             Expression sourceExpression = isLocalIndexColumnRef ?
                       NODE_FACTORY.column(TableName.create(schemaName, tableName), "\"" + colName + "\"", null).accept(new ExpressionCompiler(context))
                     : new ColumnRef(tableRef, sourceColumn.getPosition()).newColumnExpression();
@@ -807,7 +807,7 @@ public class JoinCompiler {
                 Expression sourceExpression = rowProjector.getColumnProjector(column.getPosition()).getExpression();
                 PColumnImpl projectedColumn = new PColumnImpl(PNameFactory.newName(colName), PNameFactory.newName(TupleProjector.VALUE_COLUMN_FAMILY),
                         sourceExpression.getDataType(), sourceExpression.getMaxLength(), sourceExpression.getScale(), sourceExpression.isNullable(),
-                        column.getPosition(), sourceExpression.getSortOrder(), column.getArraySize(), column.getViewConstant(), column.isViewReferenced());
+                        column.getPosition(), sourceExpression.getSortOrder(), column.getArraySize(), column.getViewConstant(), column.isViewReferenced(), column.getExpressionStr());
                 projectedColumns.add(projectedColumn);
                 sourceExpressions.add(sourceExpression);
             }
@@ -1367,7 +1367,7 @@ public class JoinCompiler {
                     } else {
                         PColumnImpl column = new PColumnImpl(c.getName(), c.getFamilyName(), c.getDataType(),
                                 c.getMaxLength(), c.getScale(), true, c.getPosition(),
-                                c.getSortOrder(), c.getArraySize(), c.getViewConstant(), c.isViewReferenced());
+                                c.getSortOrder(), c.getArraySize(), c.getViewConstant(), c.isViewReferenced(), c.getExpressionStr());
                         merged.add(column);
                     }
                 }
@@ -1378,7 +1378,7 @@ public class JoinCompiler {
                     PColumnImpl column = new PColumnImpl(c.getName(),
                             PNameFactory.newName(TupleProjector.VALUE_COLUMN_FAMILY), c.getDataType(),
                             c.getMaxLength(), c.getScale(), type == JoinType.Inner ? c.isNullable() : true, position++,
-                            c.getSortOrder(), c.getArraySize(), c.getViewConstant(), c.isViewReferenced());
+                            c.getSortOrder(), c.getArraySize(), c.getViewConstant(), c.isViewReferenced(), c.getExpressionStr());
                     merged.add(column);
                 }
             }