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 2014/10/14 10:24:19 UTC

[1/5] git commit: Remove unused imports and local vars

Repository: phoenix
Updated Branches:
  refs/heads/3.0 cc436c9b4 -> 3587d2bcc


Remove unused imports and local vars


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

Branch: refs/heads/3.0
Commit: 746f42e5a8a780deb2a44af0da8c23714bf09ca2
Parents: cc436c9
Author: James Taylor <jt...@salesforce.com>
Authored: Mon Oct 13 23:06:13 2014 -0700
Committer: James Taylor <jt...@salesforce.com>
Committed: Mon Oct 13 23:06:13 2014 -0700

----------------------------------------------------------------------
 .../src/main/java/org/apache/phoenix/schema/MetaDataClient.java     | 1 -
 .../src/main/java/org/apache/phoenix/schema/PTableImpl.java         | 1 -
 .../src/main/java/org/apache/phoenix/schema/stats/PTableStats.java  | 1 -
 3 files changed, 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/746f42e5/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
index a9a59d3..12b5323 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
@@ -301,7 +301,6 @@ public class MetaDataClient {
 
     private MetaDataMutationResult updateCache(PName tenantId, String schemaName, String tableName, boolean alwaysHitServer) throws SQLException {
         long clientTimeStamp = getClientTimeStamp();
-        Long scn = connection.getSCN();
         boolean systemTable = SYSTEM_CATALOG_SCHEMA.equals(schemaName);
         // System tables must always have a null tenantId
         tenantId = systemTable ? null : tenantId;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/746f42e5/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java
index 6733511..f585587 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/PTableImpl.java
@@ -45,7 +45,6 @@ import org.apache.phoenix.hbase.index.util.KeyValueBuilder;
 import org.apache.phoenix.index.IndexMaintainer;
 import org.apache.phoenix.query.QueryConstants;
 import org.apache.phoenix.schema.RowKeySchema.RowKeySchemaBuilder;
-import org.apache.phoenix.schema.stats.GuidePostsInfo;
 import org.apache.phoenix.schema.stats.PTableStats;
 import org.apache.phoenix.schema.stats.PTableStatsImpl;
 import org.apache.phoenix.util.ByteUtil;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/746f42e5/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStats.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStats.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStats.java
index 9719ecf..2c26739 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStats.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/stats/PTableStats.java
@@ -20,7 +20,6 @@ package org.apache.phoenix.schema.stats;
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
-import java.util.List;
 import java.util.SortedMap;
 
 import org.apache.hadoop.io.Writable;


[5/5] git commit: Backport minor fixes from 4.0

Posted by ja...@apache.org.
Backport minor fixes from 4.0


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

Branch: refs/heads/3.0
Commit: 3587d2bccc94dc95a1dd3bb99a7f343ba3d72332
Parents: 88121cb
Author: James Taylor <jt...@salesforce.com>
Authored: Tue Oct 14 01:29:38 2014 -0700
Committer: James Taylor <jt...@salesforce.com>
Committed: Tue Oct 14 01:29:38 2014 -0700

----------------------------------------------------------------------
 .../java/org/apache/phoenix/end2end/BaseViewIT.java | 16 ++++++++--------
 .../apache/phoenix/end2end/index/ViewIndexIT.java   | 14 +++++++++++++-
 .../org/apache/phoenix/schema/MetaDataClient.java   | 12 ++++++++----
 .../java/org/apache/phoenix/util/MetaDataUtil.java  | 10 +++++++++-
 4 files changed, 38 insertions(+), 14 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/3587d2bc/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
index f1135ca..5e1cb9d 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
@@ -39,11 +39,10 @@ import org.junit.experimental.categories.Category;
 
 import com.google.common.collect.Maps;
 
-@Category(HBaseManagedTimeTest.class)
-public class BaseViewIT extends BaseHBaseManagedTimeIT {
+@Category(NeedsOwnMiniClusterTest.class)
+public class BaseViewIT extends BaseOwnClusterHBaseManagedTimeIT {
 
     @BeforeClass
-    @Shadower(classBeingShadowed = BaseHBaseManagedTimeIT.class)
     public static void doSetup() throws Exception {
         Map<String,String> props = Maps.newHashMapWithExpectedSize(1);
         // Don't split intra region so we can more easily know that the n-way parallelization is for the explain plan
@@ -132,13 +131,14 @@ public class BaseViewIT extends BaseHBaseManagedTimeIT {
         conn.createStatement().execute("CREATE INDEX i2 on v(s)");
 
         // new index hasn't been analyzed yet
-        splits = getAllSplits(conn, "i2");
-        assertEquals(saltBuckets == null ? 1 : 3, splits.size());
+//        splits = getAllSplits(conn, "i2");
+//        assertEquals(saltBuckets == null ? 1 : 3, splits.size());
         
         // analyze table should analyze all view data
-        //analyzeTable(conn, "t");        
-        //splits = getAllSplits(conn, "i2");
-        //assertEquals(saltBuckets == null ? 6 : 8, splits.size());
+        analyzeTable(conn, "t");        
+        splits = getAllSplits(conn, "i2");
+//        assertEquals(saltBuckets == null ? 6 : 8, splits.size());
+        assertEquals(saltBuckets == null ? 11 : 13, splits.size());
 
         query = "SELECT k1, k2, s FROM v WHERE s = 'foo'";
         rs = conn.createStatement().executeQuery(query);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3587d2bc/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
index 19053d9..2f9f9b6 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
@@ -18,6 +18,7 @@
 package org.apache.phoenix.end2end.index;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
@@ -82,6 +83,17 @@ public class ViewIndexIT extends BaseIndexIT {
                 + PhoenixDatabaseMetaData.SEQUENCE_SCHEMA + ","
                 + PhoenixDatabaseMetaData.SEQUENCE_NAME
                 + " FROM " + PhoenixDatabaseMetaData.SEQUENCE_TABLE_NAME);
-        assertFalse("View index sequences should be deleted.", rs.next());
+        StringBuilder buf = new StringBuilder();
+        while (rs.next()) {
+            String schemaName = rs.getString(1);
+            String tableName = rs.getString(2);
+            String fullName = schemaName == null ? "" : schemaName;
+            if (tableName != null && tableName.length() > 0) {
+                fullName += "." + tableName;
+            }
+            buf.append(fullName);
+            buf.append(' ');
+        }
+        assertTrue("View index sequences should be deleted, but found " + buf.toString(), buf.length() == 0);
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3587d2bc/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
index 12b5323..6a852a2 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
@@ -1442,12 +1442,19 @@ public class MetaDataClient {
             @SuppressWarnings("deprecation") // FIXME: Remove when unintentionally deprecated method is fixed (HBASE-7870).
             Delete tableDelete = new Delete(key, clientTimeStamp, null);
             tableMetaData.add(tableDelete);
+            boolean hasViewIndexTable = false;
+            boolean dropMetaData = connection.getQueryServices().getProps().getBoolean(DROP_METADATA_ATTRIB, DEFAULT_DROP_METADATA);
+            
             if (parentTableName != null) {
                 byte[] linkKey = MetaDataUtil.getParentLinkKey(tenantIdStr, schemaName, parentTableName, tableName);
                 @SuppressWarnings("deprecation") // FIXME: Remove when unintentionally deprecated method is fixed (HBASE-7870).
                 Delete linkDelete = new Delete(linkKey, clientTimeStamp, null);
                 tableMetaData.add(linkDelete);
+            } else if (tableType == PTableType.TABLE && dropMetaData) {
+                // Check before dropping the HBase view index table in the dropTable call or we'll always get false for this
+                hasViewIndexTable = MetaDataUtil.hasViewIndexTable(connection, schemaName, tableName);
             }
+                
 
             MetaDataMutationResult result = connection.getQueryServices().dropTable(tableMetaData, tableType, cascade);
             MutationCode code = result.getMutationCode();
@@ -1465,9 +1472,6 @@ public class MetaDataClient {
                 default:
                     connection.removeTable(tenantId, SchemaUtil.getTableName(schemaName, tableName), parentTableName, result.getMutationTime());
                     
-                    // TODO: we need to drop the index data when a view is dropped
-                    boolean dropMetaData = connection.getQueryServices().getProps().getBoolean(DROP_METADATA_ATTRIB, DEFAULT_DROP_METADATA);
-                                        
                     if (result.getTable() != null && tableType != PTableType.VIEW) {
                         connection.setAutoCommit(true);
                         PTable table = result.getTable();
@@ -1476,7 +1480,7 @@ public class MetaDataClient {
                         // PName name, PTableType type, long timeStamp, long sequenceNumber, List<PColumn> columns
                         List<TableRef> tableRefs = Lists.newArrayListWithExpectedSize(2 + table.getIndexes().size());
                         // All multi-tenant tables have a view index table, so no need to check in that case
-                        if (tableType == PTableType.TABLE && (table.isMultiTenant() || MetaDataUtil.hasViewIndexTable(connection, table.getPhysicalName()))) {
+                        if (tableType == PTableType.TABLE && (table.isMultiTenant() || hasViewIndexTable)) {
                             MetaDataUtil.deleteViewIndexSequences(connection, table.getPhysicalName());
                             // TODO: consider removing this, as the DROP INDEX done for each DROP VIEW command
                             // would have deleted all the rows already

http://git-wip-us.apache.org/repos/asf/phoenix/blob/3587d2bc/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
index 4ab3c01..dfeeddb 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/MetaDataUtil.java
@@ -278,7 +278,15 @@ public class MetaDataUtil {
     }
 
     public static boolean hasViewIndexTable(PhoenixConnection connection, PName name) throws SQLException {
-        byte[] physicalIndexName = MetaDataUtil.getViewIndexPhysicalName(name.getBytes());
+        return hasViewIndexTable(connection, name.getBytes());
+    }
+    
+    public static boolean hasViewIndexTable(PhoenixConnection connection, String schemaName, String tableName) throws SQLException {
+        return hasViewIndexTable(connection, SchemaUtil.getTableNameAsBytes(schemaName, tableName));
+    }
+    
+    public static boolean hasViewIndexTable(PhoenixConnection connection, byte[] physicalTableName) throws SQLException {
+        byte[] physicalIndexName = MetaDataUtil.getViewIndexPhysicalName(physicalTableName);
         try {
             HTableDescriptor desc = connection.getQueryServices().getTableDescriptor(physicalIndexName);
             return desc != null && Boolean.TRUE.equals(PDataType.BOOLEAN.toObject(desc.getValue(IS_VIEW_INDEX_TABLE_PROP_BYTES)));


[3/5] git commit: PHOENIX-1313 Investigate why LocalIndexIT.testLocalIndexScanAfterRegionSplit() is failing(Rajeshbabu)

Posted by ja...@apache.org.
PHOENIX-1313 Investigate why LocalIndexIT.testLocalIndexScanAfterRegionSplit() is failing(Rajeshbabu)

Conflicts:
	phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java


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

Branch: refs/heads/3.0
Commit: 9777b6c50790c225b3c2b9e918eeb80f96b4d5ce
Parents: 0ea2a78
Author: James Taylor <jt...@salesforce.com>
Authored: Sat Oct 11 09:13:54 2014 -0700
Committer: James Taylor <jt...@salesforce.com>
Committed: Mon Oct 13 23:19:09 2014 -0700

----------------------------------------------------------------------
 .../apache/phoenix/iterate/ParallelIterators.java  | 17 ++++++++---------
 .../org/apache/phoenix/jdbc/PhoenixResultSet.java  |  4 +---
 2 files changed, 9 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/9777b6c5/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java
index 4cdda83..9fbee29 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java
@@ -352,10 +352,7 @@ public class ParallelIterators extends ExplainTable implements ResultIterators {
         return buf.toString();
     }
     
-    private List<Scan> addNewScan(List<List<Scan>> parallelScans, List<Scan> scans, Scan scan, boolean crossedRegionBoundary) {
-        if (scan == null) {
-            return scans;
-        }
+    private List<Scan> addNewScan(List<List<Scan>> parallelScans, List<Scan> scans, Scan scan, byte[] startKey, boolean crossedRegionBoundary) {
         PTable table = getTable();
         boolean startNewScanList = false;
         if (!plan.isRowKeyOrdered()) {
@@ -363,13 +360,15 @@ public class ParallelIterators extends ExplainTable implements ResultIterators {
         } else if (crossedRegionBoundary) {
             if (table.getBucketNum() != null) {
                 startNewScanList = scans.isEmpty() ||
-                        ScanUtil.crossesPrefixBoundary(scan.getStartRow(),
+                        ScanUtil.crossesPrefixBoundary(startKey,
                                 ScanUtil.getPrefix(scans.get(scans.size()-1).getStartRow(), SaltingUtil.NUM_SALTING_BYTES), 
                                 SaltingUtil.NUM_SALTING_BYTES);
             }
         }
-        scans.add(scan);
-        if (startNewScanList) {
+        if (scan != null) {
+            scans.add(scan);
+        }
+        if (startNewScanList && !scans.isEmpty()) {
             parallelScans.add(scans);
             scans = Lists.newArrayListWithExpectedSize(1);
         }
@@ -425,12 +424,12 @@ public class ParallelIterators extends ExplainTable implements ResultIterators {
             while (guideIndex < gpsSize
                     && (Bytes.compareTo(currentGuidePost = gps.get(guideIndex), endKey) <= 0 || endKey.length == 0)) {
                 Scan newScan = scanRanges.intersectScan(scan, currentKey, currentGuidePost, keyOffset, false);
-                scans = addNewScan(parallelScans, scans, newScan, false);
+                scans = addNewScan(parallelScans, scans, newScan, currentGuidePost, false);
                 currentKey = currentGuidePost;
                 guideIndex++;
             }
             Scan newScan = scanRanges.intersectScan(scan, currentKey, endKey, keyOffset, true);
-            scans = addNewScan(parallelScans, scans, newScan, true);
+            scans = addNewScan(parallelScans, scans, newScan, endKey, true);
             currentKey = endKey;
             regionIndex++;
         }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/9777b6c5/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
index 27c12d6..3f280ca 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
@@ -35,7 +35,6 @@ import java.sql.SQLException;
 import java.sql.SQLFeatureNotSupportedException;
 import java.sql.SQLWarning;
 import java.sql.SQLXML;
-import java.sql.Statement;
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.text.Format;
@@ -43,7 +42,6 @@ import java.util.Calendar;
 import java.util.Map;
 
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
-
 import org.apache.phoenix.compile.ColumnProjector;
 import org.apache.phoenix.compile.RowProjector;
 import org.apache.phoenix.exception.SQLExceptionCode;
@@ -552,7 +550,7 @@ public class PhoenixResultSet implements ResultSet, SQLCloseable, org.apache.pho
     }
 
     @Override
-    public Statement getStatement() throws SQLException {
+    public PhoenixStatement getStatement() throws SQLException {
         return statement;
     }
 


[2/5] git commit: PHOENIX-1338 Logic to group together parallel scans is incorrect

Posted by ja...@apache.org.
PHOENIX-1338 Logic to group together parallel scans is incorrect

Conflicts:
	phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
	phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java


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

Branch: refs/heads/3.0
Commit: 0ea2a78d28eb7efe09ef6eb6504a80038d7feede
Parents: 746f42e
Author: James Taylor <jt...@salesforce.com>
Authored: Fri Oct 10 20:44:42 2014 -0700
Committer: James Taylor <jt...@salesforce.com>
Committed: Mon Oct 13 23:16:01 2014 -0700

----------------------------------------------------------------------
 .../org/apache/phoenix/end2end/BaseViewIT.java  | 21 ++++++++++++++
 .../phoenix/iterate/ParallelIterators.java      | 29 +++++++++-----------
 2 files changed, 34 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/0ea2a78d/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
index b6a9994..f1135ca 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseViewIT.java
@@ -17,6 +17,8 @@
  */
 package org.apache.phoenix.end2end;
 
+import static org.apache.phoenix.util.TestUtil.analyzeTable;
+import static org.apache.phoenix.util.TestUtil.getAllSplits;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -25,8 +27,10 @@ import java.math.BigDecimal;
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.ResultSet;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.phoenix.query.KeyRange;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.ReadOnlyProps;
@@ -103,6 +107,13 @@ public class BaseViewIT extends BaseHBaseManagedTimeIT {
         ResultSet rs;
         Connection conn = DriverManager.getConnection(getUrl());
         conn.createStatement().execute("CREATE INDEX i1 on v(k3) include (s)");
+        conn.createStatement().execute("UPSERT INTO v(k2,S,k3) VALUES(120,'foo',50.0)");
+
+        analyzeTable(conn, "v");        
+        List<KeyRange> splits = getAllSplits(conn, "i1");
+        // More guideposts with salted, since it's already pre-split at salt buckets
+        assertEquals(saltBuckets == null ? 6 : 8, splits.size());
+        
         String query = "SELECT k1, k2, k3, s FROM v WHERE k3 = 51.0";
         rs = conn.createStatement().executeQuery(query);
         assertTrue(rs.next());
@@ -119,6 +130,16 @@ public class BaseViewIT extends BaseHBaseManagedTimeIT {
             QueryUtil.getExplainPlan(rs));
 
         conn.createStatement().execute("CREATE INDEX i2 on v(s)");
+
+        // new index hasn't been analyzed yet
+        splits = getAllSplits(conn, "i2");
+        assertEquals(saltBuckets == null ? 1 : 3, splits.size());
+        
+        // analyze table should analyze all view data
+        //analyzeTable(conn, "t");        
+        //splits = getAllSplits(conn, "i2");
+        //assertEquals(saltBuckets == null ? 6 : 8, splits.size());
+
         query = "SELECT k1, k2, s FROM v WHERE s = 'foo'";
         rs = conn.createStatement().executeQuery(query);
         assertTrue(rs.next());

http://git-wip-us.apache.org/repos/asf/phoenix/blob/0ea2a78d/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java
index e4fb0ed..4cdda83 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/iterate/ParallelIterators.java
@@ -157,7 +157,6 @@ public class ParallelIterators extends ExplainTable implements ResultIterators {
         
         this.iteratorFactory = iteratorFactory;
         this.scans = getParallelScans(context.getScan());
-        List<List<Scan>> scans = getParallelScans(context.getScan());
         List<KeyRange> splitRanges = Lists.newArrayListWithExpectedSize(scans.size() * ESTIMATED_GUIDEPOSTS_PER_REGION);
         for (List<Scan> scanList : scans) {
             for (Scan aScan : scanList) {
@@ -358,24 +357,22 @@ public class ParallelIterators extends ExplainTable implements ResultIterators {
             return scans;
         }
         PTable table = getTable();
-        if (!scans.isEmpty()) {
-            boolean startNewScanList = false;
-            if (!plan.isRowKeyOrdered()) {
-                startNewScanList = true;
-            } else if (crossedRegionBoundary) {
-                if (table.getBucketNum() != null) {
-                    byte[] previousStartKey = scans.get(scans.size()-1).getStartRow();
-                    byte[] currentStartKey = scan.getStartRow();
-                    byte[] prefix = ScanUtil.getPrefix(previousStartKey, SaltingUtil.NUM_SALTING_BYTES);
-                    startNewScanList = ScanUtil.crossesPrefixBoundary(currentStartKey, prefix, SaltingUtil.NUM_SALTING_BYTES);
-                }
-            }
-            if (startNewScanList) {
-                parallelScans.add(scans);
-                scans = Lists.newArrayListWithExpectedSize(1);
+        boolean startNewScanList = false;
+        if (!plan.isRowKeyOrdered()) {
+            startNewScanList = true;
+        } else if (crossedRegionBoundary) {
+            if (table.getBucketNum() != null) {
+                startNewScanList = scans.isEmpty() ||
+                        ScanUtil.crossesPrefixBoundary(scan.getStartRow(),
+                                ScanUtil.getPrefix(scans.get(scans.size()-1).getStartRow(), SaltingUtil.NUM_SALTING_BYTES), 
+                                SaltingUtil.NUM_SALTING_BYTES);
             }
         }
         scans.add(scan);
+        if (startNewScanList) {
+            parallelScans.add(scans);
+            scans = Lists.newArrayListWithExpectedSize(1);
+        }
         return scans;
     }
     /**


[4/5] git commit: PHOENIX-619 Support DELETE over table with immutable index when possible

Posted by ja...@apache.org.
PHOENIX-619 Support DELETE over table with immutable index when possible

Conflicts:
	phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
	phoenix-core/src/main/java/org/apache/phoenix/compile/DeleteCompiler.java
	phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
	phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
	phoenix-core/src/main/java/org/apache/phoenix/schema/MetaDataClient.java
	phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java


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

Branch: refs/heads/3.0
Commit: 88121cb05b5aa911e7cc27ff5306423299d5d1b6
Parents: 9777b6c
Author: James Taylor <jt...@salesforce.com>
Authored: Mon Oct 13 20:23:43 2014 -0700
Committer: James Taylor <jt...@salesforce.com>
Committed: Mon Oct 13 23:41:27 2014 -0700

----------------------------------------------------------------------
 .../end2end/BaseTenantSpecificTablesIT.java     |   4 +-
 .../end2end/TenantSpecificTablesDMLIT.java      |  43 ++
 .../phoenix/end2end/index/ImmutableIndexIT.java |   2 +-
 .../phoenix/end2end/index/ViewIndexIT.java      |  87 +++
 .../apache/phoenix/compile/DeleteCompiler.java  | 541 ++++++++++++-------
 .../MutatingParallelIteratorFactory.java        |   5 +-
 .../phoenix/compile/PostIndexDDLCompiler.java   |  37 +-
 .../apache/phoenix/compile/UpsertCompiler.java  |   4 +-
 .../phoenix/exception/SQLExceptionCode.java     |   2 +-
 .../apache/phoenix/execute/MutationState.java   |  44 +-
 .../apache/phoenix/jdbc/PhoenixResultSet.java   |   4 +
 .../apache/phoenix/optimize/QueryOptimizer.java |  55 +-
 .../query/ConnectionQueryServicesImpl.java      |   1 +
 .../java/org/apache/phoenix/util/IndexUtil.java |  14 +-
 .../phoenix/compile/QueryCompilerTest.java      |  14 +-
 .../TenantSpecificViewIndexCompileTest.java     |   2 +-
 16 files changed, 600 insertions(+), 259 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificTablesIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificTablesIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificTablesIT.java
index 362fa08..6d6bffc 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificTablesIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificTablesIT.java
@@ -44,7 +44,7 @@ public abstract class BaseTenantSpecificTablesIT extends BaseOwnClusterClientMan
             "                tenant_id VARCHAR(5) NOT NULL,\n" + 
             "                tenant_type_id VARCHAR(3) NOT NULL, \n" + 
             "                id INTEGER NOT NULL\n" + 
-            "                CONSTRAINT pk PRIMARY KEY (tenant_id, tenant_type_id, id)) MULTI_TENANT=true";
+            "                CONSTRAINT pk PRIMARY KEY (tenant_id, tenant_type_id, id)) MULTI_TENANT=true, IMMUTABLE_ROWS=true";
     
     protected static final String TENANT_TABLE_NAME = "TENANT_TABLE";
     protected static final String TENANT_TABLE_DDL = "CREATE VIEW " + TENANT_TABLE_NAME + " ( \n" + 
@@ -56,7 +56,7 @@ public abstract class BaseTenantSpecificTablesIT extends BaseOwnClusterClientMan
             "                user VARCHAR ,\n" + 
             "                tenant_id VARCHAR(5) NOT NULL,\n" + 
             "                id INTEGER NOT NULL,\n" + 
-            "                CONSTRAINT pk PRIMARY KEY (tenant_id, id)) MULTI_TENANT=true";
+            "                CONSTRAINT pk PRIMARY KEY (tenant_id, id)) MULTI_TENANT=true, IMMUTABLE_ROWS=true";
     
     protected static final String TENANT_TABLE_NAME_NO_TENANT_TYPE_ID = "TENANT_TABLE_NO_TENANT_TYPE_ID";
     protected static final String TENANT_TABLE_DDL_NO_TENANT_TYPE_ID = "CREATE VIEW " + TENANT_TABLE_NAME_NO_TENANT_TYPE_ID + " ( \n" + 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java
index 76a8c7c..7fd3a82 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/TenantSpecificTablesDMLIT.java
@@ -265,6 +265,49 @@ public class TenantSpecificTablesDMLIT extends BaseTenantSpecificTablesIT {
     }
     
     @Test
+    public void testDeleteWhenImmutableIndex() throws Exception {
+        Connection conn = nextConnection(getUrl());
+        try {
+            conn.setAutoCommit(true);
+            conn.createStatement().executeUpdate("delete from " + PARENT_TABLE_NAME);
+            conn.close();
+            
+            conn = nextConnection(getUrl());
+            conn.setAutoCommit(true);
+            conn.createStatement().executeUpdate("upsert into " + PARENT_TABLE_NAME + " (tenant_id, tenant_type_id, id, user) values ('AC/DC', 'abc', 1, 'Bon Scott')");
+            conn.createStatement().executeUpdate("upsert into " + PARENT_TABLE_NAME + " (tenant_id, tenant_type_id, id, user) values ('" + TENANT_ID + "', '" + TENANT_TYPE_ID + "', 1, 'Billy Gibbons')");
+            conn.createStatement().executeUpdate("upsert into " + PARENT_TABLE_NAME + " (tenant_id, tenant_type_id, id, user) values ('" + TENANT_ID + "', 'def', 1, 'Billy Gibbons')");
+            conn.close();
+            
+            conn = nextConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL);
+            conn.createStatement().executeUpdate("create index idx1 on " + TENANT_TABLE_NAME + "(user)");
+            conn.close();
+            
+            conn = nextConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL);
+            conn.setAutoCommit(true);
+            int count = conn.createStatement().executeUpdate("delete from " + TENANT_TABLE_NAME + " where user='Billy Gibbons'");
+            assertEquals("Expected 1 row have been deleted", 1, count);
+            conn.close();
+            
+            conn = nextConnection(PHOENIX_JDBC_TENANT_SPECIFIC_URL);
+            conn.setAutoCommit(true);
+            ResultSet rs = conn.createStatement().executeQuery("select * from " + TENANT_TABLE_NAME);
+            assertFalse("Expected no rows in result set", rs.next());
+            conn.close();
+            
+            conn = nextConnection(getUrl());
+            analyzeTable(conn, PARENT_TABLE_NAME);
+            conn = nextConnection(getUrl());
+            rs = conn.createStatement().executeQuery("select count(*) from " + PARENT_TABLE_NAME);
+            rs.next();
+            assertEquals(2, rs.getInt(1));
+        }
+        finally {
+            conn.close();
+        }
+    }
+    
+    @Test
     public void testDeleteOnlyDeletesTenantDataWithNoTenantTypeId() throws Exception {
         Connection conn = nextConnection(getUrl());
         try {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/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 1d7ac92..eb3a668 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
@@ -272,7 +272,7 @@ public class ImmutableIndexIT extends BaseHBaseManagedTimeIT {
             conn.createStatement().execute(dml);
             fail();
         } catch (SQLException e) {
-            assertEquals(SQLExceptionCode.NO_DELETE_IF_IMMUTABLE_INDEX.getErrorCode(), e.getErrorCode());
+            assertEquals(SQLExceptionCode.INVALID_FILTER_ON_IMMUTABLE_ROWS.getErrorCode(), e.getErrorCode());
         }
             
         conn.createStatement().execute("DROP TABLE " + INDEX_DATA_SCHEMA + QueryConstants.NAME_SEPARATOR + INDEX_DATA_TABLE);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
new file mode 100644
index 0000000..19053d9
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/ViewIndexIT.java
@@ -0,0 +1,87 @@
+/*
+ * 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.junit.Assert.assertFalse;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Map;
+
+import org.apache.hadoop.hbase.client.HBaseAdmin;
+import org.apache.phoenix.end2end.HBaseManagedTimeTest;
+import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
+import org.apache.phoenix.query.QueryServices;
+import org.apache.phoenix.util.MetaDataUtil;
+import org.apache.phoenix.util.ReadOnlyProps;
+import org.apache.phoenix.util.TestUtil;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import com.google.common.collect.Maps;
+
+@Category(HBaseManagedTimeTest.class)
+public class ViewIndexIT extends BaseIndexIT {
+
+    private String VIEW_NAME = "MY_VIEW";
+
+    @BeforeClass
+    public static void doSetup() throws Exception {
+        Map<String,String> props = Maps.newHashMapWithExpectedSize(3);
+        // Drop the HBase table metadata for this test to confirm that view index table dropped
+        props.put(QueryServices.DROP_METADATA_ATTRIB, Boolean.toString(true));
+        // Must update config before starting server
+        setUpTestDriver(new ReadOnlyProps(props.entrySet().iterator()));
+    }
+
+    private void createBaseTable(String tableName, Integer saltBuckets, String splits) throws SQLException {
+        Connection conn = DriverManager.getConnection(getUrl());
+        String ddl = "CREATE TABLE " + tableName + " (t_id VARCHAR NOT NULL,\n" +
+                "k1 INTEGER NOT NULL,\n" +
+                "k2 INTEGER NOT NULL,\n" +
+                "v1 VARCHAR,\n" +
+                "CONSTRAINT pk PRIMARY KEY (t_id, k1, k2))\n"
+                        + (saltBuckets == null || splits != null ? "" : (",salt_buckets=" + saltBuckets)
+                        + (saltBuckets != null || splits == null ? "" : ",splits=" + splits));
+        conn.createStatement().execute(ddl);
+        conn.close();
+    }
+
+    @Test
+    public void testDeleteViewIndexSequences() throws Exception {
+        createBaseTable(DATA_TABLE_NAME, null, null);
+        Connection conn1 = DriverManager.getConnection(getUrl());
+        Connection conn2 = DriverManager.getConnection(getUrl());
+        conn1.createStatement().execute("CREATE VIEW " + VIEW_NAME + " AS SELECT * FROM " + DATA_TABLE_NAME);
+        conn1.createStatement().execute("CREATE INDEX " + INDEX_TABLE_NAME + " ON " + VIEW_NAME + " (v1)");
+        conn2.createStatement().executeQuery("SELECT * FROM " + DATA_TABLE_FULL_NAME).next();
+        HBaseAdmin admin = driver.getConnectionQueryServices(getUrl(), TestUtil.TEST_PROPERTIES).getAdmin();
+        conn1.createStatement().execute("DROP VIEW " + VIEW_NAME);
+        conn1.createStatement().execute("DROP TABLE "+ DATA_TABLE_NAME);
+        admin = driver.getConnectionQueryServices(getUrl(), TestUtil.TEST_PROPERTIES).getAdmin();
+        assertFalse("View index table should be deleted.", admin.tableExists(MetaDataUtil.getViewIndexTableName(DATA_TABLE_NAME)));
+        ResultSet rs = conn2.createStatement().executeQuery("SELECT "
+                + PhoenixDatabaseMetaData.SEQUENCE_SCHEMA + ","
+                + PhoenixDatabaseMetaData.SEQUENCE_NAME
+                + " FROM " + PhoenixDatabaseMetaData.SEQUENCE_TABLE_NAME);
+        assertFalse("View index sequences should be deleted.", rs.next());
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/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 2fd5535..8af0e15 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
@@ -18,13 +18,13 @@
 package org.apache.phoenix.compile;
 
 import java.sql.ParameterMetaData;
-import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.client.Scan;
@@ -64,20 +64,24 @@ import org.apache.phoenix.schema.MetaDataClient;
 import org.apache.phoenix.schema.MetaDataEntityNotFoundException;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PDataType;
+import org.apache.phoenix.schema.PIndexState;
 import org.apache.phoenix.schema.PName;
 import org.apache.phoenix.schema.PRow;
 import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.PTableKey;
 import org.apache.phoenix.schema.PTableType;
 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.util.IndexUtil;
 import org.apache.phoenix.util.MetaDataUtil;
 import org.apache.phoenix.util.ScanUtil;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.sun.istack.NotNull;
 
 public class DeleteCompiler {
     private static ParseNodeFactory FACTORY = new ParseNodeFactory();
@@ -88,8 +92,8 @@ public class DeleteCompiler {
         this.statement = statement;
     }
     
-    private static MutationState deleteRows(PhoenixStatement statement, TableRef tableRef, ResultIterator iterator, RowProjector projector) throws SQLException {
-        PTable table = tableRef.getTable();
+    private static MutationState deleteRows(PhoenixStatement statement, TableRef targetTableRef, TableRef indexTableRef, ResultIterator iterator, RowProjector projector, TableRef sourceTableRef) throws SQLException {
+        PTable table = targetTableRef.getTable();
         PhoenixConnection connection = statement.getConnection();
         PName tenantId = connection.getTenantId();
         byte[] tenantIdBytes = null;
@@ -102,6 +106,12 @@ public class DeleteCompiler {
         final int maxSize = services.getProps().getInt(QueryServices.MAX_MUTATION_SIZE_ATTRIB,QueryServicesOptions.DEFAULT_MAX_MUTATION_SIZE);
         final int batchSize = Math.min(connection.getMutateBatchSize(), maxSize);
         Map<ImmutableBytesPtr,Map<PColumn,byte[]>> mutations = Maps.newHashMapWithExpectedSize(batchSize);
+        Map<ImmutableBytesPtr,Map<PColumn,byte[]>> indexMutations = null;
+        // If indexTableRef is set, we're deleting the rows from both the index table and
+        // the data table through a single query to save executing an additional one.
+        if (indexTableRef != null) {
+            indexMutations = Maps.newHashMapWithExpectedSize(batchSize);
+        }
         try {
             List<PColumn> pkColumns = table.getPKColumns();
             boolean isMultiTenant = table.isMultiTenant() && tenantIdBytes != null;
@@ -114,37 +124,61 @@ public class DeleteCompiler {
             if (isSharedViewIndex) {
                 values[offset++] = MetaDataUtil.getViewIndexIdDataType().toBytes(table.getViewIndexId());
             }
-            ResultSet rs = new PhoenixResultSet(iterator, projector, statement);
+            PhoenixResultSet rs = new PhoenixResultSet(iterator, projector, statement);
             int rowCount = 0;
             while (rs.next()) {
-                for (int i = offset; i < values.length; i++) {
-                    byte[] byteValue = rs.getBytes(i+1-offset);
-                    // The ResultSet.getBytes() call will have inverted it - we need to invert it back.
-                    // TODO: consider going under the hood and just getting the bytes
-                    if (pkColumns.get(i).getSortOrder() == SortOrder.DESC) {
-                        byte[] tempByteValue = Arrays.copyOf(byteValue, byteValue.length);
-                        byteValue = SortOrder.invert(byteValue, 0, tempByteValue, 0, byteValue.length);
+                ImmutableBytesPtr ptr = new ImmutableBytesPtr();  // allocate new as this is a key in a Map
+                // Use tuple directly, as projector would not have all the PK columns from
+                // our index table inside of our projection. Since the tables are equal,
+                // there's no transation required.
+                if (sourceTableRef.equals(targetTableRef)) {
+                    rs.getCurrentRow().getKey(ptr);
+                } else {
+                    for (int i = offset; i < values.length; i++) {
+                        byte[] byteValue = rs.getBytes(i+1-offset);
+                        // The ResultSet.getBytes() call will have inverted it - we need to invert it back.
+                        // TODO: consider going under the hood and just getting the bytes
+                        if (pkColumns.get(i).getSortOrder() == SortOrder.DESC) {
+                            byte[] tempByteValue = Arrays.copyOf(byteValue, byteValue.length);
+                            byteValue = SortOrder.invert(byteValue, 0, tempByteValue, 0, byteValue.length);
+                        }
+                        values[i] = byteValue;
                     }
-                    values[i] = byteValue;
+                    table.newKey(ptr, values);
                 }
-                ImmutableBytesPtr ptr = new ImmutableBytesPtr();
-                table.newKey(ptr, values);
                 mutations.put(ptr, PRow.DELETE_MARKER);
+                if (indexTableRef != null) {
+                    ImmutableBytesPtr indexPtr = new ImmutableBytesPtr(); // allocate new as this is a key in a Map
+                    rs.getCurrentRow().getKey(indexPtr);
+                    indexMutations.put(indexPtr, PRow.DELETE_MARKER);
+                }
                 if (mutations.size() > maxSize) {
                     throw new IllegalArgumentException("MutationState size of " + mutations.size() + " is bigger than max allowed size of " + maxSize);
                 }
                 rowCount++;
                 // Commit a batch if auto commit is true and we're at our batch size
                 if (isAutoCommit && rowCount % batchSize == 0) {
-                    MutationState state = new MutationState(tableRef, mutations, 0, maxSize, connection);
+                    MutationState state = new MutationState(targetTableRef, mutations, 0, maxSize, connection);
                     connection.getMutationState().join(state);
+                    if (indexTableRef != null) {
+                        MutationState indexState = new MutationState(indexTableRef, indexMutations, 0, maxSize, connection);
+                        connection.getMutationState().join(indexState);
+                    }
                     connection.commit();
                     mutations.clear();
+                    indexMutations.clear();
                 }
             }
 
             // If auto commit is true, this last batch will be committed upon return
-            return new MutationState(tableRef, mutations, rowCount / batchSize * batchSize, maxSize, connection);
+            int nCommittedRows = rowCount / batchSize * batchSize;
+            MutationState state = new MutationState(targetTableRef, mutations, nCommittedRows, maxSize, connection);
+            if (indexTableRef != null) {
+                // To prevent the counting of these index rows, we have a negative for remainingRows.
+                MutationState indexState = new MutationState(indexTableRef, indexMutations, 0, maxSize, connection);
+                state.join(indexState);
+            }
+            return state;
         } finally {
             iterator.close();
         }
@@ -152,39 +186,90 @@ public class DeleteCompiler {
     
     private static class DeletingParallelIteratorFactory extends MutatingParallelIteratorFactory {
         private RowProjector projector;
+        private TableRef targetTableRef;
+        private TableRef indexTableRef;
+        private TableRef sourceTableRef;
         
-        private DeletingParallelIteratorFactory(PhoenixConnection connection, TableRef tableRef) {
-            super(connection, tableRef);
+        private DeletingParallelIteratorFactory(PhoenixConnection connection) {
+            super(connection);
         }
         
         @Override
         protected MutationState mutate(StatementContext context, ResultIterator iterator, PhoenixConnection connection) throws SQLException {
             PhoenixStatement statement = new PhoenixStatement(connection);
-            return deleteRows(statement, tableRef, iterator, projector);
+            return deleteRows(statement, targetTableRef, indexTableRef, iterator, projector, sourceTableRef);
+        }
+        
+        public void setTargetTableRef(TableRef tableRef) {
+            this.targetTableRef = tableRef;
+        }
+        
+        public void setSourceTableRef(TableRef tableRef) {
+            this.sourceTableRef = tableRef;
         }
         
         public void setRowProjector(RowProjector projector) {
             this.projector = projector;
         }
+
+        public void setIndexTargetTableRef(TableRef indexTableRef) {
+            this.indexTableRef = indexTableRef;
+        }
         
     }
     
-    private boolean hasImmutableIndex(TableRef tableRef) {
-        return tableRef.getTable().isImmutableRows() && !tableRef.getTable().getIndexes().isEmpty();
+    private Set<PTable> getNonDisabledImmutableIndexes(TableRef tableRef) {
+        PTable table = tableRef.getTable();
+        if (table.isImmutableRows() && !table.getIndexes().isEmpty()) {
+            Set<PTable> nonDisabledIndexes = Sets.newHashSetWithExpectedSize(table.getIndexes().size());
+            for (PTable index : table.getIndexes()) {
+                if (index.getIndexState() != PIndexState.DISABLE) {
+                    nonDisabledIndexes.add(index);
+                }
+            }
+            return nonDisabledIndexes;
+        }
+        return Collections.emptySet();
     }
     
-    private boolean hasImmutableIndexWithKeyValueColumns(TableRef tableRef) {
-        if (!hasImmutableIndex(tableRef)) {
-            return false;
+    private class MultiDeleteMutationPlan implements MutationPlan {
+        private final List<MutationPlan> plans;
+        private final MutationPlan firstPlan;
+        
+        public MultiDeleteMutationPlan(@NotNull List<MutationPlan> plans) {
+            Preconditions.checkArgument(!plans.isEmpty());
+            this.plans = plans;
+            this.firstPlan = plans.get(0);
         }
-        for (PTable index : tableRef.getTable().getIndexes()) {
-            for (PColumn column : index.getPKColumns()) {
-                if (!IndexUtil.isDataPKColumn(column)) {
-                    return true;
-                }
+        
+        @Override
+        public StatementContext getContext() {
+            return firstPlan.getContext();
+        }
+
+        @Override
+        public ParameterMetaData getParameterMetaData() {
+            return firstPlan.getParameterMetaData();
+        }
+
+        @Override
+        public ExplainPlan getExplainPlan() throws SQLException {
+            return firstPlan.getExplainPlan();
+        }
+
+        @Override
+        public PhoenixConnection getConnection() {
+            return firstPlan.getConnection();
+        }
+
+        @Override
+        public MutationState execute() throws SQLException {
+            MutationState state = firstPlan.execute();
+            for (MutationPlan plan : plans.subList(1, plans.size())) {
+                plan.execute();
             }
+            return state;
         }
-        return false;
     }
     
     public MutationPlan compile(DeleteStatement delete) throws SQLException {
@@ -192,7 +277,7 @@ public class DeleteCompiler {
         final boolean isAutoCommit = connection.getAutoCommit();
         final boolean hasLimit = delete.getLimit() != null;
         final ConnectionQueryServices services = connection.getQueryServices();
-        QueryPlan planToBe = null;
+        List<QueryPlan> queryPlans;
         NamedTableNode tableNode = delete.getTable();
         String tableName = tableNode.getName().getTableName();
         String schemaName = tableNode.getName().getSchemaName();
@@ -201,6 +286,7 @@ public class DeleteCompiler {
         boolean noQueryReqd = false;
         boolean runOnServer = false;
         SelectStatement select = null;
+        Set<PTable> immutableIndex = Collections.emptySet();
         DeletingParallelIteratorFactory parallelIteratorFactory = null;
         while (true) {
             try {
@@ -211,7 +297,9 @@ public class DeleteCompiler {
                     throw new ReadOnlyTableException(table.getSchemaName().getString(),table.getTableName().getString());
                 }
                 
-                noQueryReqd = !hasLimit && !hasImmutableIndex(tableRefToBe);
+                immutableIndex = getNonDisabledImmutableIndexes(tableRefToBe);
+                boolean mayHaveImmutableIndexes = !immutableIndex.isEmpty();
+                noQueryReqd = !hasLimit;
                 runOnServer = isAutoCommit && noQueryReqd;
                 HintNode hint = delete.getHint();
                 if (runOnServer && !delete.getHint().hasHint(Hint.USE_INDEX_OVER_DATA_TABLE)) {
@@ -233,8 +321,20 @@ public class DeleteCompiler {
                         delete.getOrderBy(), delete.getLimit(),
                         delete.getBindCount(), false, false);
                 select = StatementNormalizer.normalize(select, resolver);
-                parallelIteratorFactory = hasLimit ? null : new DeletingParallelIteratorFactory(connection, tableRefToBe);
-                planToBe = new QueryOptimizer(services).optimize(statement, select, resolver, Collections.<PColumn>emptyList(), parallelIteratorFactory);
+                parallelIteratorFactory = hasLimit ? null : new DeletingParallelIteratorFactory(connection);
+                QueryOptimizer optimizer = new QueryOptimizer(services);
+                queryPlans = Lists.newArrayList(mayHaveImmutableIndexes
+                        ? optimizer.getApplicablePlans(statement, select, resolver, Collections.<PColumn>emptyList(), parallelIteratorFactory)
+                        : optimizer.getBestPlan(statement, select, resolver, Collections.<PColumn>emptyList(), parallelIteratorFactory));
+                if (mayHaveImmutableIndexes) { // FIXME: this is ugly
+                    // Lookup the table being deleted from in the cache, as it's possible that the
+                    // optimizer updated the cache if it found indexes that were out of date.
+                    // If the index was marked as disabled, it should not be in the list
+                    // of immutable indexes.
+                    table = connection.getMetaDataCache().getTable(new PTableKey(table.getTenantId(), table.getName().getString()));
+                    tableRefToBe.setTable(table);
+                    immutableIndex = getNonDisabledImmutableIndexes(tableRefToBe);
+                }
             } catch (MetaDataEntityNotFoundException e) {
                 // Catch column/column family not found exception, as our meta data may
                 // be out of sync. Update the cache once and retry if we were out of sync.
@@ -250,182 +350,223 @@ public class DeleteCompiler {
             }
             break;
         }
-        final TableRef tableRef = tableRefToBe;
-        final QueryPlan plan = planToBe;
-        if (!plan.getTableRef().equals(tableRef)) {
-            runOnServer = false;
-            noQueryReqd = false;
-        }
-        
-        final int maxSize = services.getProps().getInt(QueryServices.MAX_MUTATION_SIZE_ATTRIB,QueryServicesOptions.DEFAULT_MAX_MUTATION_SIZE);
- 
-        if (hasImmutableIndexWithKeyValueColumns(tableRef)) {
-            throw new SQLExceptionInfo.Builder(SQLExceptionCode.NO_DELETE_IF_IMMUTABLE_INDEX).setSchemaName(tableRef.getTable().getSchemaName().getString())
-            .setTableName(tableRef.getTable().getTableName().getString()).build().buildException();
+        final boolean hasImmutableIndexes = !immutableIndex.isEmpty();
+        // tableRefs is parallel with queryPlans
+        TableRef[] tableRefs = new TableRef[hasImmutableIndexes ? immutableIndex.size() : 1];
+        if (hasImmutableIndexes) {
+            int i = 0;
+            Iterator<QueryPlan> plans = queryPlans.iterator();
+            while (plans.hasNext()) {
+                QueryPlan plan = plans.next();
+                PTable table = plan.getTableRef().getTable();
+                if (table.getType() == PTableType.INDEX) { // index plans
+                    tableRefs[i++] = plan.getTableRef();
+                    immutableIndex.remove(table);
+                } else { // data plan
+                    /*
+                     * If we have immutable indexes that we need to maintain, don't execute the data plan
+                     * as we can save a query by piggy-backing on any of the other index queries, since the
+                     * PK columns that we need are always in each index row.
+                     */
+                    plans.remove();
+                }
+            }
+            /*
+             * If we have any immutable indexes remaining, then that means that the plan for that index got filtered out
+             * because it could not be executed. This would occur if a column in the where clause is not found in the
+             * immutable index.
+             */
+            if (!immutableIndex.isEmpty()) {
+                throw new SQLExceptionInfo.Builder(SQLExceptionCode.INVALID_FILTER_ON_IMMUTABLE_ROWS).setSchemaName(tableRefToBe.getTable().getSchemaName().getString())
+                .setTableName(tableRefToBe.getTable().getTableName().getString()).build().buildException();
+            }
         }
         
-        final StatementContext context = plan.getContext();
-        // If we're doing a query for a set of rows with no where clause, then we don't need to contact the server at all.
-        // A simple check of the none existence of a where clause in the parse node is not sufficient, as the where clause
-        // may have been optimized out. Instead, we check that there's a single SkipScanFilter
-        if (noQueryReqd
-                && (!context.getScan().hasFilter()
-                    || context.getScan().getFilter() instanceof SkipScanFilter)
-                && context.getScanRanges().isPointLookup()) {
-            return new MutationPlan() {
-
-                @Override
-                public ParameterMetaData getParameterMetaData() {
-                    return context.getBindManager().getParameterMetaData();
-                }
-
-                @Override
-                public MutationState execute() {
-                    // We have a point lookup, so we know we have a simple set of fully qualified
-                    // keys for our ranges
-                    ScanRanges ranges = context.getScanRanges();
-                    Iterator<KeyRange> iterator = ranges.getPointLookupKeyIterator(); 
-                    Map<ImmutableBytesPtr,Map<PColumn,byte[]>> mutation = Maps.newHashMapWithExpectedSize(ranges.getPointLookupCount());
-                    while (iterator.hasNext()) {
-                        mutation.put(new ImmutableBytesPtr(iterator.next().getLowerRange()), PRow.DELETE_MARKER);
+        // Make sure the first plan is targeting deletion from the data table
+        // In the case of an immutable index, we'll also delete from the index.
+        tableRefs[0] = tableRefToBe;
+        /*
+         * Create a mutationPlan for each queryPlan. One plan will be for the deletion of the rows
+         * from the data table, while the others will be for deleting rows from immutable indexes.
+         */
+        List<MutationPlan> mutationPlans = Lists.newArrayListWithExpectedSize(tableRefs.length);
+        for (int i = 0; i < tableRefs.length; i++) {
+            final TableRef tableRef = tableRefs[i];
+            final QueryPlan plan = queryPlans.get(i);
+            if (!plan.getTableRef().equals(tableRef)) {
+                runOnServer = false;
+                noQueryReqd = false; // FIXME: why set this to false in this case?
+            }
+            
+            final int maxSize = services.getProps().getInt(QueryServices.MAX_MUTATION_SIZE_ATTRIB,QueryServicesOptions.DEFAULT_MAX_MUTATION_SIZE);
+     
+            final StatementContext context = plan.getContext();
+            // If we're doing a query for a set of rows with no where clause, then we don't need to contact the server at all.
+            // A simple check of the none existence of a where clause in the parse node is not sufficient, as the where clause
+            // may have been optimized out. Instead, we check that there's a single SkipScanFilter
+            if (noQueryReqd
+                    && (!context.getScan().hasFilter()
+                        || context.getScan().getFilter() instanceof SkipScanFilter)
+                    && context.getScanRanges().isPointLookup()) {
+                mutationPlans.add(new MutationPlan() {
+    
+                    @Override
+                    public ParameterMetaData getParameterMetaData() {
+                        return context.getBindManager().getParameterMetaData();
                     }
-                    return new MutationState(tableRef, mutation, 0, maxSize, connection);
-                }
-
-                @Override
-                public ExplainPlan getExplainPlan() throws SQLException {
-                    return new ExplainPlan(Collections.singletonList("DELETE SINGLE ROW"));
-                }
-
-                @Override
-                public PhoenixConnection getConnection() {
-                    return connection;
-                }
-
-                @Override
-                public StatementContext getContext() {
-                    return context;
-                }
-            };
-        } else if (runOnServer) {
-            // TODO: better abstraction
-            Scan scan = context.getScan();
-            scan.setAttribute(BaseScannerRegionObserver.DELETE_AGG, QueryConstants.TRUE);
-
-            // Build an ungrouped aggregate query: select COUNT(*) from <table> where <where>
-            // The coprocessor will delete each row returned from the scan
-            // Ignoring ORDER BY, since with auto commit on and no limit makes no difference
-            SelectStatement aggSelect = SelectStatement.create(SelectStatement.COUNT_ONE, delete.getHint());
-            final RowProjector projector = ProjectionCompiler.compile(context, aggSelect, GroupBy.EMPTY_GROUP_BY);
-            final QueryPlan aggPlan = new AggregatePlan(context, select, tableRef, projector, null, OrderBy.EMPTY_ORDER_BY, null, GroupBy.EMPTY_GROUP_BY, null);
-            return new MutationPlan() {
-
-                @Override
-                public PhoenixConnection getConnection() {
-                    return connection;
-                }
-
-                @Override
-                public ParameterMetaData getParameterMetaData() {
-                    return context.getBindManager().getParameterMetaData();
-                }
-
-                @Override
-                public StatementContext getContext() {
-                    return context;
-                }
-
-                @Override
-                public MutationState execute() throws SQLException {
-                    // TODO: share this block of code with UPSERT SELECT
-                    ImmutableBytesWritable ptr = context.getTempPtr();
-                    tableRef.getTable().getIndexMaintainers(ptr);
-                    ServerCache cache = null;
-                    try {
-                        if (ptr.getLength() > 0) {
-                            IndexMetaDataCacheClient client = new IndexMetaDataCacheClient(connection, tableRef);
-                            cache = client.addIndexMetadataCache(context.getScanRanges(), ptr);
-                            byte[] uuidValue = cache.getId();
-                            context.getScan().setAttribute(PhoenixIndexCodec.INDEX_UUID, uuidValue);
+    
+                    @Override
+                    public MutationState execute() {
+                        // We have a point lookup, so we know we have a simple set of fully qualified
+                        // keys for our ranges
+                        ScanRanges ranges = context.getScanRanges();
+                        Iterator<KeyRange> iterator = ranges.getPointLookupKeyIterator(); 
+                        Map<ImmutableBytesPtr,Map<PColumn,byte[]>> mutation = Maps.newHashMapWithExpectedSize(ranges.getPointLookupCount());
+                        while (iterator.hasNext()) {
+                            mutation.put(new ImmutableBytesPtr(iterator.next().getLowerRange()), PRow.DELETE_MARKER);
                         }
-                        ResultIterator iterator = aggPlan.iterator();
+                        return new MutationState(tableRef, mutation, 0, maxSize, connection);
+                    }
+    
+                    @Override
+                    public ExplainPlan getExplainPlan() throws SQLException {
+                        return new ExplainPlan(Collections.singletonList("DELETE SINGLE ROW"));
+                    }
+    
+                    @Override
+                    public PhoenixConnection getConnection() {
+                        return connection;
+                    }
+    
+                    @Override
+                    public StatementContext getContext() {
+                        return context;
+                    }
+                });
+            } else if (runOnServer) {
+                // TODO: better abstraction
+                Scan scan = context.getScan();
+                scan.setAttribute(BaseScannerRegionObserver.DELETE_AGG, QueryConstants.TRUE);
+    
+                // Build an ungrouped aggregate query: select COUNT(*) from <table> where <where>
+                // The coprocessor will delete each row returned from the scan
+                // Ignoring ORDER BY, since with auto commit on and no limit makes no difference
+                SelectStatement aggSelect = SelectStatement.create(SelectStatement.COUNT_ONE, delete.getHint());
+                final RowProjector projector = ProjectionCompiler.compile(context, aggSelect, GroupBy.EMPTY_GROUP_BY);
+                final QueryPlan aggPlan = new AggregatePlan(context, select, tableRef, projector, null, OrderBy.EMPTY_ORDER_BY, null, GroupBy.EMPTY_GROUP_BY, null);
+                mutationPlans.add(new MutationPlan() {
+    
+                    @Override
+                    public PhoenixConnection getConnection() {
+                        return connection;
+                    }
+    
+                    @Override
+                    public ParameterMetaData getParameterMetaData() {
+                        return context.getBindManager().getParameterMetaData();
+                    }
+    
+                    @Override
+                    public StatementContext getContext() {
+                        return context;
+                    }
+    
+                    @Override
+                    public MutationState execute() throws SQLException {
+                        // TODO: share this block of code with UPSERT SELECT
+                        ImmutableBytesWritable ptr = context.getTempPtr();
+                        tableRef.getTable().getIndexMaintainers(ptr);
+                        ServerCache cache = null;
                         try {
-                            Tuple row = iterator.next();
-                            final long mutationCount = (Long)projector.getColumnProjector(0).getValue(row, PDataType.LONG, ptr);
-                            return new MutationState(maxSize, connection) {
-                                @Override
-                                public long getUpdateCount() {
-                                    return mutationCount;
-                                }
-                            };
+                            if (ptr.getLength() > 0) {
+                                IndexMetaDataCacheClient client = new IndexMetaDataCacheClient(connection, tableRef);
+                                cache = client.addIndexMetadataCache(context.getScanRanges(), ptr);
+                                byte[] uuidValue = cache.getId();
+                                context.getScan().setAttribute(PhoenixIndexCodec.INDEX_UUID, uuidValue);
+                            }
+                            ResultIterator iterator = aggPlan.iterator();
+                            try {
+                                Tuple row = iterator.next();
+                                final long mutationCount = (Long)projector.getColumnProjector(0).getValue(row, PDataType.LONG, ptr);
+                                return new MutationState(maxSize, connection) {
+                                    @Override
+                                    public long getUpdateCount() {
+                                        return mutationCount;
+                                    }
+                                };
+                            } finally {
+                                iterator.close();
+                            }
                         } finally {
-                            iterator.close();
-                        }
-                    } finally {
-                        if (cache != null) {
-                            cache.close();
+                            if (cache != null) {
+                                cache.close();
+                            }
                         }
                     }
+    
+                    @Override
+                    public ExplainPlan getExplainPlan() throws SQLException {
+                        List<String> queryPlanSteps =  aggPlan.getExplainPlan().getPlanSteps();
+                        List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size()+1);
+                        planSteps.add("DELETE ROWS");
+                        planSteps.addAll(queryPlanSteps);
+                        return new ExplainPlan(planSteps);
+                    }
+                });
+            } else {
+                final boolean deleteFromImmutableIndexToo = hasImmutableIndexes && !plan.getTableRef().equals(tableRef);
+                if (parallelIteratorFactory != null) {
+                    parallelIteratorFactory.setRowProjector(plan.getProjector());
+                    parallelIteratorFactory.setTargetTableRef(tableRef);
+                    parallelIteratorFactory.setSourceTableRef(plan.getTableRef());
+                    parallelIteratorFactory.setIndexTargetTableRef(deleteFromImmutableIndexToo ? plan.getTableRef() : null);
                 }
-
-                @Override
-                public ExplainPlan getExplainPlan() throws SQLException {
-                    List<String> queryPlanSteps =  aggPlan.getExplainPlan().getPlanSteps();
-                    List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size()+1);
-                    planSteps.add("DELETE ROWS");
-                    planSteps.addAll(queryPlanSteps);
-                    return new ExplainPlan(planSteps);
-                }
-            };
-        } else {
-            if (parallelIteratorFactory != null) {
-                parallelIteratorFactory.setRowProjector(plan.getProjector());
-            }
-            return new MutationPlan() {
-
-                @Override
-                public PhoenixConnection getConnection() {
-                    return connection;
-                }
-
-                @Override
-                public ParameterMetaData getParameterMetaData() {
-                    return context.getBindManager().getParameterMetaData();
-                }
-
-                @Override
-                public StatementContext getContext() {
-                    return context;
-                }
-
-                @Override
-                public MutationState execute() throws SQLException {
-                    ResultIterator iterator = plan.iterator();
-                    if (!hasLimit) {
-                        Tuple tuple;
-                        long totalRowCount = 0;
-                        while ((tuple=iterator.next()) != null) {// Runs query
-                            KeyValue kv = tuple.getValue(0);
-                            totalRowCount += PDataType.LONG.getCodec().decodeLong(kv.getBuffer(), kv.getValueOffset(), SortOrder.getDefault());
+                mutationPlans.add( new MutationPlan() {
+    
+                    @Override
+                    public PhoenixConnection getConnection() {
+                        return connection;
+                    }
+    
+                    @Override
+                    public ParameterMetaData getParameterMetaData() {
+                        return context.getBindManager().getParameterMetaData();
+                    }
+    
+                    @Override
+                    public StatementContext getContext() {
+                        return context;
+                    }
+    
+                    @Override
+                    public MutationState execute() throws SQLException {
+                        ResultIterator iterator = plan.iterator();
+                        if (!hasLimit) {
+                            Tuple tuple;
+                            long totalRowCount = 0;
+                            while ((tuple=iterator.next()) != null) {// Runs query
+                                KeyValue kv = tuple.getValue(0);
+                                totalRowCount += PDataType.LONG.getCodec().decodeLong(kv.getBuffer(), kv.getValueOffset(), SortOrder.getDefault());
+                            }
+                            // Return total number of rows that have been delete. In the case of auto commit being off
+                            // the mutations will all be in the mutation state of the current connection.
+                            return new MutationState(maxSize, connection, totalRowCount);
+                        } else {
+                            return deleteRows(statement, tableRef, deleteFromImmutableIndexToo ? plan.getTableRef() : null, iterator, plan.getProjector(), plan.getTableRef());
                         }
-                        // Return total number of rows that have been delete. In the case of auto commit being off
-                        // the mutations will all be in the mutation state of the current connection.
-                        return new MutationState(maxSize, connection, totalRowCount);
-                    } else {
-                        return deleteRows(statement, tableRef, iterator, plan.getProjector());
                     }
-                }
-
-                @Override
-                public ExplainPlan getExplainPlan() throws SQLException {
-                    List<String> queryPlanSteps =  plan.getExplainPlan().getPlanSteps();
-                    List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size()+1);
-                    planSteps.add("DELETE ROWS");
-                    planSteps.addAll(queryPlanSteps);
-                    return new ExplainPlan(planSteps);
-                }
-            };
+    
+                    @Override
+                    public ExplainPlan getExplainPlan() throws SQLException {
+                        List<String> queryPlanSteps =  plan.getExplainPlan().getPlanSteps();
+                        List<String> planSteps = Lists.newArrayListWithExpectedSize(queryPlanSteps.size()+1);
+                        planSteps.add("DELETE ROWS");
+                        planSteps.addAll(queryPlanSteps);
+                        return new ExplainPlan(planSteps);
+                    }
+                });
+            }
         }
-       
+        return mutationPlans.size() == 1 ? mutationPlans.get(0) : new MultiDeleteMutationPlan(mutationPlans);
     }
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java
index df91b1d..6388b1a 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/MutatingParallelIteratorFactory.java
@@ -36,7 +36,6 @@ import org.apache.phoenix.query.ConnectionQueryServices;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.query.QueryServicesOptions;
 import org.apache.phoenix.schema.PDataType;
-import org.apache.phoenix.schema.TableRef;
 import org.apache.phoenix.schema.tuple.SingleKeyValueTuple;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.util.KeyValueUtil;
@@ -46,11 +45,9 @@ import org.apache.phoenix.util.KeyValueUtil;
  */
 public abstract class MutatingParallelIteratorFactory implements ParallelIteratorFactory {
     protected final PhoenixConnection connection;
-    protected final TableRef tableRef;
 
-    protected MutatingParallelIteratorFactory(PhoenixConnection connection, TableRef tableRef) {
+    protected MutatingParallelIteratorFactory(PhoenixConnection connection) {
         this.connection = connection;
-        this.tableRef = tableRef;
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/compile/PostIndexDDLCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/PostIndexDDLCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/PostIndexDDLCompiler.java
index 5998e16..2ea42ce 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/PostIndexDDLCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/PostIndexDDLCompiler.java
@@ -24,6 +24,7 @@ import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.jdbc.PhoenixStatement;
 import org.apache.phoenix.schema.ColumnNotFoundException;
 import org.apache.phoenix.schema.PColumn;
+import org.apache.phoenix.schema.PColumnFamily;
 import org.apache.phoenix.schema.PTable;
 import org.apache.phoenix.schema.TableRef;
 import org.apache.phoenix.util.IndexUtil;
@@ -55,25 +56,33 @@ public class PostIndexDDLCompiler {
         //   that would allow the user to easily monitor the process of index creation.
         StringBuilder indexColumns = new StringBuilder();
         StringBuilder dataColumns = new StringBuilder();
-        List<PColumn> dataTableColumns = dataTableRef.getTable().getColumns();
+        List<PColumn> dataPKColumns = dataTableRef.getTable().getPKColumns();
         PTable dataTable = dataTableRef.getTable();
-        int nColumns = dataTable.getColumns().size();
+        int nPKColumns = dataPKColumns.size();
         boolean isSalted = dataTable.getBucketNum() != null;
         boolean isMultiTenant = connection.getTenantId() != null && dataTable.isMultiTenant();
-        boolean isSharedViewIndex = dataTable.getViewIndexId() != null;
-        int posOffset = (isSalted ? 1 : 0) + (isMultiTenant ? 1 : 0) + (isSharedViewIndex ? 1 : 0);
-        for (int i = posOffset; i < nColumns; i++) {
-            PColumn col = dataTableColumns.get(i);
-            String indexColName = IndexUtil.getIndexColumnName(col);
-            try {
-                indexTable.getColumn(indexColName);
-                if (col.getFamilyName() != null) {
-                    dataColumns.append('"').append(col.getFamilyName()).append("\".");
-                }
+        int posOffset = (isSalted ? 1 : 0) + (isMultiTenant ? 1 : 0);
+        for (int i = posOffset; i < nPKColumns; i++) {
+            PColumn col = dataPKColumns.get(i);
+            if (col.getViewConstant() == null) {
+                String indexColName = IndexUtil.getIndexColumnName(col);
                 dataColumns.append('"').append(col.getName()).append("\",");
                 indexColumns.append('"').append(indexColName).append("\",");
-            } catch (ColumnNotFoundException e) {
-                // Catch and ignore - means that this data column is not in the index
+            }
+        }
+        for (PColumnFamily family : dataTableRef.getTable().getColumnFamilies()) {
+            for (PColumn col : family.getColumns()) {
+                if (col.getViewConstant() == null) {
+                    String indexColName = IndexUtil.getIndexColumnName(col);
+                    try {
+                        indexTable.getColumn(indexColName);
+                        dataColumns.append('"').append(col.getFamilyName()).append("\".");
+                        dataColumns.append('"').append(col.getName()).append("\",");
+                        indexColumns.append('"').append(indexColName).append("\",");
+                    } catch (ColumnNotFoundException e) {
+                        // Catch and ignore - means that this data column is not in the index
+                    }
+                }
             }
         }
         dataColumns.setLength(dataColumns.length()-1);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
index 046e375..e789567 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
@@ -177,9 +177,11 @@ public class UpsertCompiler {
         private RowProjector projector;
         private int[] columnIndexes;
         private int[] pkSlotIndexes;
+        private final TableRef tableRef;
 
         private UpsertingParallelIteratorFactory (PhoenixConnection connection, TableRef tableRef) {
-            super(connection, tableRef);
+            super(connection);
+            this.tableRef = tableRef;
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
index a300611..a8775f9 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/exception/SQLExceptionCode.java
@@ -206,7 +206,7 @@ public enum SQLExceptionCode {
     SET_UNSUPPORTED_PROP_ON_ALTER_TABLE(1025, "42Y84", "Unsupported property set in ALTER TABLE command."),
     CANNOT_ADD_NOT_NULLABLE_COLUMN(1038, "42Y84", "Only nullable columns may be added for a pre-existing table."),
     NO_MUTABLE_INDEXES(1026, "42Y85", "Mutable secondary indexes are only supported for HBase version " + MetaDataUtil.decodeHBaseVersionAsString(PhoenixDatabaseMetaData.MUTABLE_SI_VERSION_THRESHOLD) + " and above."),
-    NO_DELETE_IF_IMMUTABLE_INDEX(1027, "42Y86", "Delete not allowed on a table with IMMUTABLE_ROW with non PK column in index."),
+    INVALID_FILTER_ON_IMMUTABLE_ROWS(1027, "42Y86", "All columns referenced in a WHERE clause must be available in every index for a table with immutable rows."),
     INVALID_INDEX_STATE_TRANSITION(1028, "42Y87", "Invalid index state transition."),
     INVALID_MUTABLE_INDEX_CONFIG(1029, "42Y88", "Mutable secondary indexes must have the " 
             + IndexManagementUtil.WAL_EDIT_CODEC_CLASS_KEY + " property set to " 

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/execute/MutationState.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/execute/MutationState.java b/phoenix-core/src/main/java/org/apache/phoenix/execute/MutationState.java
index 9864218..42ba931 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/execute/MutationState.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/execute/MutationState.java
@@ -19,6 +19,7 @@ package org.apache.phoenix.execute;
 
 import java.io.IOException;
 import java.sql.SQLException;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -45,6 +46,7 @@ import org.apache.phoenix.schema.MetaDataClient;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PRow;
 import org.apache.phoenix.schema.PTable;
+import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.TableRef;
 import org.apache.phoenix.util.ByteUtil;
 import org.apache.phoenix.util.IndexUtil;
@@ -72,7 +74,7 @@ public class MutationState implements SQLCloseable {
     private final long maxSize;
     private final ImmutableBytesPtr tempPtr = new ImmutableBytesPtr();
     private final Map<TableRef, Map<ImmutableBytesPtr,Map<PColumn,byte[]>>> mutations = Maps.newHashMapWithExpectedSize(3); // TODO: Sizing?
-    private final long sizeOffset;
+    private long sizeOffset;
     private int numRows = 0;
 
     public MutationState(int maxSize, PhoenixConnection connection) {
@@ -125,10 +127,14 @@ public class MutationState implements SQLCloseable {
         if (this == newMutation) { // Doesn't make sense
             return;
         }
+        this.sizeOffset += newMutation.sizeOffset;
         // Merge newMutation with this one, keeping state from newMutation for any overlaps
         for (Map.Entry<TableRef, Map<ImmutableBytesPtr,Map<PColumn,byte[]>>> entry : newMutation.mutations.entrySet()) {
             // Replace existing entries for the table with new entries
-            Map<ImmutableBytesPtr,Map<PColumn,byte[]>> existingRows = this.mutations.put(entry.getKey(), entry.getValue());
+            TableRef tableRef = entry.getKey();
+            PTable table = tableRef.getTable();
+            boolean isIndex = table.getType() == PTableType.INDEX;
+            Map<ImmutableBytesPtr,Map<PColumn,byte[]>> existingRows = this.mutations.put(tableRef, entry.getValue());
             if (existingRows != null) { // Rows for that table already exist
                 // Loop through new rows and replace existing with new
                 for (Map.Entry<ImmutableBytesPtr,Map<PColumn,byte[]>> rowEntry : entry.getValue().entrySet()) {
@@ -149,38 +155,52 @@ public class MutationState implements SQLCloseable {
                             }
                         }
                     } else {
-                        numRows++;
+                        if (!isIndex) { // Don't count index rows in row count
+                            numRows++;
+                        }
                     }
                 }
                 // Put the existing one back now that it's merged
                 this.mutations.put(entry.getKey(), existingRows);
             } else {
-                numRows += entry.getValue().size();
+                if (!isIndex) {
+                    numRows += entry.getValue().size();
+                }
             }
         }
         throwIfTooBig();
     }
     
     private Iterator<Pair<byte[],List<Mutation>>> addRowMutations(final TableRef tableRef, final Map<ImmutableBytesPtr, Map<PColumn, byte[]>> values, long timestamp, boolean includeMutableIndexes) {
+        final Iterator<PTable> indexes = // Only maintain tables with immutable rows through this client-side mechanism
+                (tableRef.getTable().isImmutableRows() || includeMutableIndexes) ? 
+                        IndexMaintainer.nonDisabledIndexIterator(tableRef.getTable().getIndexes().iterator()) : 
+                        Iterators.<PTable>emptyIterator();
         final List<Mutation> mutations = Lists.newArrayListWithExpectedSize(values.size());
+        final List<Mutation> mutationsPertainingToIndex = indexes.hasNext() ? Lists.<Mutation>newArrayListWithExpectedSize(values.size()) : null;
         Iterator<Map.Entry<ImmutableBytesPtr,Map<PColumn,byte[]>>> iterator = values.entrySet().iterator();
         while (iterator.hasNext()) {
             Map.Entry<ImmutableBytesPtr,Map<PColumn,byte[]>> rowEntry = iterator.next();
             ImmutableBytesPtr key = rowEntry.getKey();
             PRow row = tableRef.getTable().newRow(connection.getKeyValueBuilder(), timestamp, key);
+            List<Mutation> rowMutations, rowMutationsPertainingToIndex;
             if (rowEntry.getValue() == PRow.DELETE_MARKER) { // means delete
                 row.delete();
+                rowMutations = row.toRowMutations();
+                // Row deletes for index tables are processed by running a re-written query
+                // against the index table (as this allows for flexibility in being able to
+                // delete rows).
+                rowMutationsPertainingToIndex = Collections.emptyList();
             } else {
                 for (Map.Entry<PColumn,byte[]> valueEntry : rowEntry.getValue().entrySet()) {
                     row.setValue(valueEntry.getKey(), valueEntry.getValue());
                 }
+                rowMutations = row.toRowMutations();
+                rowMutationsPertainingToIndex = rowMutations;
             }
-            mutations.addAll(row.toRowMutations());
+            mutations.addAll(rowMutations);
+            if (mutationsPertainingToIndex != null) mutationsPertainingToIndex.addAll(rowMutationsPertainingToIndex);
         }
-        final Iterator<PTable> indexes = // Only maintain tables with immutable rows through this client-side mechanism
-                (tableRef.getTable().isImmutableRows() || includeMutableIndexes) ? 
-                        IndexMaintainer.nonDisabledIndexIterator(tableRef.getTable().getIndexes().iterator()) : 
-                        Iterators.<PTable>emptyIterator();
         return new Iterator<Pair<byte[],List<Mutation>>>() {
             boolean isFirst = true;
 
@@ -199,7 +219,7 @@ public class MutationState implements SQLCloseable {
                 List<Mutation> indexMutations;
                 try {
                     indexMutations =
-                            IndexUtil.generateIndexData(tableRef.getTable(), index, mutations,
+                            IndexUtil.generateIndexData(tableRef.getTable(), index, mutationsPertainingToIndex,
                                 tempPtr, connection.getKeyValueBuilder());
                 } catch (SQLException e) {
                     throw new IllegalDataException(e);
@@ -436,7 +456,9 @@ public class MutationState implements SQLCloseable {
                 } while (shouldRetry && retryCount++ < 1);
                 isDataTable = false;
             }
-            numRows -= entry.getValue().size();
+            if (tableRef.getTable().getType() != PTableType.INDEX) {
+                numRows -= entry.getValue().size();
+            }
             iterator.remove(); // Remove batches as we process them
         }
         assert(numRows==0);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
index 3f280ca..e662a3f 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixResultSet.java
@@ -723,6 +723,10 @@ public class PhoenixResultSet implements ResultSet, SQLCloseable, org.apache.pho
         throw new SQLFeatureNotSupportedException();
     }
 
+    public Tuple getCurrentRow() {
+        return currentRow;
+    }
+    
     @Override
     public boolean next() throws SQLException {
         checkOpen();

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
index f0c43b7..57fdde6 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/optimize/QueryOptimizer.java
@@ -77,18 +77,37 @@ public class QueryOptimizer {
     }
     
     public QueryPlan optimize(QueryPlan dataPlan, PhoenixStatement statement, List<? extends PDatum> targetColumns, ParallelIteratorFactory parallelIteratorFactory) throws SQLException {
+        List<QueryPlan>plans = getApplicablePlans(dataPlan, statement, targetColumns, parallelIteratorFactory, true);
+        return plans.get(0);
+    }
+    
+    public List<QueryPlan> getBestPlan(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver, List<? extends PDatum> targetColumns, ParallelIteratorFactory parallelIteratorFactory) throws SQLException {
+        return getApplicablePlans(statement, select, resolver, targetColumns, parallelIteratorFactory, true);
+    }
+    
+    public List<QueryPlan> getApplicablePlans(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver, List<? extends PDatum> targetColumns, ParallelIteratorFactory parallelIteratorFactory) throws SQLException {
+        return getApplicablePlans(statement, select, resolver, targetColumns, parallelIteratorFactory, false);
+    }
+    
+    private List<QueryPlan> getApplicablePlans(PhoenixStatement statement, SelectStatement select, ColumnResolver resolver, List<? extends PDatum> targetColumns, ParallelIteratorFactory parallelIteratorFactory, boolean stopAtBestPlan) throws SQLException {
+        QueryCompiler compiler = new QueryCompiler(statement, select, resolver, targetColumns, parallelIteratorFactory, new SequenceManager(statement));
+        QueryPlan dataPlan = compiler.compile();
+        return getApplicablePlans(dataPlan, statement, targetColumns, parallelIteratorFactory, false);
+    }
+    
+    private List<QueryPlan> getApplicablePlans(QueryPlan dataPlan, PhoenixStatement statement, List<? extends PDatum> targetColumns, ParallelIteratorFactory parallelIteratorFactory, boolean stopAtBestPlan) throws SQLException {
         SelectStatement select = (SelectStatement)dataPlan.getStatement();
         // Exit early if we have a point lookup as we can't get better than that
         if (!useIndexes 
                 || select.isJoin() 
                 || dataPlan.getContext().getResolver().getTables().size() > 1
-                || dataPlan.getContext().getScanRanges().isPointLookup()) {
-            return dataPlan;
+                || (dataPlan.getContext().getScanRanges().isPointLookup() && stopAtBestPlan)) {
+            return Collections.singletonList(dataPlan);
         }
         PTable dataTable = dataPlan.getTableRef().getTable();
         List<PTable>indexes = Lists.newArrayList(dataTable.getIndexes());
         if (indexes.isEmpty() || dataPlan.isDegenerate() || dataPlan.getTableRef().hasDynamicCols() || select.getHint().hasHint(Hint.NO_INDEX)) {
-            return dataPlan;
+            return Collections.singletonList(dataPlan);
         }
         
         // The targetColumns is set for UPSERT SELECT to ensure that the proper type conversion takes place.
@@ -109,20 +128,24 @@ public class QueryOptimizer {
         plans.add(dataPlan);
         QueryPlan hintedPlan = getHintedQueryPlan(statement, translatedIndexSelect, indexes, targetColumns, parallelIteratorFactory, plans);
         if (hintedPlan != null) {
-            return hintedPlan;
+            if (stopAtBestPlan) {
+                return Collections.singletonList(hintedPlan);
+            }
+            plans.add(0, hintedPlan);
         }
+        
         for (PTable index : indexes) {
             QueryPlan plan = addPlan(statement, translatedIndexSelect, index, targetColumns, parallelIteratorFactory, dataPlan);
             if (plan != null) {
                 // Query can't possibly return anything so just return this plan.
                 if (plan.isDegenerate()) {
-                    return plan;
+                    return Collections.singletonList(plan);
                 }
                 plans.add(plan);
             }
         }
         
-        return chooseBestPlan(select, plans);
+        return hintedPlan == null ? orderPlansBestToWorst(select, plans) : plans;
     }
     
     private static QueryPlan getHintedQueryPlan(PhoenixStatement statement, SelectStatement select, List<PTable> indexes, List<? extends PDatum> targetColumns, ParallelIteratorFactory parallelIteratorFactory, List<QueryPlan> plans) throws SQLException {
@@ -159,12 +182,13 @@ public class QueryOptimizer {
                 String indexName = indexHint.substring(startIndex, endIndex);
                 int indexPos = getIndexPosition(indexes, indexName);
                 if (indexPos >= 0) {
-                    // Hinted index is applicable, so return it. It'll be the plan at position 1, after the data plan
-                    QueryPlan plan = addPlan(statement, select, indexes.get(indexPos), targetColumns, parallelIteratorFactory, dataPlan);
+                    // Hinted index is applicable, so return it's index
+                    PTable index = indexes.get(indexPos);
+                    indexes.remove(indexPos);
+                    QueryPlan plan = addPlan(statement, select, index, targetColumns, parallelIteratorFactory, dataPlan);
                     if (plan != null) {
                         return plan;
                     }
-                    indexes.remove(indexPos);
                 }
                 startIndex = endIndex + 1;
             }
@@ -213,7 +237,7 @@ public class QueryOptimizer {
     }
     
     /**
-     * Choose the best plan among all the possible ones.
+     * Order the plans among all the possible ones from best to worst.
      * Since we don't keep stats yet, we use the following simple algorithm:
      * 1) If the query is a point lookup (i.e. we have a set of exact row keys), choose among those.
      * 2) If the query has an ORDER BY and a LIMIT, choose the plan that has all the ORDER BY expression
@@ -223,12 +247,16 @@ public class QueryOptimizer {
      *    b) the plan that preserves ordering for a group by.
      *    c) the data table plan
      * @param plans the list of candidate plans
+<<<<<<< HEAD
      * @return
+=======
+     * @return list of plans ordered from best to worst.
+>>>>>>> 6c47f8a... PHOENIX-619 Support DELETE over table with immutable index when possible
      */
-    private QueryPlan chooseBestPlan(SelectStatement select, List<QueryPlan> plans) {
+    private List<QueryPlan> orderPlansBestToWorst(SelectStatement select, List<QueryPlan> plans) {
         final QueryPlan dataPlan = plans.get(0);
         if (plans.size() == 1) {
-            return dataPlan;
+            return plans;
         }
         
         /**
@@ -312,8 +340,7 @@ public class QueryOptimizer {
             
         });
         
-        return candidates.get(0);
-        
+        return bestCandidates;
     }
 
     

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
index 478ce69..03ecbc9 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/query/ConnectionQueryServicesImpl.java
@@ -932,6 +932,7 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
             try {
                 desc = admin.getTableDescriptor(physicalIndexName);
                 if (Boolean.TRUE.equals(PDataType.BOOLEAN.toObject(desc.getValue(MetaDataUtil.IS_VIEW_INDEX_TABLE_PROP_BYTES)))) {
+                    this.tableStatsCache.invalidate(Bytes.toString(physicalIndexName));
                     final ReadOnlyProps props = this.getProps();
                     final boolean dropMetadata = props.getBoolean(DROP_METADATA_ATTRIB, DEFAULT_DROP_METADATA);
                     if (dropMetadata) {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java b/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java
index 6669b5b..58709d8 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/IndexUtil.java
@@ -151,6 +151,12 @@ public class IndexUtil {
            for (final Mutation dataMutation : dataMutations) {
                 long ts = MetaDataUtil.getClientTimeStamp(dataMutation);
                 ptr.set(dataMutation.getRow());
+                /*
+                 * We only need to generate the additional mutations for a Put for immutable indexes.
+                 * Deletes of rows are handled by running a re-written query against the index table,
+                 * and Deletes of column values should never be necessary, as you should never be
+                 * updating an existing row.
+                 */
                 if (dataMutation instanceof Put) {
                     // TODO: is this more efficient than looking in our mutation map
                     // using the key plus finding the PColumn?
@@ -183,14 +189,6 @@ public class IndexUtil {
                         
                     };
                     indexMutations.add(maintainer.buildUpdateMutation(kvBuilder, valueGetter, ptr, ts));
-                } else {
-                    // We can only generate the correct Delete if we have no KV columns in our index.
-                    // Perhaps it'd be best to ignore Delete mutations all together here, as this
-                    // gets triggered typically for an initial population where Delete markers make
-                    // little sense.
-                    if (maintainer.getIndexedColumns().isEmpty()) {
-                        indexMutations.add(maintainer.buildDeleteMutation(kvBuilder, ptr, ts));
-                    }
                 }
             }
             return indexMutations;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
index cdc1805..9a84bac 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/QueryCompilerTest.java
@@ -1166,10 +1166,20 @@ public class QueryCompilerTest extends BaseConnectionlessQueryTest {
             assertImmutableRows(conn, "T", true);
             conn.createStatement().execute(indexDDL);
             assertImmutableRows(conn, "T", true);
-            conn.createStatement().execute("DELETE FROM t");
+            conn.createStatement().execute("DELETE FROM t WHERE v2 = 'foo'");
             fail();
         } catch (SQLException e) {
-            assertEquals(SQLExceptionCode.NO_DELETE_IF_IMMUTABLE_INDEX.getErrorCode(), e.getErrorCode());
+            assertEquals(SQLExceptionCode.INVALID_FILTER_ON_IMMUTABLE_ROWS.getErrorCode(), e.getErrorCode());
+        }
+        // Test with one index having the referenced key value column, but one not having it.
+        // Still should fail
+        try {
+            indexDDL = "CREATE INDEX i2 ON t (v2)";
+            conn.createStatement().execute(indexDDL);
+            conn.createStatement().execute("DELETE FROM t WHERE v2 = 'foo'");
+            fail();
+        } catch (SQLException e) {
+            assertEquals(SQLExceptionCode.INVALID_FILTER_ON_IMMUTABLE_ROWS.getErrorCode(), e.getErrorCode());
         }
     }
     

http://git-wip-us.apache.org/repos/asf/phoenix/blob/88121cb0/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
index 94483b5..475ab65 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/compile/TenantSpecificViewIndexCompileTest.java
@@ -97,7 +97,7 @@ public class TenantSpecificViewIndexCompileTest extends BaseConnectionlessQueryT
         conn.createStatement().execute("CREATE VIEW v2(v3 VARCHAR) AS SELECT * FROM v WHERE k1 > 'a'");
         conn.createStatement().execute("CREATE INDEX i2 ON v2(v3) include(v2)");
         
-        // Confirm that a read-only view on an updatable view still optimizes out the read-olnly parts of the updatable view
+        // Confirm that a read-only view on an updatable view still optimizes out the read-only parts of the updatable view
         ResultSet rs = conn.createStatement().executeQuery("EXPLAIN SELECT v2 FROM v2 WHERE v3 > 'a' and k2 = 'a' ORDER BY v3,k2");
         assertEquals("CLIENT PARALLEL 1-WAY RANGE SCAN OVER _IDX_T ['me',-32767,'a'] - ['me',-32767,*]",
                 QueryUtil.getExplainPlan(rs));