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/04/26 06:36:04 UTC

git commit: PHOENIX-935 create local index table with the same split keys of user table (Rajeshbabu)

Repository: incubator-phoenix
Updated Branches:
  refs/heads/local-index 611045292 -> 492f77522


PHOENIX-935 create local index table with the same split keys of user table (Rajeshbabu)


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

Branch: refs/heads/local-index
Commit: 492f775227391d87128aa174035d4c3a60b9b42e
Parents: 6110452
Author: James Taylor <ja...@apache.org>
Authored: Fri Apr 25 21:36:03 2014 -0700
Committer: James Taylor <ja...@apache.org>
Committed: Fri Apr 25 21:36:03 2014 -0700

----------------------------------------------------------------------
 .../phoenix/end2end/index/LocalIndexIT.java     | 62 +++++++++++++++++-
 .../index/balancer/TestIndexLoadBalancer.java   | 43 +-----------
 .../hbase/index/IndexRegionSplitPolicy.java     | 32 +++++++++
 .../hbase/index/master/IndexMasterObserver.java | 69 ++++++++++++++++++++
 .../query/ConnectionQueryServicesImpl.java      | 47 ++++++++++++-
 .../apache/phoenix/schema/MetaDataClient.java   | 35 ++++++++--
 .../org/apache/phoenix/util/MetaDataUtil.java   | 29 ++++++++
 7 files changed, 267 insertions(+), 50 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/492f7752/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
index 4589259..4c3e8ab 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/LocalIndexIT.java
@@ -19,33 +19,43 @@ package org.apache.phoenix.end2end.index;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
 
 import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.SQLException;
 
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.client.HBaseAdmin;
+import org.apache.hadoop.hbase.client.HTable;
+import org.apache.phoenix.hbase.index.IndexRegionSplitPolicy;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.schema.PTable;
 import org.apache.phoenix.schema.PTable.IndexType;
 import org.apache.phoenix.schema.PTableKey;
