Index: src/test/java/org/apache/hadoop/hbase/coprocessor/TestMasterObserver.java =================================================================== --- src/test/java/org/apache/hadoop/hbase/coprocessor/TestMasterObserver.java (revision 1166933) +++ src/test/java/org/apache/hadoop/hbase/coprocessor/TestMasterObserver.java (working copy) @@ -66,6 +66,8 @@ private boolean postDeleteTableCalled; private boolean preModifyTableCalled; private boolean postModifyTableCalled; + private boolean preAlterTableCalled; + private boolean postAlterTableCalled; private boolean preAddColumnCalled; private boolean postAddColumnCalled; private boolean preModifyColumnCalled; @@ -103,6 +105,8 @@ postDeleteTableCalled = false; preModifyTableCalled = false; postModifyTableCalled = false; + preAlterTableCalled = false; + postAlterTableCalled = false; preAddColumnCalled = false; postAddColumnCalled = false; preModifyColumnCalled = false; @@ -195,6 +199,35 @@ } @Override + public void preAlterTable( + ObserverContext env, byte[] tableName, + List columnsToAdd, + List columnsToMod, + List columnsToDel) throws IOException { + if (bypass) { + env.bypass(); + } + preAlterTableCalled = true; + } + + @Override + public void postAlterTable( + ObserverContext env, byte[] tableName, + List columnsToAdd, + List columnsToMod, + List columnsToDel) throws IOException { + postAlterTableCalled = true; + } + + public boolean wasAlterTableCalled() { + return preAlterTableCalled && postAlterTableCalled; + } + + public boolean preAlterTableCalledOnly() { + return preAlterTableCalled && !postAlterTableCalled; + } + + @Override public void preAddColumn(ObserverContext env, byte[] tableName, HColumnDescriptor column) throws IOException { if (bypass) { @@ -547,16 +580,18 @@ cp.wasModifyTableCalled()); // add a column family + cp.resetStates(); admin.addColumn(TEST_TABLE, new HColumnDescriptor(TEST_FAMILY2)); assertTrue("New column family shouldn't have been added to test table", - cp.preAddColumnCalledOnly()); + cp.preAlterTableCalledOnly() || cp.preAddColumnCalledOnly()); // modify a column family + cp.resetStates(); HColumnDescriptor hcd1 = new HColumnDescriptor(TEST_FAMILY2); hcd1.setMaxVersions(25); admin.modifyColumn(TEST_TABLE, hcd1); assertTrue("Second column family should be modified", - cp.preModifyColumnCalledOnly()); + cp.preAlterTableCalledOnly() || cp.preModifyColumnCalledOnly()); // delete table admin.deleteTable(TEST_TABLE); @@ -589,16 +624,18 @@ cp.wasModifyTableCalled()); // add a column family + cp.resetStates(); admin.addColumn(TEST_TABLE, new HColumnDescriptor(TEST_FAMILY2)); assertTrue("New column family should have been added to test table", - cp.wasAddColumnCalled()); + cp.wasAlterTableCalled() || cp.wasAddColumnCalled()); // modify a column family + cp.resetStates(); HColumnDescriptor hcd = new HColumnDescriptor(TEST_FAMILY2); hcd.setMaxVersions(25); admin.modifyColumn(TEST_TABLE, hcd); assertTrue("Second column family should be modified", - cp.wasModifyColumnCalled()); + cp.wasAlterTableCalled() || cp.wasModifyColumnCalled()); // enable assertFalse(cp.wasEnableTableCalled()); @@ -612,13 +649,13 @@ assertTrue(admin.isTableDisabled(TEST_TABLE)); // delete column - assertFalse("No column family deleted yet", cp.wasDeleteColumnCalled()); + cp.resetStates(); admin.deleteColumn(TEST_TABLE, TEST_FAMILY2); HTableDescriptor tableDesc = admin.getTableDescriptor(TEST_TABLE); assertNull("'"+Bytes.toString(TEST_FAMILY2)+"' should have been removed", tableDesc.getFamily(TEST_FAMILY2)); assertTrue("Coprocessor should have been called on column delete", - cp.wasDeleteColumnCalled()); + cp.wasAlterTableCalled() || cp.wasDeleteColumnCalled()); // delete table assertFalse("No table deleted yet", cp.wasDeleteTableCalled()); Index: src/main/java/org/apache/hadoop/hbase/executor/EventHandler.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/executor/EventHandler.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/executor/EventHandler.java (working copy) @@ -94,7 +94,7 @@ * convention: event type names specify the component from which the event * originated and then where its destined -- e.g. RS2ZK_ prefix means the * event came from a regionserver destined for zookeeper -- and then what - * the even is; e.g. REGION_OPENING. + * the event is; e.g. REGION_OPENING. * *

We give the enums indices so we can add types later and keep them * grouped together rather than have to add them always to the end as we @@ -127,6 +127,7 @@ C_M_DELETE_FAMILY (45), // Client asking Master to delete family of table C_M_MODIFY_FAMILY (46), // Client asking Master to modify family of table C_M_CREATE_TABLE (47), // Client asking Master to create a table + C_M_MULTI_FAMILY (48), // Client asking Master to perform a batch family operation on a table // Updates from master to ZK. This is done by the master and there is // nothing to process by either Master or RS @@ -145,6 +146,7 @@ this.equals(EventType.C_M_ADD_FAMILY) || this.equals(EventType.C_M_DELETE_FAMILY) || this.equals(EventType.C_M_MODIFY_FAMILY) || + this.equals(EventType.C_M_MULTI_FAMILY) || this.equals(EventType.C_M_MODIFY_TABLE) ); } Index: src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/client/HBaseAdmin.java (working copy) @@ -25,6 +25,7 @@ import java.lang.reflect.UndeclaredThrowableException; import java.net.SocketTimeoutException; import java.util.Arrays; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -863,6 +864,57 @@ } /** + * Batch alter a table. Only takes regions offline once and performs a single + * update to .META. + * Asynchronous operation. + * + * @param tableName name of the table to add column to + * @param columnAdditions column descriptors to add to the table + * @param columnModifications pairs of column names with new descriptors + * @param columnDeletions column names to delete from the table + * @throws IOException if a remote or network exception occurs + */ + public void alterTable(final String tableName, + List columnAdditions, + List columnModifications, + List columnDeletions) throws IOException { + // convert all of the strings to bytes and pass to the bytes method + List deletionsBytes = + new ArrayList(columnDeletions.size()); + for(String c : columnDeletions) { + deletionsBytes.add(Bytes.toBytes(c)); + } + alterTable(Bytes.toBytes(tableName), columnAdditions, columnModifications, + deletionsBytes); + } + + /** + * Batch alter a table. Only takes regions offline once and performs a single + * update to .META. + * Any of the three lists can be null, in which case those types of + * alterations will be ignored. + * Asynchronous operation. + * + * @param tableName name of the table to add column to + * @param columnAdditions column descriptors to add to the table + * @param columnModifications pairs of column names with new descriptors + * @param columnDeletions column names to delete from the table + * @throws IOException if a remote or network exception occurs + */ + public void alterTable(final byte [] tableName, + List columnAdditions, + List columnModifications, + List columnDeletions) throws IOException { + HTableDescriptor.isLegalTableName(tableName); + try { + getMaster().alterTable(tableName, columnAdditions, columnModifications, + columnDeletions); + } catch (RemoteException e) { + throw RemoteExceptionHandler.decodeRemoteException(e); + } + } + + /** * Get the status of alter command - indicates how many regions have received * the updated schema Asynchronous operation. * @@ -894,7 +946,7 @@ */ public void addColumn(final String tableName, HColumnDescriptor column) throws IOException { - addColumn(Bytes.toBytes(tableName), column); + alterTable(Bytes.toBytes(tableName), Arrays.asList(column), null, null); } /** @@ -907,12 +959,7 @@ */ public void addColumn(final byte [] tableName, HColumnDescriptor column) throws IOException { - HTableDescriptor.isLegalTableName(tableName); - try { - getMaster().addColumn(tableName, column); - } catch (RemoteException e) { - throw RemoteExceptionHandler.decodeRemoteException(e); - } + alterTable(tableName, Arrays.asList(column), null, null); } /** @@ -925,7 +972,8 @@ */ public void deleteColumn(final String tableName, final String columnName) throws IOException { - deleteColumn(Bytes.toBytes(tableName), Bytes.toBytes(columnName)); + alterTable(Bytes.toBytes(tableName), null, null, + Arrays.asList(Bytes.toBytes(columnName))); } /** @@ -938,11 +986,7 @@ */ public void deleteColumn(final byte [] tableName, final byte [] columnName) throws IOException { - try { - getMaster().deleteColumn(tableName, columnName); - } catch (RemoteException e) { - throw RemoteExceptionHandler.decodeRemoteException(e); - } + alterTable(tableName, null, null, Arrays.asList(columnName)); } /** @@ -953,12 +997,13 @@ * @param columnName name of column to be modified * @param descriptor new column descriptor to use * @throws IOException if a remote or network exception occurs - * @deprecated The columnName is redundant. Use {@link #addColumn(String, HColumnDescriptor)} + * @deprecated The columnName is redundant. Use {@link #modifyColumn(String, HColumnDescriptor)} */ public void modifyColumn(final String tableName, final String columnName, HColumnDescriptor descriptor) throws IOException { - modifyColumn(tableName, descriptor); + alterTable(Bytes.toBytes(tableName), null, Arrays.asList(descriptor), + null); } /** @@ -971,7 +1016,8 @@ */ public void modifyColumn(final String tableName, HColumnDescriptor descriptor) throws IOException { - modifyColumn(Bytes.toBytes(tableName), descriptor); + alterTable(Bytes.toBytes(tableName), null, Arrays.asList(descriptor), + null); } /** @@ -987,7 +1033,7 @@ public void modifyColumn(final byte [] tableName, final byte [] columnName, HColumnDescriptor descriptor) throws IOException { - modifyColumn(tableName, descriptor); + alterTable(tableName, null, Arrays.asList(descriptor), null); } /** @@ -1000,14 +1046,7 @@ */ public void modifyColumn(final byte [] tableName, HColumnDescriptor descriptor) throws IOException { - try { - getMaster().modifyColumn(tableName, descriptor); - } catch (RemoteException re) { - // Convert RE exceptions in here; client shouldn't have to deal with them, - // at least w/ the type of exceptions that come out of this method: - // TableNotFoundException, etc. - throw RemoteExceptionHandler.decodeRemoteException(re); - } + alterTable(tableName, null, Arrays.asList(descriptor), null); } /** Index: src/main/java/org/apache/hadoop/hbase/master/HMaster.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/master/HMaster.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/master/HMaster.java (working copy) @@ -71,6 +71,7 @@ import org.apache.hadoop.hbase.master.handler.TableAddFamilyHandler; import org.apache.hadoop.hbase.master.handler.TableDeleteFamilyHandler; import org.apache.hadoop.hbase.master.handler.TableModifyFamilyHandler; +import org.apache.hadoop.hbase.master.handler.TableMultiFamilyHandler; import org.apache.hadoop.hbase.master.handler.CreateTableHandler; import org.apache.hadoop.hbase.master.metrics.MasterMetrics; import org.apache.hadoop.hbase.monitoring.MemoryBoundedLogMessageBuffer; @@ -994,6 +995,24 @@ } } + public void alterTable(final byte [] tableName, + List columnAdditions, + List columnModifications, + List columnDeletions) throws IOException { + if (cpHost != null) { + if(cpHost.preAlterTable(tableName, columnAdditions, columnModifications, + columnDeletions)) { + return; + } + } + new TableMultiFamilyHandler(tableName, columnAdditions, columnModifications, + columnDeletions, this, this).process(); + if (cpHost != null) { + cpHost.postAlterTable(tableName, columnAdditions, columnModifications, + columnDeletions); + } + } + /** * Get the number of regions of the table that have been updated by the alter. * @@ -1008,41 +1027,17 @@ public void addColumn(byte [] tableName, HColumnDescriptor column) throws IOException { - if (cpHost != null) { - if (cpHost.preAddColumn(tableName, column)) { - return; - } - } - new TableAddFamilyHandler(tableName, column, this, this).process(); - if (cpHost != null) { - cpHost.postAddColumn(tableName, column); - } + alterTable(tableName, Arrays.asList(column), null, null); } public void modifyColumn(byte [] tableName, HColumnDescriptor descriptor) throws IOException { - if (cpHost != null) { - if (cpHost.preModifyColumn(tableName, descriptor)) { - return; - } - } - new TableModifyFamilyHandler(tableName, descriptor, this, this).process(); - if (cpHost != null) { - cpHost.postModifyColumn(tableName, descriptor); - } + alterTable(tableName, null, Arrays.asList(descriptor), null); } public void deleteColumn(final byte [] tableName, final byte [] c) throws IOException { - if (cpHost != null) { - if (cpHost.preDeleteColumn(tableName, c)) { - return; - } - } - new TableDeleteFamilyHandler(tableName, c, this, this).process(); - if (cpHost != null) { - cpHost.postDeleteColumn(tableName, c); - } + alterTable(tableName, null, null, Arrays.asList(c)); } public void enableTable(final byte [] tableName) throws IOException { Index: src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/master/MasterCoprocessorHost.java (working copy) @@ -25,6 +25,7 @@ import org.apache.hadoop.hbase.coprocessor.*; import java.io.IOException; +import java.util.List; /** * Provides the coprocessor framework and environment for master oriented @@ -152,6 +153,43 @@ } } + boolean preAlterTable(byte [] tableName, + List columnAdditions, + List columnModifications, + List columnDeletions) throws IOException { + boolean bypass = false; + ObserverContext ctx = null; + for (MasterEnvironment env: coprocessors) { + if (env.getInstance() instanceof MasterObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + ((MasterObserver)env.getInstance()).preAlterTable(ctx, tableName, + columnAdditions, columnModifications, columnDeletions); + bypass |= ctx.shouldBypass(); + if (ctx.shouldComplete()) { + break; + } + } + } + return bypass; + } + + void postAlterTable(byte [] tableName, + List columnAdditions, + List columnModifications, + List columnDeletions) throws IOException { + ObserverContext ctx = null; + for (MasterEnvironment env: coprocessors) { + if (env.getInstance() instanceof MasterObserver) { + ctx = ObserverContext.createAndPrepare(env, ctx); + ((MasterObserver)env.getInstance()).postAlterTable(ctx, tableName, + columnAdditions, columnModifications, columnDeletions); + if (ctx.shouldComplete()) { + break; + } + } + } + } + boolean preAddColumn(byte [] tableName, HColumnDescriptor column) throws IOException { boolean bypass = false; Index: src/main/java/org/apache/hadoop/hbase/master/handler/TableModifyFamilyHandler.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/master/handler/TableModifyFamilyHandler.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/master/handler/TableModifyFamilyHandler.java (working copy) @@ -32,9 +32,9 @@ import org.apache.hadoop.hbase.util.Bytes; /** - * Handles adding a new family to an existing table. + * Handles modifying an existing family in an existing table. */ -public class TableModifyFamilyHandler extends TableEventHandler { +public class TableModifyFamilyHandler extends TableFamilyHandler { private final HColumnDescriptor familyDesc; @@ -46,24 +46,17 @@ } @Override - protected void handleTableOperation(List regions) throws IOException { - AssignmentManager am = this.masterServices.getAssignmentManager(); - HTableDescriptor htd = this.masterServices.getTableDescriptors().get(Bytes.toString(tableName)); + protected void updateTableDescriptor(HTableDescriptor htd) + throws IOException { byte [] familyName = familyDesc.getName(); - if (htd == null) { - throw new IOException("Modify Family operation could not be completed as " + - "HTableDescritor is missing for table = " - + Bytes.toString(tableName)); - } if(!htd.hasFamily(familyName)) { throw new InvalidFamilyOperationException("Family '" + Bytes.toString(familyName) + "' doesn't exists so cannot be modified"); } - // Update table descriptor in HDFS - htd = this.masterServices.getMasterFileSystem().modifyColumn(tableName, familyDesc); - // Update in-memory descriptor cache - this.masterServices.getTableDescriptors().add(htd); + // Update table descriptor + htd.addFamily(familyDesc); } + @Override public String toString() { String name = "UnknownServerName"; Index: src/main/java/org/apache/hadoop/hbase/master/handler/TableAddFamilyHandler.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/master/handler/TableAddFamilyHandler.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/master/handler/TableAddFamilyHandler.java (working copy) @@ -33,7 +33,7 @@ /** * Handles adding a new family to an existing table. */ -public class TableAddFamilyHandler extends TableEventHandler { +public class TableAddFamilyHandler extends TableFamilyHandler { private final HColumnDescriptor familyDesc; @@ -44,27 +44,18 @@ } @Override - protected void handleTableOperation(List hris) + protected void updateTableDescriptor(HTableDescriptor htd) throws IOException { - HTableDescriptor htd = this.masterServices.getTableDescriptors(). - get(Bytes.toString(tableName)); byte [] familyName = familyDesc.getName(); - if (htd == null) { - throw new IOException("Add Family operation could not be completed as " + - "HTableDescritor is missing for table = " - + Bytes.toString(tableName)); - } if(htd.hasFamily(familyName)) { throw new InvalidFamilyOperationException( "Family '" + Bytes.toString(familyName) + "' already exists so " + "cannot be added"); } - // Update table descriptor in HDFS - htd = this.masterServices.getMasterFileSystem() - .addColumn(tableName, familyDesc); - // Update in-memory descriptor cache - this.masterServices.getTableDescriptors().add(htd); + // Update table descriptor + htd.addFamily(familyDesc); } + @Override public String toString() { String name = "UnknownServerName"; Index: src/main/java/org/apache/hadoop/hbase/master/handler/TableMultiFamilyHandler.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/master/handler/TableMultiFamilyHandler.java (revision 0) +++ src/main/java/org/apache/hadoop/hbase/master/handler/TableMultiFamilyHandler.java (revision 0) @@ -0,0 +1,79 @@ + /** + * Copyright 2010 The Apache Software Foundation + * + * 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.hadoop.hbase.master.handler; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.InvalidFamilyOperationException; +import org.apache.hadoop.hbase.Server; +import org.apache.hadoop.hbase.master.MasterServices; + +/** + * Handles adding a new family to an existing table. + */ +public class TableMultiFamilyHandler extends TableFamilyHandler { + + private final List operations; + + public TableMultiFamilyHandler(byte[] tableName, + List columnAdditions, + List columnModifications, + List columnDeletions, + Server server, final MasterServices masterServices) throws IOException { + super(EventType.C_M_MULTI_FAMILY, tableName, server, masterServices); + // convert the three separate lists into a single list of sub-operations + List argsAsOperations = + new ArrayList(); + if (columnAdditions != null) { + for (HColumnDescriptor newColumn : columnAdditions) { + argsAsOperations.add(new TableAddFamilyHandler( + tableName, newColumn, server, masterServices)); + } + } + if (columnModifications != null) { + for (HColumnDescriptor modColumn : columnModifications) { + argsAsOperations.add(new TableModifyFamilyHandler( + tableName, modColumn, server, masterServices)); + } + } + if (columnDeletions != null) { + for (byte [] columnToReap : columnDeletions) { + argsAsOperations.add(new TableDeleteFamilyHandler( + tableName, columnToReap, server, masterServices)); + } + } + this.operations = argsAsOperations; + } + + @Override + protected void updateTableDescriptor(HTableDescriptor desc) + throws IOException { + // just ask all of the sub-operations to update the descriptor + for (TableFamilyHandler op : operations) { + op.updateTableDescriptor(desc); + } + } +} + Index: src/main/java/org/apache/hadoop/hbase/master/handler/TableDeleteFamilyHandler.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/master/handler/TableDeleteFamilyHandler.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/master/handler/TableDeleteFamilyHandler.java (working copy) @@ -33,7 +33,7 @@ /** * Handles adding a new family to an existing table. */ -public class TableDeleteFamilyHandler extends TableEventHandler { +public class TableDeleteFamilyHandler extends TableFamilyHandler { private final byte [] familyName; @@ -44,24 +44,15 @@ } @Override - protected void handleTableOperation(List hris) throws IOException { - AssignmentManager am = this.masterServices.getAssignmentManager(); - HTableDescriptor htd = this.masterServices.getTableDescriptors().get(Bytes.toString(tableName)); - if (htd == null) { - throw new IOException("Add Family operation could not be completed as " + - "HTableDescritor is missing for table = " - + Bytes.toString(tableName)); - } + protected void updateTableDescriptor(HTableDescriptor htd) + throws IOException { if(!htd.hasFamily(familyName)) { throw new InvalidFamilyOperationException( "Family '" + Bytes.toString(familyName) + "' does not exist so " + "cannot be deleted"); } - // Update table descriptor in HDFS - htd = this.masterServices.getMasterFileSystem() - .deleteColumn(tableName, familyName); - // Update in-memory descriptor cache - this.masterServices.getTableDescriptors().add(htd); + // Update table descriptor + htd.removeFamily(familyName); } @Override Index: src/main/java/org/apache/hadoop/hbase/master/handler/TableFamilyHandler.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/master/handler/TableFamilyHandler.java (revision 0) +++ src/main/java/org/apache/hadoop/hbase/master/handler/TableFamilyHandler.java (revision 0) @@ -0,0 +1,68 @@ + /** + * Copyright 2010 The Apache Software Foundation + * + * 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.hadoop.hbase.master.handler; + +import java.io.IOException; +import java.util.List; + +import org.apache.hadoop.hbase.HColumnDescriptor; +import org.apache.hadoop.hbase.HRegionInfo; +import org.apache.hadoop.hbase.HTableDescriptor; +import org.apache.hadoop.hbase.InvalidFamilyOperationException; +import org.apache.hadoop.hbase.Server; +import org.apache.hadoop.hbase.catalog.MetaEditor; +import org.apache.hadoop.hbase.master.MasterServices; +import org.apache.hadoop.hbase.util.Bytes; + +/** + * Handles adding a new family to an existing table. + */ +public abstract class TableFamilyHandler extends TableEventHandler { + + public TableFamilyHandler(EventType eventType, byte [] tableName, + Server server, MasterServices masterServices) + throws IOException { + super(eventType, tableName, server, masterServices); + } + + /** + * Simply updates the given table descriptor with the relevant changes + * for the given column operation + * @param desc The descriptor that will be updated. + */ + protected abstract void updateTableDescriptor(HTableDescriptor desc) + throws IOException; + + @Override + protected void handleTableOperation(List hris) + throws IOException { + HTableDescriptor htd = this.masterServices.getTableDescriptors(). + get(Bytes.toString(tableName)); + if (htd == null) { + throw new IOException("Family operation could not be completed as " + + "HTableDescritor is missing for table = " + + Bytes.toString(tableName)); + } + // Update the HTD + updateTableDescriptor(htd); + // Update in-memory descriptor cache + this.masterServices.getTableDescriptors().add(htd); + } +} Index: src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/coprocessor/MasterObserver.java (working copy) @@ -23,6 +23,7 @@ import org.apache.hadoop.hbase.*; import java.io.IOException; +import java.util.List; /** * Defines coprocessor hooks for interacting with operations on the @@ -90,6 +91,24 @@ final byte[] tableName, HTableDescriptor htd) throws IOException; /** + * Called prior to modifying a table's schema. + */ + public void preAlterTable(ObserverContext ctx, + byte[] tableName, List columnAdditions, + List columnModifications, List columnDeletions) + throws IOException; + + /** + * Called after {@link org.apache.hadoop.hbase.master.HMaster} has processed + * the requested schema changes, but not necessarily after all of the table + * regions have been updated. + */ + public void postAlterTable(ObserverContext ctx, + byte[] tableName, List columnAdditions, + List columnModifications, List columnDeletions) + throws IOException; + + /** * Called prior to adding a new column family to the table. * @param ctx the environment to interact with the framework and master * @param tableName the name of the table Index: src/main/java/org/apache/hadoop/hbase/coprocessor/BaseMasterObserver.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/coprocessor/BaseMasterObserver.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/coprocessor/BaseMasterObserver.java (working copy) @@ -28,6 +28,7 @@ import org.apache.hadoop.hbase.UnknownRegionException; import java.io.IOException; +import java.util.List; public class BaseMasterObserver implements MasterObserver { @Override @@ -61,6 +62,20 @@ } @Override + public void preAlterTable(ObserverContext ctx, + byte[] tableName, List columnAdditions, + List columnModifications, List columnDeletions) + throws IOException { + } + + @Override + public void postAlterTable(ObserverContext ctx, + byte[] tableName, List columnAdditions, + List columnModifications, List columnDeletions) + throws IOException { + } + + @Override public void preAddColumn(ObserverContext ctx, byte[] tableName, HColumnDescriptor column) throws IOException { } Index: src/main/java/org/apache/hadoop/hbase/ipc/HMasterInterface.java =================================================================== --- src/main/java/org/apache/hadoop/hbase/ipc/HMasterInterface.java (revision 1166933) +++ src/main/java/org/apache/hadoop/hbase/ipc/HMasterInterface.java (working copy) @@ -73,6 +73,21 @@ public void deleteTable(final byte [] tableName) throws IOException; /** + * Batch adds, modifies, and deletes columns from the specified table. + * Any of the lists may be null, in which case those types of alterations + * will not occur. + * @param tableName table to modify + * @param columnAdditions column descriptors to add to the table + * @param columnModifications pairs of column names with new descriptors + * @param columnDeletions column names to delete from the table + * @throws IOException e + */ + public void alterTable(final byte [] tableName, + List columnAdditions, + List columnModifications, + List columnDeletions) throws IOException; + + /** * Used by the client to get the number of regions that have received the * updated schema * Index: src/main/ruby/hbase/admin.rb =================================================================== --- src/main/ruby/hbase/admin.rb (revision 1166933) +++ src/main/ruby/hbase/admin.rb (working copy) @@ -310,6 +310,10 @@ htd = @admin.getTableDescriptor(table_name.to_java_bytes) # Process all args + columnsToAdd = java.util.ArrayList.new() + columnsToMod = java.util.ArrayList.new() + columnsToDel = java.util.ArrayList.new() + makeBatchReq = false args.each do |arg| # Normalize args to support column name only alter specs arg = { NAME => arg } if arg.kind_of?(String) @@ -327,17 +331,11 @@ # If column already exist, then try to alter it. Create otherwise. if htd.hasFamily(column_name.to_java_bytes) - @admin.modifyColumn(table_name, column_name, descriptor) - if wait == true - puts "Updating all regions with the new schema..." - alter_status(table_name) - end + columnsToMod.add(descriptor) + makeBatchReq = true else - @admin.addColumn(table_name, descriptor) - if wait == true - puts "Updating all regions with the new schema..." - alter_status(table_name) - end + columnsToAdd.add(descriptor) + makeBatchReq = true end next end @@ -345,11 +343,8 @@ # Delete column family if method == "delete" raise(ArgumentError, "NAME parameter missing for delete method") unless arg[NAME] - @admin.deleteColumn(table_name, arg[NAME]) - if wait == true - puts "Updating all regions with the new schema..." - alter_status(table_name) - end + columnsToDel.add(arg[NAME]) + makeBatchReq = true next end @@ -370,6 +365,14 @@ # Unknown method raise ArgumentError, "Unknown method: #{method}" end + # now batch process alter requests + if makeBatchReq + @admin.alterTable(table_name, columnsToAdd, columnsToMod, columnsToDel) + if wait == true + puts "Updating all regions with the new schema..." + alter_status(table_name) + end + end end def status(format)