You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@phoenix.apache.org by gj...@apache.org on 2020/03/17 18:30:41 UTC

[phoenix] branch 4.x updated: PHOENIX-5317 Upserting rows into child views with pk fails when the base view has an index on it

This is an automated email from the ASF dual-hosted git repository.

gjacoby pushed a commit to branch 4.x
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/4.x by this push:
     new 3713421  PHOENIX-5317 Upserting rows into child views with pk fails when the base view has an index on it
3713421 is described below

commit 37134215a47b1b4ed8fd426c112b4f4e4e41e8cd
Author: Sandeep Guggilam <sg...@sandeepg-ltm.internal.salesforce.com>
AuthorDate: Sun Mar 8 14:10:53 2020 -0700

    PHOENIX-5317 Upserting rows into child views with pk fails when the base view has an index on it
    
    Signed-off-by: Geoffrey Jacoby <gj...@apache.org>
---
 .../phoenix/end2end/MetaDataEndpointImplIT.java    |  82 ++++++++++++-
 .../coprocessor/generated/ServerCachingProtos.java | 129 ++++++++++++++++++---
 .../org/apache/phoenix/index/IndexMaintainer.java  |  49 ++++++--
 .../src/main/ServerCachingService.proto            |   1 +
 4 files changed, 230 insertions(+), 31 deletions(-)

diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MetaDataEndpointImplIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MetaDataEndpointImplIT.java
index 21ab6f8..dca4b6b 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/MetaDataEndpointImplIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/MetaDataEndpointImplIT.java
@@ -11,18 +11,19 @@ import java.sql.SQLException;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Properties;
 
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.HTable;
-import org.apache.phoenix.util.TableViewFinderResult;
-import org.apache.phoenix.util.ViewUtil;
 import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.jdbc.PhoenixDatabaseMetaData;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.PTable;
 import org.apache.phoenix.schema.TableNotFoundException;
 import org.apache.phoenix.util.PhoenixRuntime;
+import org.apache.phoenix.util.TableViewFinderResult;
+import org.apache.phoenix.util.ViewUtil;
 import org.junit.Test;
 
 import com.google.common.base.Joiner;
@@ -113,6 +114,83 @@ public class MetaDataEndpointImplIT extends ParallelStatsDisabledIT {
         // now lets check and make sure the columns are correct
         assertColumnNamesEqual(PhoenixRuntime.getTable(conn, leftChild.toUpperCase()), "PK2", "V1", "V2", "CARRIER");
     }