+import org.apache.phoenix.schema.TableNotFoundException;
+import org.apache.phoenix.util.MetaDataUtil;
+import org.apache.phoenix.util.TestUtil;
 import org.junit.Test;
 
 public class LocalIndexIT extends BaseIndexIT {
-    private void createBaseTable(String tableName, Integer saltBuckets) throws SQLException {
+    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 ? "" : (",salt_buckets="+saltBuckets));
+                        + (saltBuckets == null || splits != null ? "" : (",salt_buckets=" + saltBuckets)
+                        + (saltBuckets != null || splits == null ? "" : ",splits=" + splits));
         conn.createStatement().execute(ddl);
         conn.close();
     }
     
     @Test
     public void testLocalIndexRoundTrip() throws Exception {
-        createBaseTable(DATA_TABLE_NAME, null);
+        createBaseTable(DATA_TABLE_NAME, null, null);
         Connection conn1 = DriverManager.getConnection(getUrl());
         Connection conn2 = DriverManager.getConnection(getUrl());
         conn1.createStatement().execute("CREATE LOCAL INDEX " + INDEX_TABLE_NAME + " ON " + DATA_TABLE_NAME + "(v1)");
@@ -54,5 +64,51 @@ public class LocalIndexIT extends BaseIndexIT {
         assertEquals(IndexType.LOCAL, localIndex.getIndexType());
         assertNotNull(localIndex.getViewIndexId());
     }
+    
+    @Test
+    public void testLocalIndexCreationWithSplitsShouldFail() throws Exception {
+        createBaseTable(DATA_TABLE_NAME, null, null);
+        Connection conn1 = DriverManager.getConnection(getUrl());
+        Connection conn2 = DriverManager.getConnection(getUrl());
+        try {
+            conn1.createStatement().execute("CREATE LOCAL INDEX " + INDEX_TABLE_NAME + " ON " + DATA_TABLE_NAME + "(v1)"+" splits={1,2,3}");
+            fail("Local index cannot be pre-split");
+        } catch (SQLException e) { }
+        try {
+            conn2.createStatement().executeQuery("SELECT * FROM " + DATA_TABLE_FULL_NAME).next();
+            conn2.unwrap(PhoenixConnection.class).getMetaDataCache().getTable(new PTableKey(null,INDEX_TABLE_NAME));
+            fail("Local index should be created.");
+        } catch (TableNotFoundException e) { }
+    }
+
+    @Test
+    public void testLocalIndexCreationWithSaltingShouldFail() throws Exception {
+        createBaseTable(DATA_TABLE_NAME, null, null);
+        Connection conn1 = DriverManager.getConnection(getUrl());
+        Connection conn2 = DriverManager.getConnection(getUrl());
+        try {
+            conn1.createStatement().execute("CREATE LOCAL INDEX " + INDEX_TABLE_NAME + " ON " + DATA_TABLE_NAME + "(v1)"+" salt_buckets=16");
+            fail("Local index cannot be salted.");
+        } catch (SQLException e) { }
+        try {
+            conn2.createStatement().executeQuery("SELECT * FROM " + DATA_TABLE_FULL_NAME).next();
+            conn2.unwrap(PhoenixConnection.class).getMetaDataCache().getTable(new PTableKey(null,INDEX_TABLE_NAME));
+            fail("Local index should not be created.");
+        } catch (TableNotFoundException e) { }
+    }
 
+    @Test
+    public void testLocalIndexTableRegionSplitPolicyAndSplitKeys() throws Exception {
+        createBaseTable(DATA_TABLE_NAME, null,"{1,2,3}");
+        Connection conn1 = DriverManager.getConnection(getUrl());
+        Connection conn2 = DriverManager.getConnection(getUrl());
+        conn1.createStatement().execute("CREATE LOCAL INDEX " + INDEX_TABLE_NAME + " ON " + DATA_TABLE_NAME + "(v1)");
+        conn2.createStatement().executeQuery("SELECT * FROM " + DATA_TABLE_FULL_NAME).next();
+        HBaseAdmin admin = driver.getConnectionQueryServices(getUrl(), TestUtil.TEST_PROPERTIES).getAdmin();
+        HTableDescriptor htd = admin.getTableDescriptor(TableName.valueOf(MetaDataUtil.getLocalIndexTableName(DATA_TABLE_NAME)));
+        assertEquals(IndexRegionSplitPolicy.class.getName(), htd.getValue(HTableDescriptor.SPLIT_POLICY));
+        HTable userTable = new HTable(admin.getConfiguration(),TableName.valueOf(DATA_TABLE_NAME));
+        HTable indexTable = new HTable(admin.getConfiguration(),TableName.valueOf(MetaDataUtil.getLocalIndexTableName(DATA_TABLE_NAME)));
+        assertEquals("Both user region and index table should have same split keys.", userTable.getStartKeys(), indexTable.getStartKeys());
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/492f7752/phoenix-core/src/it/java/org/apache/phoenix/hbase/index/balancer/TestIndexLoadBalancer.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/hbase/index/balancer/TestIndexLoadBalancer.java b/phoenix-core/src/it/java/org/apache/phoenix/hbase/index/balancer/TestIndexLoadBalancer.java
index fb0afa9..7a5d61c 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/hbase/index/balancer/TestIndexLoadBalancer.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/hbase/index/balancer/TestIndexLoadBalancer.java
@@ -39,10 +39,7 @@ import org.apache.hadoop.hbase.catalog.MetaReader;
 import org.apache.hadoop.hbase.client.HBaseAdmin;
 import org.apache.hadoop.hbase.client.HTable;
 import org.apache.hadoop.hbase.client.Put;
-import org.apache.hadoop.hbase.coprocessor.BaseMasterObserver;
 import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
-import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
-import org.apache.hadoop.hbase.coprocessor.ObserverContext;
 import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.LoadBalancer;
 import org.apache.hadoop.hbase.master.RegionStates;
@@ -55,6 +52,7 @@ import org.apache.hadoop.hbase.zookeeper.ZKAssign;
 import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
 import org.apache.phoenix.hbase.index.IndexTestingUtils;
 import org.apache.phoenix.hbase.index.Indexer;
+import org.apache.phoenix.hbase.index.master.IndexMasterObserver;
 import org.apache.phoenix.util.ConfigUtil;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -75,7 +73,7 @@ public class TestIndexLoadBalancer {
         final int NUM_RS = 4;
         Configuration conf = UTIL.getConfiguration();
         conf.setBoolean(HConstants.REGIONSERVER_INFO_PORT_AUTO, true);
-        conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, MockedMasterObserver.class.getName());
+        conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, IndexMasterObserver.class.getName());
         conf.setClass(HConstants.HBASE_MASTER_LOADBALANCER_CLASS, IndexLoadBalancer.class,
             LoadBalancer.class);
         IndexTestingUtils.setupConfig(conf);
@@ -491,41 +489,4 @@ public class TestIndexLoadBalancer {
         }
         return regionsColocated;
     }