+    
+    @Test
+    public void testUpsertIntoChildViewWithPKAndIndex() throws Exception {
+        String baseTable = generateUniqueName();
+        String view = generateUniqueName();
+        String childView = generateUniqueName();
+
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            String baseTableDDL = "CREATE TABLE IF NOT EXISTS " + baseTable + 
+                    " (TENANT_ID VARCHAR NOT NULL, KEY_PREFIX CHAR(3) NOT NULL, "
+                    + "V1 VARCHAR CONSTRAINT PK PRIMARY KEY(TENANT_ID, KEY_PREFIX)) "
+                    + "VERSIONS=1, IMMUTABLE_ROWS=TRUE";
+            conn.createStatement().execute(baseTableDDL);
+            String view1DDL = "CREATE VIEW IF NOT EXISTS " + view + 
+                    "(V2 VARCHAR NOT NULL,V3 BIGINT NOT NULL, "
+                    + "V4 VARCHAR CONSTRAINT PKVIEW PRIMARY KEY(V2, V3)) AS SELECT * FROM " 
+                    + baseTable + " WHERE KEY_PREFIX = '0CY'";
+            conn.createStatement().execute(view1DDL);
+
+            // Create an Index on the base view
+            String view1Index = generateUniqueName() + "_IDX";
+            conn.createStatement().execute("CREATE INDEX " + view1Index + 
+                " ON " + view + " (V2, V3) include (V1, V4)");
+
+            // Create a child view with primary key constraint
+            String childViewDDL = "CREATE VIEW IF NOT EXISTS " + childView 
+                    + " (V5 VARCHAR NOT NULL, V6 VARCHAR NOT NULL CONSTRAINT PK PRIMARY KEY "
+                    + "(V5, V6)) AS SELECT * FROM " + view;
+            conn.createStatement().execute(childViewDDL);
+
+            String upsert = "UPSERT INTO " + childView + " (TENANT_ID, V2, V3, V5, V6) "
+                    + "VALUES ('00D005000000000',  'zzzzz', 10, 'zzzzz', 'zzzzz')";
+            conn.createStatement().executeUpdate(upsert);
+            conn.commit();
+        }
+    }
+    
+    @Test
+    public void testUpsertIntoTenantChildViewWithPKAndIndex() throws Exception {
+        String baseTable = generateUniqueName();
+        String view = generateUniqueName();
+        String childView = generateUniqueName();
+        String tenantId = "TENANT";
+
+        try (Connection conn = DriverManager.getConnection(getUrl())) {
+            String baseTableDDL = "CREATE TABLE IF NOT EXISTS " + baseTable + 
+                    " (TENANT_ID VARCHAR NOT NULL, KEY_PREFIX CHAR(3) NOT NULL, "
+                    + "V1 VARCHAR CONSTRAINT PK PRIMARY KEY(TENANT_ID, KEY_PREFIX)) "
+                    + "MULTI_TENANT=TRUE, VERSIONS=1, IMMUTABLE_ROWS=TRUE";
+            conn.createStatement().execute(baseTableDDL);
+            String view1DDL = "CREATE VIEW IF NOT EXISTS " + view + 
+                    "(V2 VARCHAR NOT NULL,V3 BIGINT NOT NULL, "
+                    + "V4 VARCHAR CONSTRAINT PKVIEW PRIMARY KEY(V2, V3)) AS SELECT * FROM " 
+                    + baseTable + " WHERE KEY_PREFIX = '0CY'";
+            conn.createStatement().execute(view1DDL);
+
+            // Create an Index on the base view
+            String view1Index = generateUniqueName() + "_IDX";
+            conn.createStatement().execute("CREATE INDEX " + view1Index + 
+                " ON " + view + " (V2, V3) include (V1, V4)");
+
+            // Create a child view with primary key constraint owned by tenant
+            Properties tenantProps = new Properties();
+            tenantProps.setProperty(PhoenixRuntime.TENANT_ID_ATTRIB, tenantId);
+            try (Connection tenantConn = DriverManager.getConnection(getUrl(), tenantProps)) {
+                String childViewDDL = "CREATE VIEW IF NOT EXISTS " + childView 
+                        + " (V5 VARCHAR NOT NULL, V6 VARCHAR NOT NULL CONSTRAINT PK PRIMARY KEY "
+                        + "(V5, V6)) AS SELECT * FROM " + view;
+                conn.createStatement().execute(childViewDDL);
+            }
+            
+            String upsert = "UPSERT INTO " + childView + " (TENANT_ID, V2, V3, V5, V6) "
+                    + "VALUES ('00D005000000000',  'zzzzz', 10, 'zzzzz', 'zzzzz')";
+            conn.createStatement().executeUpdate(upsert);
+            conn.commit();
+        }
+    }
 
     @Test
     public void testDroppingADerivedColumn() throws Exception {
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/generated/ServerCachingProtos.java b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/generated/ServerCachingProtos.java
index 138be15..3fd01a2 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/generated/ServerCachingProtos.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/coprocessor/generated/ServerCachingProtos.java
@@ -2167,6 +2167,16 @@ public final class ServerCachingProtos {
      * <code>optional int32 viewIndexIdType = 22;</code>
      */
     int getViewIndexIdType();
+
+    // optional int32 indexDataColumnCount = 23 [default = -1];
+    /**
+     * <code>optional int32 indexDataColumnCount = 23 [default = -1];</code>
+     */
+    boolean hasIndexDataColumnCount();
+    /**
+     * <code>optional int32 indexDataColumnCount = 23 [default = -1];</code>
+     */
+    int getIndexDataColumnCount();
   }
   /**
    * Protobuf type {@code IndexMaintainer}
@@ -2365,6 +2375,11 @@ public final class ServerCachingProtos {
               viewIndexIdType_ = input.readInt32();
               break;
             }
+            case 184: {
+              bitField0_ |= 0x00020000;
+              indexDataColumnCount_ = input.readInt32();
+              break;
+            }
           }
         }
       } catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -2865,6 +2880,22 @@ public final class ServerCachingProtos {
       return viewIndexIdType_;
     }
 
+    // optional int32 indexDataColumnCount = 23 [default = -1];
+    public static final int INDEXDATACOLUMNCOUNT_FIELD_NUMBER = 23;
+    private int indexDataColumnCount_;
+    /**
+     * <code>optional int32 indexDataColumnCount = 23 [default = -1];</code>
+     */
+    public boolean hasIndexDataColumnCount() {
+      return ((bitField0_ & 0x00020000) == 0x00020000);
+    }
+    /**
+     * <code>optional int32 indexDataColumnCount = 23 [default = -1];</code>
+     */
+    public int getIndexDataColumnCount() {
+      return indexDataColumnCount_;
+    }
+
     private void initFields() {
       saltBuckets_ = 0;
       isMultiTenant_ = false;
@@ -2888,6 +2919,7 @@ public final class ServerCachingProtos {
       encodingScheme_ = 0;
       immutableStorageScheme_ = 0;
       viewIndexIdType_ = 0;
+      indexDataColumnCount_ = -1;
     }
     private byte memoizedIsInitialized = -1;
     public final boolean isInitialized() {
@@ -3051,6 +3083,9 @@ public final class ServerCachingProtos {
       if (((bitField0_ & 0x00010000) == 0x00010000)) {
         output.writeInt32(22, viewIndexIdType_);
       }
+      if (((bitField0_ & 0x00020000) == 0x00020000)) {
+        output.writeInt32(23, indexDataColumnCount_);
+      }
       getUnknownFields().writeTo(output);
     }
 
@@ -3153,6 +3188,10 @@ public final class ServerCachingProtos {
         size += com.google.protobuf.CodedOutputStream
           .computeInt32Size(22, viewIndexIdType_);
       }
+      if (((bitField0_ & 0x00020000) == 0x00020000)) {
+        size += com.google.protobuf.CodedOutputStream
+          .computeInt32Size(23, indexDataColumnCount_);
+      }
       size += getUnknownFields().getSerializedSize();
       memoizedSerializedSize = size;
       return size;
@@ -3271,6 +3310,11 @@ public final class ServerCachingProtos {
         result = result && (getViewIndexIdType()
             == other.getViewIndexIdType());
       }
+      result = result && (hasIndexDataColumnCount() == other.hasIndexDataColumnCount());
+      if (hasIndexDataColumnCount()) {
+        result = result && (getIndexDataColumnCount()
+            == other.getIndexDataColumnCount());
+      }
       result = result &&
           getUnknownFields().equals(other.getUnknownFields());
       return result;
@@ -3372,6 +3416,10 @@ public final class ServerCachingProtos {
         hash = (37 * hash) + VIEWINDEXIDTYPE_FIELD_NUMBER;
         hash = (53 * hash) + getViewIndexIdType();
       }
+      if (hasIndexDataColumnCount()) {
+        hash = (37 * hash) + INDEXDATACOLUMNCOUNT_FIELD_NUMBER;
+        hash = (53 * hash) + getIndexDataColumnCount();
+      }
       hash = (29 * hash) + getUnknownFields().hashCode();
       memoizedHashCode = hash;
       return hash;
@@ -3550,6 +3598,8 @@ public final class ServerCachingProtos {
         bitField0_ = (bitField0_ & ~0x00100000);
         viewIndexIdType_ = 0;
         bitField0_ = (bitField0_ & ~0x00200000);
+        indexDataColumnCount_ = -1;
+        bitField0_ = (bitField0_ & ~0x00400000);
         return this;
       }
 
@@ -3691,6 +3741,10 @@ public final class ServerCachingProtos {
           to_bitField0_ |= 0x00010000;
         }
         result.viewIndexIdType_ = viewIndexIdType_;
+        if (((from_bitField0_ & 0x00400000) == 0x00400000)) {
+          to_bitField0_ |= 0x00020000;
+        }
+        result.indexDataColumnCount_ = indexDataColumnCount_;
         result.bitField0_ = to_bitField0_;
         onBuilt();
         return result;
@@ -3872,6 +3926,9 @@ public final class ServerCachingProtos {
         if (other.hasViewIndexIdType()) {
           setViewIndexIdType(other.getViewIndexIdType());
         }
+        if (other.hasIndexDataColumnCount()) {
+          setIndexDataColumnCount(other.getIndexDataColumnCount());
+        }
         this.mergeUnknownFields(other.getUnknownFields());
         return this;
       }
@@ -5669,6 +5726,39 @@ public final class ServerCachingProtos {
         return this;
       }
 
+      // optional int32 indexDataColumnCount = 23 [default = -1];
+      private int indexDataColumnCount_ = -1;
+      /**
+       * <code>optional int32 indexDataColumnCount = 23 [default = -1];</code>
+       */
+      public boolean hasIndexDataColumnCount() {
+        return ((bitField0_ & 0x00400000) == 0x00400000);
+      }
+      /**
+       * <code>optional int32 indexDataColumnCount = 23 [default = -1];</code>
+       */
+      public int getIndexDataColumnCount() {
+        return indexDataColumnCount_;
+      }
+      /**
+       * <code>optional int32 indexDataColumnCount = 23 [default = -1];</code>
+       */
+      public Builder setIndexDataColumnCount(int value) {
+        bitField0_ |= 0x00400000;
+        indexDataColumnCount_ = value;
+        onChanged();
+        return this;
+      }
+      /**
+       * <code>optional int32 indexDataColumnCount = 23 [default = -1];</code>
+       */
+      public Builder clearIndexDataColumnCount() {
+        bitField0_ = (bitField0_ & ~0x00400000);
+        indexDataColumnCount_ = -1;
+        onChanged();
+        return this;
+      }
+
       // @@protoc_insertion_point(builder_scope:IndexMaintainer)
     }
 
@@ -8795,7 +8885,7 @@ public final class ServerCachingProtos {
       "ength\030\003 \002(\005\"4\n\017ColumnReference\022\016\n\006family" +
       "\030\001 \002(\014\022\021\n\tqualifier\030\002 \002(\014\"4\n\nColumnInfo\022" +
       "\022\n\nfamilyName\030\001 \001(\t\022\022\n\ncolumnName\030\002 \002(\t\"" +
-      "\337\005\n\017IndexMaintainer\022\023\n\013saltBuckets\030\001 \002(\005" +
+      "\201\006\n\017IndexMaintainer\022\023\n\013saltBuckets\030\001 \002(\005" +
       "\022\025\n\risMultiTenant\030\002 \002(\010\022\023\n\013viewIndexId\030\003" +
       " \001(\014\022(\n\016indexedColumns\030\004 \003(\0132\020.ColumnRef" +
       "erence\022 \n\030indexedColumnTypeOrdinal\030\005 \003(\005",
@@ -8813,23 +8903,24 @@ public final class ServerCachingProtos {
       "\timmutable\030\022 \002(\010\022&\n\021indexedColumnInfo\030\023 " +
       "\003(\0132\013.ColumnInfo\022\026\n\016encodingScheme\030\024 \002(\005" +
       "\022\036\n\026immutableStorageScheme\030\025 \002(\005\022\027\n\017view" +
-      "IndexIdType\030\026 \001(\005\"\370\001\n\025AddServerCacheRequ" +
-      "est\022\020\n\010tenantId\030\001 \001(\014\022\017\n\007cacheId\030\002 \002(\014\022)" +
-      "\n\010cachePtr\030\003 \002(\0132\027.ImmutableBytesWritabl" +
-      "e\022)\n\014cacheFactory\030\004 \002(\0132\023.ServerCacheFac" +
-      "tory\022\017\n\007txState\030\005 \001(\014\022\"\n\032hasProtoBufInde" +
-      "xMaintainer\030\006 \001(\010\022\025\n\rclientVersion\030\007 \001(\005",
-      "\022\032\n\022usePersistentCache\030\010 \001(\010\"(\n\026AddServe" +
-      "rCacheResponse\022\016\n\006return\030\001 \002(\010\"=\n\030Remove" +
-      "ServerCacheRequest\022\020\n\010tenantId\030\001 \001(\014\022\017\n\007" +
-      "cacheId\030\002 \002(\014\"+\n\031RemoveServerCacheRespon" +
-      "se\022\016\n\006return\030\001 \002(\0102\245\001\n\024ServerCachingServ" +
-      "ice\022A\n\016addServerCache\022\026.AddServerCacheRe" +
-      "quest\032\027.AddServerCacheResponse\022J\n\021remove" +
-      "ServerCache\022\031.RemoveServerCacheRequest\032\032" +
-      ".RemoveServerCacheResponseBG\n(org.apache" +
-      ".phoenix.coprocessor.generatedB\023ServerCa",
-      "chingProtosH\001\210\001\001\240\001\001"
+      "IndexIdType\030\026 \001(\005\022 \n\024indexDataColumnCoun" +
+      "t\030\027 \001(\005:\002-1\"\370\001\n\025AddServerCacheRequest\022\020\n" +
+      "\010tenantId\030\001 \001(\014\022\017\n\007cacheId\030\002 \002(\014\022)\n\010cach" +
+      "ePtr\030\003 \002(\0132\027.ImmutableBytesWritable\022)\n\014c" +
+      "acheFactory\030\004 \002(\0132\023.ServerCacheFactory\022\017" +
+      "\n\007txState\030\005 \001(\014\022\"\n\032hasProtoBufIndexMaint",
+      "ainer\030\006 \001(\010\022\025\n\rclientVersion\030\007 \001(\005\022\032\n\022us" +
+      "ePersistentCache\030\010 \001(\010\"(\n\026AddServerCache" +
+      "Response\022\016\n\006return\030\001 \002(\010\"=\n\030RemoveServer" +
+      "CacheRequest\022\020\n\010tenantId\030\001 \001(\014\022\017\n\007cacheI" +
+      "d\030\002 \002(\014\"+\n\031RemoveServerCacheResponse\022\016\n\006" +
+      "return\030\001 \002(\0102\245\001\n\024ServerCachingService\022A\n" +
+      "\016addServerCache\022\026.AddServerCacheRequest\032" +
+      "\027.AddServerCacheResponse\022J\n\021removeServer" +
+      "Cache\022\031.RemoveServerCacheRequest\032\032.Remov" +
+      "eServerCacheResponseBG\n(org.apache.phoen",
+      "ix.coprocessor.generatedB\023ServerCachingP" +
+      "rotosH\001\210\001\001\240\001\001"
     };
     com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
       new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -8859,7 +8950,7 @@ public final class ServerCachingProtos {
           internal_static_IndexMaintainer_fieldAccessorTable = new
             com.google.protobuf.GeneratedMessage.FieldAccessorTable(
               internal_static_IndexMaintainer_descriptor,
-              new java.lang.String[] { "SaltBuckets", "IsMultiTenant", "ViewIndexId", "IndexedColumns", "IndexedColumnTypeOrdinal", "DataTableColRefForCoveredColumns", "IndexTableColRefForCoveredColumns", "IsLocalIndex", "IndexTableName", "RowKeyOrderOptimizable", "DataTableEmptyKeyValueColFamily", "EmptyKeyValueColFamily", "IndexedExpressions", "RowKeyMetadata", "NumDataTableColFamilies", "IndexWalDisabled", "IndexRowKeyByteSize", "Immutable", "IndexedColumnInfo", "EncodingScheme", "Imm [...]
+              new java.lang.String[] { "SaltBuckets", "IsMultiTenant", "ViewIndexId", "IndexedColumns", "IndexedColumnTypeOrdinal", "DataTableColRefForCoveredColumns", "IndexTableColRefForCoveredColumns", "IsLocalIndex", "IndexTableName", "RowKeyOrderOptimizable", "DataTableEmptyKeyValueColFamily", "EmptyKeyValueColFamily", "IndexedExpressions", "RowKeyMetadata", "NumDataTableColFamilies", "IndexWalDisabled", "IndexRowKeyByteSize", "Immutable", "IndexedColumnInfo", "EncodingScheme", "Imm [...]
           internal_static_AddServerCacheRequest_descriptor =
             getDescriptor().getMessageTypes().get(4);
           internal_static_AddServerCacheRequest_fieldAccessorTable = new
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java b/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
index dba165b..8d6b54c 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/index/IndexMaintainer.java
@@ -108,6 +108,7 @@ import org.apache.phoenix.util.EncodedColumnsUtil;
 import org.apache.phoenix.util.ExpressionUtil;
 import org.apache.phoenix.util.IndexUtil;
 import org.apache.phoenix.util.MetaDataUtil;
+import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.SchemaUtil;
 import org.apache.phoenix.util.TransactionUtil;
 import org.apache.phoenix.util.TrustedByteArrayOutputStream;
@@ -340,6 +341,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
     private Set<ColumnReference> allColumns;
     // TODO remove this in the next major release
     private List<PDataType> indexedColumnTypes;
+    private int indexDataColumnCount;
     private RowKeyMetaData rowKeyMetaData;
     private byte[] indexTableName;
     private int nIndexSaltBuckets;
@@ -422,17 +424,35 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
             }
             indexedExpressionCount++;
         }
-        int indexPkColumnCount = this.dataRowKeySchema.getFieldCount() + indexedExpressionCount  - (this.isDataTableSalted ? 1 : 0) - (this.isMultiTenant ? 1 : 0);
-        this.rowKeyMetaData = newRowKeyMetaData(indexPkColumnCount);
-        BitSet bitSet = this.rowKeyMetaData.getViewConstantColumnBitSet();
 
         int dataPosOffset = (isDataTableSalted ? 1 : 0) + (this.isMultiTenant ? 1 : 0);
-        int nDataPKColumns = dataRowKeySchema.getFieldCount() - dataPosOffset;
+        
         // For indexes on views, we need to remember which data columns are "constants"
         // These are the values in a VIEW where clause. For these, we don't put them in the
         // index, as they're the same for every row in the index. The data table can be
         // either a VIEW or PROJECTED
         List<PColumn>dataPKColumns = dataTable.getPKColumns();
+        
+        this.indexDataColumnCount = dataPKColumns.size();
+        // We need to get the PK column for the table on which the index is created
+        if (!dataTable.getName().equals(index.getParentName())) {
+            try {
+                String tenantId = (index.getTenantId() != null) ? 
+                        index.getTenantId().getString() : null;
+                PTable indexTable = PhoenixRuntime.getTable(connection, 
+                        tenantId, index.getParentName().getString());
+                this.indexDataColumnCount = indexTable.getPKColumns().size();
+            } catch (SQLException e) {
+                throw new RuntimeException(e);
+            }
+        }
+        
+        int indexPkColumnCount = this.indexDataColumnCount + 
+                indexedExpressionCount  - (this.isDataTableSalted ? 1 : 0) - (this.isMultiTenant ? 1 : 0);
+        this.rowKeyMetaData = newRowKeyMetaData(indexPkColumnCount);
+        BitSet bitSet = this.rowKeyMetaData.getViewConstantColumnBitSet();
+
+        int nDataPKColumns = this.indexDataColumnCount - dataPosOffset;
         for (int i = dataPosOffset; i < dataPKColumns.size(); i++) {
             PColumn dataPKColumn = dataPKColumns.get(i);
             if (dataPKColumn.getViewConstant() != null) {
@@ -440,6 +460,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
                 nDataPKColumns--;
             }
         }
+        
         this.indexTableName = indexTableName;
         this.indexedColumnTypes = Lists.<PDataType>newArrayListWithExpectedSize(nIndexPKColumns-nDataPKColumns);
         this.indexedExpressions = Lists.newArrayListWithExpectedSize(nIndexPKColumns-nDataPKColumns);
@@ -620,7 +641,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
             }
             
             // Write index row key
-            for (int i = dataPosOffset; i < dataRowKeySchema.getFieldCount(); i++) {
+            for (int i = dataPosOffset; i < indexDataColumnCount; i++) {
                 Boolean hasValue=dataRowKeySchema.next(ptr, i, maxRowKeyOffset);
                 // Ignore view constants from the data table, as these
                 // don't need to appear in the index (as they're the
@@ -1374,6 +1395,11 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
             maintainer.indexedColumnTypes.add(PDataType.values()[typeOrdinal]);
         }
         maintainer.indexTableName = proto.getIndexTableName().toByteArray();
+        maintainer.indexDataColumnCount = dataTableRowKeySchema.getFieldCount();
+        if (proto.getIndexDataColumnCount() != -1) {
+            maintainer.indexDataColumnCount = proto.getIndexDataColumnCount();
+        }
+        
         maintainer.rowKeyOrderOptimizable = proto.getRowKeyOrderOptimizable();
         maintainer.dataEmptyKeyValueCF = proto.getDataTableEmptyKeyValueColFamily().toByteArray();
         ServerCachingProtos.ImmutableBytesWritable emptyKeyValueColFamily = proto.getEmptyKeyValueColFamily();
@@ -1509,6 +1535,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
             }
         }
         builder.setIsLocalIndex(maintainer.isLocalIndex);
+        builder.setIndexDataColumnCount(maintainer.indexDataColumnCount);
         builder.setIndexTableName(ByteStringer.wrap(maintainer.indexTableName));
         builder.setRowKeyOrderOptimizable(maintainer.rowKeyOrderOptimizable);
         builder.setDataTableEmptyKeyValueColFamily(ByteStringer.wrap(maintainer.dataEmptyKeyValueCF));
@@ -1624,7 +1651,7 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
         Arrays.fill(dataPkPosition, EXPRESSION_NOT_PRESENT);
         int numViewConstantColumns = 0;
         BitSet viewConstantColumnBitSet = rowKeyMetaData.getViewConstantColumnBitSet();
-        for (int i = dataPkOffset; i < dataRowKeySchema.getFieldCount(); i++) {
+        for (int i = dataPkOffset; i < indexDataColumnCount; i++) {
             if (!viewConstantColumnBitSet.get(i)) {
                 int indexPkPosition = rowKeyMetaData.getIndexPkPosition(i-dataPkOffset);
                 this.dataPkPosition[indexPkPosition] = i;
@@ -1660,11 +1687,12 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
     }
 
     private int getIndexPkColumnCount() {
-        return getIndexPkColumnCount(dataRowKeySchema, indexedExpressions.size(), isDataTableSalted, isMultiTenant);
+        return getIndexPkColumnCount(indexDataColumnCount, indexedExpressions.size(), 
+                isDataTableSalted, isMultiTenant);
     }
     
-    private static int getIndexPkColumnCount(RowKeySchema rowKeySchema, int numIndexExpressions, boolean isDataTableSalted, boolean isMultiTenant) {
-        return rowKeySchema.getFieldCount() + numIndexExpressions - (isDataTableSalted ? 1 : 0) - (isMultiTenant ? 1 : 0);
+    private static int getIndexPkColumnCount(int indexDataColumnCount, int numIndexExpressions, boolean isDataTableSalted, boolean isMultiTenant) {
+        return indexDataColumnCount + numIndexExpressions - (isDataTableSalted ? 1 : 0) - (isMultiTenant ? 1 : 0);
     }
     
     private RowKeyMetaData newRowKeyMetaData() {
@@ -1672,7 +1700,8 @@ public class IndexMaintainer implements Writable, Iterable<ColumnReference> {
     }
     
     private static RowKeyMetaData newRowKeyMetaData(IndexMaintainer i, RowKeySchema rowKeySchema, int numIndexExpressions, boolean isDataTableSalted, boolean isMultiTenant) {
-        int indexPkColumnCount = getIndexPkColumnCount(rowKeySchema, numIndexExpressions, isDataTableSalted, isMultiTenant);
+        int indexPkColumnCount = getIndexPkColumnCount(i.indexDataColumnCount, numIndexExpressions, 
+                isDataTableSalted, isMultiTenant);
         return indexPkColumnCount < 0xFF ? i.new ByteSizeRowKeyMetaData() : i.new IntSizedRowKeyMetaData();
     }
 
diff --git a/phoenix-protocol/src/main/ServerCachingService.proto b/phoenix-protocol/src/main/ServerCachingService.proto
index 5891d25..fbe151a 100644
--- a/phoenix-protocol/src/main/ServerCachingService.proto
+++ b/phoenix-protocol/src/main/ServerCachingService.proto
@@ -63,6 +63,7 @@ message IndexMaintainer {
   required int32 encodingScheme = 20;
   required int32 immutableStorageScheme = 21;
   optional int32 viewIndexIdType = 22 ;
+  optional int32 indexDataColumnCount = 23 [default = -1];
 }
 
 message AddServerCacheRequest {