-
-    public static class MockedMasterObserver extends BaseMasterObserver {
-        IndexLoadBalancer balancer = null;
-
-        @Override
-        public void preMasterInitialization(ObserverContext<MasterCoprocessorEnvironment> ctx)
-                throws IOException {
-            LoadBalancer loadBalancer =
-                    ctx.getEnvironment().getMasterServices().getAssignmentManager().getBalancer();
-            if (loadBalancer instanceof IndexLoadBalancer) {
-                balancer = (IndexLoadBalancer) loadBalancer;
-            }
-            super.preMasterInitialization(ctx);
-        }
-
-        @Override
-        public void preCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
-                HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
-            TableName userTableName = null;
-            if (balancer != null && desc.getValue(IndexLoadBalancer.PARENT_TABLE_KEY) != null) {
-                userTableName =
-                        TableName.valueOf(desc.getValue(IndexLoadBalancer.PARENT_TABLE_KEY));
-                balancer.addTablesToColocate(userTableName, desc.getTableName());
-            }
-            if (userTableName != null) balancer.populateRegionLocations(userTableName);
-            super.preCreateTableHandler(ctx, desc, regions);
-        }
-
-        @Override
-        public void postDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
-                TableName tableName) throws IOException {
-            if (balancer.isTableColocated(tableName)) {
-                balancer.removeTablesFromColocation(tableName);
-            }
-        }
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/492f7752/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/IndexRegionSplitPolicy.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/IndexRegionSplitPolicy.java b/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/IndexRegionSplitPolicy.java
new file mode 100644
index 0000000..b754ad9
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/IndexRegionSplitPolicy.java
@@ -0,0 +1,32 @@
+/*
+ * 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.hbase.index;
+
+import org.apache.hadoop.hbase.regionserver.RegionSplitPolicy;
+
+/**
+ * Split policy for index regions to avoid split from external requests.
+ */
+public class IndexRegionSplitPolicy extends RegionSplitPolicy {
+
+    @Override
+    protected boolean shouldSplit() {
+        return false;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/492f7752/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/master/IndexMasterObserver.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/master/IndexMasterObserver.java b/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/master/IndexMasterObserver.java
new file mode 100644
index 0000000..35a0fae
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/hbase/index/master/IndexMasterObserver.java
@@ -0,0 +1,69 @@
+/*
+ * 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.hbase.index.master;
+
+import java.io.IOException;
+
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HTableDescriptor;
+import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.coprocessor.BaseMasterObserver;
+import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
+import org.apache.hadoop.hbase.coprocessor.ObserverContext;
+import org.apache.hadoop.hbase.master.LoadBalancer;
+import org.apache.phoenix.hbase.index.balancer.IndexLoadBalancer;
+
+/**
+ * Defines of coprocessor hooks(to support secondary indexing) of operations on
+ * {@link org.apache.hadoop.hbase.master.HMaster} process.
+ */
+public class IndexMasterObserver extends BaseMasterObserver {
+    IndexLoadBalancer balancer = null;
+
+    @Override
+    public void preMasterInitialization(ObserverContext<MasterCoprocessorEnvironment> ctx)
+            throws IOException {
+        LoadBalancer loadBalancer =
+                ctx.getEnvironment().getMasterServices().getAssignmentManager().getBalancer();
+        if (loadBalancer instanceof IndexLoadBalancer) {
+            balancer = (IndexLoadBalancer) loadBalancer;
+        }
+        super.preMasterInitialization(ctx);
+    }
+
+    @Override
+    public void preCreateTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+            HTableDescriptor desc, HRegionInfo[] regions) throws IOException {
+        TableName userTableName = null;
+        if (balancer != null && desc.getValue(IndexLoadBalancer.PARENT_TABLE_KEY) != null) {
+            userTableName =
+                    TableName.valueOf(desc.getValue(IndexLoadBalancer.PARENT_TABLE_KEY));
+            balancer.addTablesToColocate(userTableName, desc.getTableName());
+        }
+        if (userTableName != null) balancer.populateRegionLocations(userTableName);
+        super.preCreateTableHandler(ctx, desc, regions);
+    }
+
+    @Override
+    public void postDeleteTableHandler(ObserverContext<MasterCoprocessorEnvironment> ctx,
+            TableName tableName) throws IOException {
+        if (balancer != null && balancer.isTableColocated(tableName)) {
+            balancer.removeTablesFromColocation(tableName);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/492f7752/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 bd7c9d4..6e0a6ca 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
@@ -120,6 +120,7 @@ import org.apache.phoenix.exception.PhoenixIOException;
 import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.exception.SQLExceptionInfo;
 import org.apache.phoenix.execute.MutationState;
+import org.apache.phoenix.hbase.index.IndexRegionSplitPolicy;
 import org.apache.phoenix.hbase.index.Indexer;
 import org.apache.phoenix.hbase.index.covered.CoveredColumnsIndexBuilder;
 import org.apache.phoenix.hbase.index.util.KeyValueBuilder;
@@ -788,6 +789,9 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
                 if (isMetaTable) {
                     newDesc.remove(HTableDescriptor.SPLIT_POLICY);
                 }
+                if (newDesc.getValue(MetaDataUtil.IS_LOCAL_INDEX_TABLE_PROP_BYTES) != null && Boolean.TRUE.equals(PDataType.BOOLEAN.toObject(newDesc.getValue(MetaDataUtil.IS_LOCAL_INDEX_TABLE_PROP_BYTES)))) {
+                    newDesc.setValue(HTableDescriptor.SPLIT_POLICY, IndexRegionSplitPolicy.class.getName());
+                }
                 try {
                     if (splits == null) {
                         admin.createTable(newDesc);
@@ -1004,6 +1008,40 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
         }
     }
 
+    private void ensureLocalIndexTableCreated(byte[] physicalTableName, Map<String,Object> tableProps, List<Pair<byte[],Map<String,Object>>> families, byte[][] splits, long timestamp) throws SQLException {
+        PTable table;
+        String parentTableName = Bytes.toString(physicalTableName, MetaDataUtil.LOCAL_INDEX_TABLE_PREFIX_BYTES.length, 
+            physicalTableName.length - MetaDataUtil.LOCAL_INDEX_TABLE_PREFIX_BYTES.length);
+        try {
+            table = latestMetaData.getTable(new PTableKey(PName.EMPTY_NAME, parentTableName));
+            if (table.getTimeStamp() >= timestamp) { // Table in cache is newer than client timestamp which shouldn't be the case
+                throw new TableNotFoundException(table.getSchemaName().getString(), table.getTableName().getString());
+            }
+        } catch (TableNotFoundException e) {
+            byte[] schemaName = Bytes.toBytes(SchemaUtil.getSchemaNameFromFullName(parentTableName));
+            byte[] tableName = Bytes.toBytes(SchemaUtil.getTableNameFromFullName(parentTableName));
+            MetaDataMutationResult result = this.getTable(null, schemaName, tableName, HConstants.LATEST_TIMESTAMP, timestamp);
+            table = result.getTable();
+            if (table == null) {
+                throw e;
+            }
+        }
+        ensureLocalIndexTableCreated(physicalTableName, tableProps, families, splits);
+    }
+
+    private void ensureLocalIndexTableCreated(byte[] physicalTableName, Map<String, Object> tableProps, List<Pair<byte[], Map<String, Object>>> families, byte[][] splits) throws SQLException, TableAlreadyExistsException {
+        tableProps.put(MetaDataUtil.IS_LOCAL_INDEX_TABLE_PROP_NAME, TRUE_BYTES_AS_STRING);
+        HTableDescriptor desc = ensureTableCreated(physicalTableName, PTableType.TABLE, tableProps, families, splits, false);
+        if (desc != null) {
+            if (!Boolean.TRUE.equals(PDataType.BOOLEAN.toObject(desc.getValue(MetaDataUtil.IS_LOCAL_INDEX_TABLE_PROP_BYTES)))) {
+                String fullTableName = Bytes.toString(physicalTableName);
+                throw new TableAlreadyExistsException(
+                        "Unable to create shared physical table for local indexes.",
+                        SchemaUtil.getSchemaNameFromFullName(fullTableName),
+                        SchemaUtil.getTableNameFromFullName(fullTableName));
+            }
+        }
+    }
 
     private boolean ensureViewIndexTableDropped(byte[] physicalTableName, long timestamp) throws SQLException {
         byte[] physicalIndexName = MetaDataUtil.getViewIndexPhysicalName(physicalTableName);
@@ -1049,6 +1087,8 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
         byte[] schemaBytes = rowKeyMetadata[PhoenixDatabaseMetaData.SCHEMA_NAME_INDEX];
         byte[] tableBytes = rowKeyMetadata[PhoenixDatabaseMetaData.TABLE_NAME_INDEX];
         byte[] tableName = physicalTableName != null ? physicalTableName : SchemaUtil.getTableNameAsBytes(schemaBytes, tableBytes);
+        boolean localIndexTable = Boolean.TRUE.equals(tableProps.remove(MetaDataUtil.IS_LOCAL_INDEX_TABLE_PROP_NAME));
+
         if ((tableType == PTableType.VIEW && physicalTableName != null) || (tableType != PTableType.VIEW && physicalTableName == null)) {
             // For views this will ensure that metadata already exists
             // For tables and indexes, this will create the metadata if it doesn't already exist
@@ -1059,7 +1099,11 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
             // Physical index table created up front for multi tenant
             // TODO: if viewIndexId is Short.MIN_VALUE, then we don't need to attempt to create it
             if (physicalTableName != null && !MetaDataUtil.isMultiTenant(m, kvBuilder, ptr)) {
-                ensureViewIndexTableCreated(tenantIdBytes.length == 0 ? null : PNameFactory.newName(tenantIdBytes), physicalTableName, MetaDataUtil.getClientTimeStamp(m));
+                if (localIndexTable) {
+                    ensureLocalIndexTableCreated(tableName, tableProps, families, splits, MetaDataUtil.getClientTimeStamp(m));
+                } else {
+                    ensureViewIndexTableCreated(tenantIdBytes.length == 0 ? null : PNameFactory.newName(tenantIdBytes), physicalTableName, MetaDataUtil.getClientTimeStamp(m));
+                }
             }
         } else if (tableType == PTableType.TABLE && MetaDataUtil.isMultiTenant(m, kvBuilder, ptr)) { // Create view index table up front for multi tenant tables
             ptr.set(QueryConstants.DEFAULT_COLUMN_FAMILY_BYTES);
@@ -1081,6 +1125,7 @@ public class ConnectionQueryServicesImpl extends DelegateQueryServices implement
                 familiesPlusDefault.add(new Pair<byte[],Map<String,Object>>(defaultCF,Collections.<String,Object>emptyMap()));
             }
             ensureViewIndexTableCreated(tableName, tableProps, familiesPlusDefault, MetaDataUtil.isSalted(m, kvBuilder, ptr) ? splits : null, MetaDataUtil.getClientTimeStamp(m));
+            ensureLocalIndexTableCreated(MetaDataUtil.getLocalIndexPhysicalName(tableName), tableProps, families, splits);
         }
         
         byte[] tableKey = SchemaUtil.getTableKey(tenantIdBytes, schemaBytes, tableBytes);

http://git-wip-us.apache.org/repos/asf/incubator-phoenix/blob/492f7752/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 6d02bad..557e79f 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
@@ -77,6 +77,7 @@ import java.util.Set;
 
 import org.apache.hadoop.hbase.HColumnDescriptor;
 import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HRegionLocation;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.client.Delete;
 import org.apache.hadoop.hbase.client.Mutation;
@@ -764,8 +765,13 @@ public class MetaDataClient {
                     saltBucketNum = parent.getBucketNum();
                     addSaltColumn = (saltBucketNum != null && indexType != IndexType.LOCAL);
                     defaultFamilyName = parent.getDefaultFamilyName() == null ? null : parent.getDefaultFamilyName().getString();
-                    // Set physical name of view index table
-                    physicalNames = Collections.singletonList(PNameFactory.newName(MetaDataUtil.getViewIndexPhysicalName(physicalName.getBytes())));
+                    if (indexType == IndexType.LOCAL) {
+                        // Set physical name of local index table
+                        physicalNames = Collections.singletonList(PNameFactory.newName(MetaDataUtil.getLocalIndexPhysicalName(physicalName.getBytes())));
+                    } else {
+                        // Set physical name of view index table
+                        physicalNames = Collections.singletonList(PNameFactory.newName(MetaDataUtil.getViewIndexPhysicalName(physicalName.getBytes())));
+                    }
                 }
                 
                 multiTenant = parent.isMultiTenant();
@@ -1170,8 +1176,14 @@ public class MetaDataClient {
              */
             Collections.reverse(tableMetaData);
             
-            splits = SchemaUtil.processSplits(splits, pkColumns, saltBucketNum, connection.getQueryServices().getProps().getBoolean(
+            if (parent != null && tableType == PTableType.INDEX && indexType == IndexType.LOCAL) {
+                tableProps.put(MetaDataUtil.PARENT_TABLE_KEY, parent.getPhysicalName().getString());
+                tableProps.put(MetaDataUtil.IS_LOCAL_INDEX_TABLE_PROP_NAME, Boolean.TRUE);
+                splits = getSplitKeys(connection.getQueryServices().getAllTableRegions(parent.getPhysicalName().getBytes()));
+            } else {
+                splits = SchemaUtil.processSplits(splits, pkColumns, saltBucketNum, connection.getQueryServices().getProps().getBoolean(
                     QueryServices.ROW_KEY_ORDER_SALTED_TABLE_ATTRIB, QueryServicesOptions.DEFAULT_ROW_KEY_ORDER_SALTED_TABLE));
+            }
             MetaDataMutationResult result = connection.getQueryServices().createTable(
                     tableMetaData, 
                     viewType == ViewType.MAPPED || indexId != null ? physicalNames.get(0).getBytes() : null,
@@ -1207,7 +1219,20 @@ public class MetaDataClient {
             connection.setAutoCommit(wasAutoCommit);
         }
     }
-    
+
+    private byte[][] getSplitKeys(List<HRegionLocation> allTableRegions) {
+        if(allTableRegions.size() == 1) return null;
+        byte[][] splitKeys = new byte[allTableRegions.size()-1][];
+        int i = 0;
+        for (HRegionLocation region : allTableRegions) {
+            if (region.getRegionInfo().getStartKey().length != 0) {
+                splitKeys[i] = region.getRegionInfo().getStartKey();
+                i++;
+            }
+        }
+        return splitKeys;
+    }
+
     private static boolean hasColumnWithSameNameAndFamily(Collection<PColumn> columns, PColumn column) {
         for (PColumn currColumn : columns) {
            if (Objects.equal(currColumn.getFamilyName(), column.getFamilyName()) &&
@@ -1317,7 +1342,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() || MetaDataUtil.hasViewIndexTable(connection, table.getPhysicalName()) || MetaDataUtil.hasLocalIndexTable(connection, table.getPhysicalName()))) {
                             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/incubator-phoenix/blob/492f7752/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 1af644f..8a3a190 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
@@ -48,9 +48,13 @@ import org.apache.phoenix.schema.TableNotFoundException;
 public class MetaDataUtil {
     public static final String VIEW_INDEX_TABLE_PREFIX = "_IDX_";
     public static final byte[] VIEW_INDEX_TABLE_PREFIX_BYTES = Bytes.toBytes(VIEW_INDEX_TABLE_PREFIX);
+    public static final String LOCAL_INDEX_TABLE_PREFIX = "_LOCAL_IDX_";
+    public static final byte[] LOCAL_INDEX_TABLE_PREFIX_BYTES = Bytes.toBytes(LOCAL_INDEX_TABLE_PREFIX);
     public static final String VIEW_INDEX_SEQUENCE_PREFIX = "_SEQ_";
     public static final byte[] VIEW_INDEX_SEQUENCE_PREFIX_BYTES = Bytes.toBytes(VIEW_INDEX_SEQUENCE_PREFIX);
     public static final String VIEW_INDEX_ID_COLUMN_NAME = "_INDEX_ID";
+    public static final String PARENT_TABLE_KEY = "PARENT_TABLE";
+    public static final byte[] PARENT_TABLE_KEY_BYTES = Bytes.toBytes("PARENT_TABLE");
     
     public static boolean areClientAndServerCompatible(long version) {
         // As of 3.0, we allow a client and server to differ for the minor version.
@@ -246,6 +250,18 @@ public class MetaDataUtil {
         return schemaName;
     }
 
+    public static byte[] getLocalIndexPhysicalName(byte[] physicalTableName) {
+        return ByteUtil.concat(LOCAL_INDEX_TABLE_PREFIX_BYTES, physicalTableName);
+    }
+    
+    public static String getLocalIndexTableName(String tableName) {
+        return LOCAL_INDEX_TABLE_PREFIX + tableName;
+    }
+    
+    public static String getLocalIndexSchemaName(String schemaName) {
+        return schemaName;
+    }  
+
     public static SequenceKey getViewIndexSequenceKey(String tenantId, PName physicalName) {
         // Create global sequence of the form: <prefixed base table name><tenant id>
         // rather than tenant-specific sequence, as it makes it much easier
@@ -274,6 +290,16 @@ public class MetaDataUtil {
         }
     }
     
+    public static boolean hasLocalIndexTable(PhoenixConnection connection, PName name) throws SQLException {
+        byte[] physicalIndexName = MetaDataUtil.getLocalIndexPhysicalName(name.getBytes());
+        try {
+            HTableDescriptor desc = connection.getQueryServices().getTableDescriptor(physicalIndexName);
+            return desc != null && Boolean.TRUE.equals(PDataType.BOOLEAN.toObject(desc.getValue(IS_LOCAL_INDEX_TABLE_PROP_BYTES)));
+        } catch (TableNotFoundException e) {
+            return false;
+        }
+    }
+
     public static void deleteViewIndexSequences(PhoenixConnection connection, PName name) throws SQLException {
         SequenceKey key = getViewIndexSequenceKey(null, name);
         connection.createStatement().executeUpdate("DELETE FROM " + PhoenixDatabaseMetaData.SEQUENCE_TABLE_NAME + 
@@ -284,4 +310,7 @@ public class MetaDataUtil {
 
     public static final String IS_VIEW_INDEX_TABLE_PROP_NAME = "IS_VIEW_INDEX_TABLE";
     public static final byte[] IS_VIEW_INDEX_TABLE_PROP_BYTES = Bytes.toBytes(IS_VIEW_INDEX_TABLE_PROP_NAME);
+
+    public static final String IS_LOCAL_INDEX_TABLE_PROP_NAME = "IS_LOCAL_INDEX_TABLE";
+    public static final byte[] IS_LOCAL_INDEX_TABLE_PROP_BYTES = Bytes.toBytes(IS_LOCAL_INDEX_TABLE_PROP_NAME);
 }