You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hbase.apache.org by st...@apache.org on 2012/11/07 22:00:33 UTC

svn commit: r1406800 - in /hbase/trunk/hbase-server/src: main/java/org/apache/hadoop/hbase/HTableDescriptor.java main/ruby/hbase/admin.rb main/ruby/shell/commands/alter.rb main/ruby/shell/commands/create.rb test/ruby/hbase/admin_test.rb

Author: stack
Date: Wed Nov  7 21:00:32 2012
New Revision: 1406800

URL: http://svn.apache.org/viewvc?rev=1406800&view=rev
Log:
HBASE-6894 Adding metadata to a table in the shell is both arcane and painful

Modified:
    hbase/trunk/hbase-server/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java
    hbase/trunk/hbase-server/src/main/ruby/hbase/admin.rb
    hbase/trunk/hbase-server/src/main/ruby/shell/commands/alter.rb
    hbase/trunk/hbase-server/src/main/ruby/shell/commands/create.rb
    hbase/trunk/hbase-server/src/test/ruby/hbase/admin_test.rb

Modified: hbase/trunk/hbase-server/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-server/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java?rev=1406800&r1=1406799&r2=1406800&view=diff
==============================================================================
--- hbase/trunk/hbase-server/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java (original)
+++ hbase/trunk/hbase-server/src/main/java/org/apache/hadoop/hbase/HTableDescriptor.java Wed Nov  7 21:00:32 2012
@@ -781,13 +781,15 @@ public class HTableDescriptor implements
     if (reservedKeys.isEmpty() && configKeys.isEmpty()) return s;
 
     // step 2: printing
-    s.append(", {METHOD => 'table_att'");
+    s.append(", {TABLE_ATTRIBUTES => {");
 
     // print all reserved keys first
+    boolean printCommaForAttr = false;
     for (ImmutableBytesWritable k : reservedKeys) {
       String key = Bytes.toString(k.get());
       String value = Bytes.toString(values.get(k).get());
-      s.append(", ");
+      if (printCommaForAttr) s.append(", ");
+      printCommaForAttr = true;
       s.append(key);
       s.append(" => ");
       s.append('\'').append(value).append('\'');
@@ -795,15 +797,16 @@ public class HTableDescriptor implements
 
     if (!configKeys.isEmpty()) {
       // print all non-reserved, advanced config keys as a separate subset
-      s.append(", ");
+      if (printCommaForAttr) s.append(", ");
+      printCommaForAttr = true;
       s.append(HConstants.CONFIG).append(" => ");
       s.append("{");
-      boolean printComma = false;
+      boolean printCommaForCfg = false;
       for (ImmutableBytesWritable k : configKeys) {
         String key = Bytes.toString(k.get());
         String value = Bytes.toString(values.get(k).get());
-        if (printComma) s.append(", ");
-        printComma = true;
+        if (printCommaForCfg) s.append(", ");
+        printCommaForCfg = true;
         s.append('\'').append(key).append('\'');
         s.append(" => ");
         s.append('\'').append(value).append('\'');
@@ -811,8 +814,7 @@ public class HTableDescriptor implements
       s.append("}");
     }
 
-    s.append('}'); // end METHOD
-
+    s.append("}}"); // end METHOD
     return s;
   }
 

Modified: hbase/trunk/hbase-server/src/main/ruby/hbase/admin.rb
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-server/src/main/ruby/hbase/admin.rb?rev=1406800&r1=1406799&r2=1406800&view=diff
==============================================================================
--- hbase/trunk/hbase-server/src/main/ruby/hbase/admin.rb (original)
+++ hbase/trunk/hbase-server/src/main/ruby/hbase/admin.rb Wed Nov  7 21:00:32 2012
@@ -186,9 +186,7 @@ module Hbase
 
       # Flatten params array
       args = args.flatten.compact
-
-      # Fail if no column families defined
-      raise(ArgumentError, "Table must have at least one column family") if args.empty?
+      has_columns = false
 
       # Start defining the table
       htd = org.apache.hadoop.hbase.HTableDescriptor.new(table_name)
@@ -199,69 +197,70 @@ module Hbase
         unless arg.kind_of?(String) || arg.kind_of?(Hash)
           raise(ArgumentError, "#{arg.class} of #{arg.inspect} is not of Hash or String type")
         end
-
-        if arg.kind_of?(String)
-          # the arg is a string, default action is to add a column to the table
+        
+        # First, handle all the cases where arg is a column family.
+        if arg.kind_of?(String) or arg.has_key?(NAME)
+          # If the arg is a string, default action is to add a column to the table.
+          # If arg has a name, it must also be a column descriptor.
           htd.addFamily(hcd(arg, htd))
-        else
-          # arg is a hash.  4 possibilities:
-          if (arg.has_key?(SPLITS) or arg.has_key?(SPLITS_FILE))
-            if arg.has_key?(SPLITS_FILE)
-              unless File.exist?(arg[SPLITS_FILE])
-                raise(ArgumentError, "Splits file #{arg[SPLITS_FILE]} doesn't exist")
-              end
-              arg[SPLITS] = []
-              File.foreach(arg[SPLITS_FILE]) do |line|
-                arg[SPLITS].push(line.strip())
-              end
-            end
-
-            splits = Java::byte[][arg[SPLITS].size].new
-            idx = 0
-            arg[SPLITS].each do |split|
-              splits[idx] = org.apache.hadoop.hbase.util.Bytes.toBytesBinary(split)
-              idx = idx + 1
-            end
-          elsif (arg.has_key?(NUMREGIONS) or arg.has_key?(SPLITALGO))
-            # (1) deprecated region pre-split API
-            raise(ArgumentError, "Column family configuration should be specified in a separate clause") if arg.has_key?(NAME)
-            raise(ArgumentError, "Number of regions must be specified") unless arg.has_key?(NUMREGIONS)
-            raise(ArgumentError, "Split algorithm must be specified") unless arg.has_key?(SPLITALGO)
-            raise(ArgumentError, "Number of regions must be greater than 1") unless arg[NUMREGIONS] > 1
-            num_regions = arg[NUMREGIONS]
-            split_algo = RegionSplitter.newSplitAlgoInstance(@conf, arg[SPLITALGO])
-            splits = split_algo.split(JInteger.valueOf(num_regions))
-          elsif (method = arg.delete(METHOD))
-            # (2) table_att modification
+          has_columns = true
+          next
+        end
+        
+        # Get rid of the "METHOD", which is deprecated for create.
+        # We'll do whatever it used to do below if it's table_att.
+        if (method = arg.delete(METHOD))
             raise(ArgumentError, "table_att is currently the only supported method") unless method == 'table_att'
-            raise(ArgumentError, "NUMREGIONS & SPLITALGO must both be specified") unless arg.has_key?(NUMREGIONS) == arg.has_key?(split_algo)
-            htd.setMaxFileSize(JLong.valueOf(arg[MAX_FILESIZE])) if arg[MAX_FILESIZE]
-            htd.setReadOnly(JBoolean.valueOf(arg[READONLY])) if arg[READONLY]
-            htd.setMemStoreFlushSize(JLong.valueOf(arg[MEMSTORE_FLUSHSIZE])) if arg[MEMSTORE_FLUSHSIZE]
-            htd.setDeferredLogFlush(JBoolean.valueOf(arg[DEFERRED_LOG_FLUSH])) if arg[DEFERRED_LOG_FLUSH]
-            htd.setValue(COMPRESSION_COMPACT, arg[COMPRESSION_COMPACT]) if arg[COMPRESSION_COMPACT]
-            if arg[NUMREGIONS]
-              raise(ArgumentError, "Number of regions must be greater than 1") unless arg[NUMREGIONS] > 1
-              num_regions = arg[NUMREGIONS]
-              split_algo = RegionSplitter.newSplitAlgoInstance(@conf, arg[SPLITALGO])
-              splits = split_algo.split(JInteger.valueOf(num_regions))
-            end
-            if arg[CONFIG]
-              raise(ArgumentError, "#{CONFIG} must be a Hash type") unless arg.kind_of?(Hash)
-              for k,v in arg[CONFIG]
-                v = v.to_s unless v.nil?
-                htd.setValue(k, v)
-              end
-            end
-          else
-            # (3) column family spec
-            descriptor = hcd(arg, htd)
-            htd.setValue(COMPRESSION_COMPACT, arg[COMPRESSION_COMPACT]) if arg[COMPRESSION_COMPACT]
-            htd.addFamily(hcd(arg, htd))
+        end
+        
+        # The hash is not a column family. Figure out what's in it.
+        # First, handle splits.
+        if arg.has_key?(SPLITS_FILE)
+          splits_file = arg.delete(SPLITS_FILE)
+          unless File.exist?(splits_file)
+            raise(ArgumentError, "Splits file #{splits_file} doesn't exist")
+          end
+          arg[SPLITS] = []
+          File.foreach(splits_file) do |line|
+            arg[SPLITS].push(line.strip())
           end
         end
-      end
 
+        if arg.has_key?(SPLITS) 
+          splits = Java::byte[][arg[SPLITS].size].new
+          idx = 0
+          arg.delete(SPLITS).each do |split|
+            splits[idx] = org.apache.hadoop.hbase.util.Bytes.toBytesBinary(split)
+            idx = idx + 1
+          end
+        elsif arg.has_key?(NUMREGIONS) or arg.has_key?(SPLITALGO)
+          # deprecated region pre-split API; if one of the above is specified, will be ignored.
+          raise(ArgumentError, "Number of regions must be specified") unless arg.has_key?(NUMREGIONS)
+          raise(ArgumentError, "Split algorithm must be specified") unless arg.has_key?(SPLITALGO)
+          raise(ArgumentError, "Number of regions must be greater than 1") unless arg[NUMREGIONS] > 1
+          num_regions = arg.delete(NUMREGIONS)
+          split_algo = RegionSplitter.newSplitAlgoInstance(@conf, arg.delete(SPLITALGO))
+          splits = split_algo.split(JInteger.valueOf(num_regions))
+        end
+        
+        # Done with splits; apply formerly-table_att parameters.
+        htd.setOwnerString(arg.delete(OWNER)) if arg[OWNER] 
+        htd.setMaxFileSize(JLong.valueOf(arg.delete(MAX_FILESIZE))) if arg[MAX_FILESIZE]
+        htd.setReadOnly(JBoolean.valueOf(arg.delete(READONLY))) if arg[READONLY]
+        htd.setMemStoreFlushSize(JLong.valueOf(arg.delete(MEMSTORE_FLUSHSIZE))) if arg[MEMSTORE_FLUSHSIZE]
+        htd.setDeferredLogFlush(JBoolean.valueOf(arg.delete(DEFERRED_LOG_FLUSH))) if arg[DEFERRED_LOG_FLUSH]
+        if arg[CONFIG]
+          apply_config(htd, arg.delete(CONFIG))
+        end
+        
+        arg.each_key do |ignored_key|
+          puts("An argument ignored (unknown or overridden): %s" % [ ignored_key ])
+        end
+      end
+      
+      # Fail if no column families defined
+      raise(ArgumentError, "Table must have at least one column family") if !has_columns
+      
       if splits.nil?
         # Perform the create table call
         @admin.createTable(htd)
@@ -368,133 +367,130 @@ module Hbase
 
       # Process all args
       args.each do |arg|
+      
+      
         # Normalize args to support column name only alter specs
         arg = { NAME => arg } if arg.kind_of?(String)
 
         # Normalize args to support shortcut delete syntax
         arg = { METHOD => 'delete', NAME => arg['delete'] } if arg['delete']
-
-        # No method parameter, try to use the args as a column definition
-        unless method = arg.delete(METHOD)
-          # Note that we handle owner here, and also below (see (2)) as part of the "METHOD => 'table_att'" table attributes.
-          # In other words, if OWNER is specified, then METHOD is set to table_att.
-          #   alter 'tablename', {OWNER => 'username'} (that is, METHOD => 'table_att' is not specified).
-          if arg[OWNER]
-            htd.setOwnerString(arg[OWNER])
-            @admin.modifyTable(table_name.to_java_bytes, htd)
-            return
-          end
-
+        
+        # There are 3 possible options.
+        # 1) Column family spec. Distinguished by having a NAME and no METHOD.
+        method = arg.delete(METHOD)
+        if method == nil and arg.has_key?(NAME)
           descriptor = hcd(arg, htd)
-
-          if arg[COMPRESSION_COMPACT]
-            descriptor.setValue(COMPRESSION_COMPACT, arg[COMPRESSION_COMPACT])
-          end
           column_name = descriptor.getNameAsString
 
           # If column already exist, then try to alter it. Create otherwise.
           if htd.hasFamily(column_name.to_java_bytes)
             @admin.modifyColumn(table_name, descriptor)
-            if wait == true
-              puts "Updating all regions with the new schema..."
-              alter_status(table_name)
-            end
           else
             @admin.addColumn(table_name, descriptor)
-            if wait == true
-              puts "Updating all regions with the new schema..."
-              alter_status(table_name)
-            end
           end
-          next
-        end
 
-        # 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
+          
+          # We bypass descriptor when adding column families; refresh it to apply other args correctly.
+          htd = @admin.getTableDescriptor(table_name.to_java_bytes)
           next
         end
-
-        # Change table attributes
-        if method == "table_att"
-          htd.setMaxFileSize(JLong.valueOf(arg[MAX_FILESIZE])) if arg[MAX_FILESIZE]
-          htd.setReadOnly(JBoolean.valueOf(arg[READONLY])) if arg[READONLY]
-          htd.setMemStoreFlushSize(JLong.valueOf(arg[MEMSTORE_FLUSHSIZE])) if arg[MEMSTORE_FLUSHSIZE]
-          htd.setDeferredLogFlush(JBoolean.valueOf(arg[DEFERRED_LOG_FLUSH])) if arg[DEFERRED_LOG_FLUSH]
-          # (2) Here, we handle the alternate syntax of ownership setting, where method => 'table_att' is specified.
-          htd.setOwnerString(arg[OWNER]) if arg[OWNER]
-
-          # set a coprocessor attribute
-          if arg.kind_of?(Hash)
-            arg.each do |key, value|
-              k = String.new(key) # prepare to strip
-              k.strip!
-
-              if (k =~ /coprocessor/i)
-                # validate coprocessor specs
-                v = String.new(value)
-                v.strip!
-                if !(v =~ /^([^\|]*)\|([^\|]+)\|[\s]*([\d]*)[\s]*(\|.*)?$/)
-                  raise ArgumentError, "Coprocessor value doesn't match spec: #{v}"
-                end
-
-                # generate a coprocessor ordinal by checking max id of existing cps
-                maxId = 0
-                htd.getValues().each do |k1, v1|
-                  attrName = org.apache.hadoop.hbase.util.Bytes.toString(k1.get())
-                  # a cp key is coprocessor$(\d)
-                  if (attrName =~ /coprocessor\$(\d+)/i)
-                    ids = attrName.scan(/coprocessor\$(\d+)/i)
-                    maxId = ids[0][0].to_i if ids[0][0].to_i > maxId
-                  end
-                end
-                maxId += 1
-                htd.setValue(k + "\$" + maxId.to_s, value)
-              end
+          
+        # 2) Method other than table_att, with some args.
+        name = arg.delete(NAME)
+        if method != nil and method != "table_att"
+          # Delete column family
+          if method == "delete"
+            raise(ArgumentError, "NAME parameter missing for delete method") unless name
+            @admin.deleteColumn(table_name, name)
+          # Unset table attributes
+          elsif method == "table_att_unset"
+            raise(ArgumentError, "NAME parameter missing for table_att_unset method") unless name
+            if (htd.getValue(name) == nil)
+              raise ArgumentError, "Can not find attribute: #{name}"
             end
+            htd.remove(name.to_java_bytes)
+            @admin.modifyTable(table_name.to_java_bytes, htd)
+          # Unknown method
+          else
+            raise ArgumentError, "Unknown method: #{method}"
           end
-
-          if arg[CONFIG]
-            raise(ArgumentError, "#{CONFIG} must be a Hash type") unless arg.kind_of?(Hash)
-            for k,v in arg[CONFIG]
-              v = v.to_s unless v.nil?
-              htd.setValue(k, v)
-            end
+          
+          arg.each_key do |unknown_key|
+            puts("Unknown argument ignored: %s" % [unknown_key])
           end
-          @admin.modifyTable(table_name.to_java_bytes, htd)
+          
           if wait == true
             puts "Updating all regions with the new schema..."
             alter_status(table_name)
           end
-          next
+          
+          if method == "delete"
+            # We bypass descriptor when deleting column families; refresh it to apply other args correctly.
+            htd = @admin.getTableDescriptor(table_name.to_java_bytes)
+          end
+          next          
         end
+        
+        # 3) Some args for the table, optionally with METHOD => table_att (deprecated)
+        raise(ArgumentError, "NAME argument in an unexpected place") if name
+        htd.setOwnerString(arg.delete(OWNER)) if arg[OWNER] 
+        apply_config(htd, arg.delete(CONFIG)) if arg[CONFIG]
+        htd.setMaxFileSize(JLong.valueOf(arg.delete(MAX_FILESIZE))) if arg[MAX_FILESIZE]
+        htd.setReadOnly(JBoolean.valueOf(arg.delete(READONLY))) if arg[READONLY]
+        htd.setMemStoreFlushSize(JLong.valueOf(arg.delete(MEMSTORE_FLUSHSIZE))) if arg[MEMSTORE_FLUSHSIZE]
+        htd.setDeferredLogFlush(JBoolean.valueOf(arg.delete(DEFERRED_LOG_FLUSH))) if arg[DEFERRED_LOG_FLUSH]
+
+        # set a coprocessor attribute
+        valid_coproc_keys = []
+        if arg.kind_of?(Hash)
+          arg.each do |key, value|
+            k = String.new(key) # prepare to strip
+            k.strip!
+
+            if (k =~ /coprocessor/i)
+              # validate coprocessor specs
+              v = String.new(value)
+              v.strip!
+              if !(v =~ /^([^\|]*)\|([^\|]+)\|[\s]*([\d]*)[\s]*(\|.*)?$/)
+                raise ArgumentError, "Coprocessor value doesn't match spec: #{v}"
+              end
 
-        # Unset table attributes
-        if method == "table_att_unset"
-          if arg.kind_of?(Hash)
-            if (!arg[NAME])
-              next
-            end
-            if (htd.getValue(arg[NAME]) == nil)
-              raise ArgumentError, "Can not find attribute: #{arg[NAME]}"
-            end
-            htd.remove(arg[NAME].to_java_bytes)
-            @admin.modifyTable(table_name.to_java_bytes, htd)
-            if wait == true
-              puts "Updating all regions with the new schema..."
-              alter_status(table_name)
+              # generate a coprocessor ordinal by checking max id of existing cps
+              maxId = 0
+              htd.getValues().each do |k1, v1|
+                attrName = org.apache.hadoop.hbase.util.Bytes.toString(k1.get())
+                # a cp key is coprocessor$(\d)
+                if (attrName =~ /coprocessor\$(\d+)/i)
+                  ids = attrName.scan(/coprocessor\$(\d+)/i)
+                  maxId = ids[0][0].to_i if ids[0][0].to_i > maxId
+                end
+              end
+              maxId += 1
+              htd.setValue(k + "\$" + maxId.to_s, value)
+              valid_coproc_keys << key
             end
           end
+          
+          valid_coproc_keys.each do |key|
+            arg.delete(key)
+          end
+
+          @admin.modifyTable(table_name.to_java_bytes, htd)
+                    
+          arg.each_key do |unknown_key|
+            puts("Unknown argument ignored: %s" % [unknown_key])
+          end
+          
+          if wait == true
+            puts "Updating all regions with the new schema..."
+            alter_status(table_name)
+          end
           next
         end
-
-        # Unknown method
-        raise ArgumentError, "Unknown method: #{method}"
       end
     end
 
@@ -555,7 +551,7 @@ module Hbase
     def exists?(table_name)
       @admin.tableExists(table_name)
     end
-
+    
     #----------------------------------------------------------------------------------------------
     # Is table enabled
     def enabled?(table_name)
@@ -574,24 +570,25 @@ module Hbase
       # String arg, single parameter constructor
       return org.apache.hadoop.hbase.HColumnDescriptor.new(arg) if arg.kind_of?(String)
 
-      raise(ArgumentError, "Column family #{arg} must have a name") unless name = arg[NAME]
+      raise(ArgumentError, "Column family #{arg} must have a name") unless name = arg.delete(NAME)
 
       family = htd.getFamily(name.to_java_bytes)
       # create it if it's a new family
       family ||= org.apache.hadoop.hbase.HColumnDescriptor.new(name.to_java_bytes)
 
-      family.setBlockCacheEnabled(JBoolean.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::BLOCKCACHE])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::BLOCKCACHE)
-      family.setScope(JInteger.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::REPLICATION_SCOPE])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::REPLICATION_SCOPE)
-      family.setInMemory(JBoolean.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::IN_MEMORY])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::IN_MEMORY)
-      family.setTimeToLive(JInteger.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::TTL])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::TTL)
-      family.setDataBlockEncoding(org.apache.hadoop.hbase.io.encoding.DataBlockEncoding.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::DATA_BLOCK_ENCODING])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::DATA_BLOCK_ENCODING)
-      family.setEncodeOnDisk(JBoolean.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::ENCODE_ON_DISK])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::ENCODE_ON_DISK)
-      family.setBlocksize(JInteger.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::BLOCKSIZE])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::BLOCKSIZE)
-      family.setMaxVersions(JInteger.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::VERSIONS])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::VERSIONS)
-      family.setMinVersions(JInteger.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::MIN_VERSIONS])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::MIN_VERSIONS)
-      family.setKeepDeletedCells(JBoolean.valueOf(arg[org.apache.hadoop.hbase.HColumnDescriptor::KEEP_DELETED_CELLS])) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::KEEP_DELETED_CELLS)
+      family.setBlockCacheEnabled(JBoolean.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::BLOCKCACHE))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::BLOCKCACHE)
+      family.setScope(JInteger.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::REPLICATION_SCOPE))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::REPLICATION_SCOPE)
+      family.setInMemory(JBoolean.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::IN_MEMORY))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::IN_MEMORY)
+      family.setTimeToLive(JInteger.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::TTL))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::TTL)
+      family.setDataBlockEncoding(org.apache.hadoop.hbase.io.encoding.DataBlockEncoding.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::DATA_BLOCK_ENCODING))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::DATA_BLOCK_ENCODING)
+      family.setEncodeOnDisk(JBoolean.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::ENCODE_ON_DISK))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::ENCODE_ON_DISK)
+      family.setBlocksize(JInteger.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::BLOCKSIZE))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::BLOCKSIZE)
+      family.setMaxVersions(JInteger.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::VERSIONS))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::VERSIONS)
+      family.setMinVersions(JInteger.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::MIN_VERSIONS))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::MIN_VERSIONS)
+      family.setKeepDeletedCells(JBoolean.valueOf(arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::KEEP_DELETED_CELLS))) if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::KEEP_DELETED_CELLS)
+      family.setValue(COMPRESSION_COMPACT, arg.delete(COMPRESSION_COMPACT)) if arg.include?(COMPRESSION_COMPACT)
       if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::BLOOMFILTER)
-        bloomtype = arg[org.apache.hadoop.hbase.HColumnDescriptor::BLOOMFILTER].upcase
+        bloomtype = arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::BLOOMFILTER).upcase
         unless org.apache.hadoop.hbase.regionserver.StoreFile::BloomType.constants.include?(bloomtype)      
           raise(ArgumentError, "BloomFilter type #{bloomtype} is not supported. Use one of " + org.apache.hadoop.hbase.regionserver.StoreFile::BloomType.constants.join(" ")) 
         else 
@@ -599,7 +596,7 @@ module Hbase
         end
       end
       if arg.include?(org.apache.hadoop.hbase.HColumnDescriptor::COMPRESSION)
-        compression = arg[org.apache.hadoop.hbase.HColumnDescriptor::COMPRESSION].upcase
+        compression = arg.delete(org.apache.hadoop.hbase.HColumnDescriptor::COMPRESSION).upcase
         unless org.apache.hadoop.hbase.io.hfile.Compression::Algorithm.constants.include?(compression)      
           raise(ArgumentError, "Compression #{compression} is not supported. Use one of " + org.apache.hadoop.hbase.io.hfile.Compression::Algorithm.constants.join(" ")) 
         else 
@@ -607,13 +604,14 @@ module Hbase
         end
       end
 
-      if arg[CONFIG]
-        raise(ArgumentError, "#{CONFIG} must be a Hash type") unless arg.kind_of?(Hash)
-        for k,v in arg[CONFIG]
-          v = v.to_s unless v.nil?
-          family.setValue(k, v)
-        end
+      if config = arg.delete(CONFIG)
+        apply_config(family, config)
       end
+      
+      arg.each_key do |unknown_key|
+        puts("Unknown argument ignored for column family %s: %s" % [name, unknown_key])
+      end
+      
       return family
     end
 
@@ -639,5 +637,15 @@ module Hbase
       put.add(org.apache.hadoop.hbase.HConstants::CATALOG_FAMILY, org.apache.hadoop.hbase.HConstants::REGIONINFO_QUALIFIER, org.apache.hadoop.hbase.util.Writables.getBytes(hri))
       meta.put(put)
     end
+    
+    # Apply config to table/column descriptor
+    def apply_config(descriptor, config)
+      raise(ArgumentError, "#{CONFIG} must be a Hash type") unless config.kind_of?(Hash)
+        for k,v in config
+          v = v.to_s unless v.nil?
+          descriptor.setValue(k, v)
+        end
+    end
+    
   end
 end

Modified: hbase/trunk/hbase-server/src/main/ruby/shell/commands/alter.rb
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-server/src/main/ruby/shell/commands/alter.rb?rev=1406800&r1=1406799&r2=1406800&view=diff
==============================================================================
--- hbase/trunk/hbase-server/src/main/ruby/shell/commands/alter.rb (original)
+++ hbase/trunk/hbase-server/src/main/ruby/shell/commands/alter.rb Wed Nov  7 21:00:32 2012
@@ -22,34 +22,35 @@ module Shell
     class Alter < Command
       def help
         return <<-EOF
-Alter column family schema;  pass table name and a dictionary
-specifying new column family schema. Dictionaries are described
-on the main help command output. Dictionary must include name
-of column family to alter. For example,
+Alter a table. Table must be disabled to be altered (see help 'disable').
+You can add/modify/delete column families, as well as change table 
+configuration. Column families work similarly to create; column family 
+spec can either be a name string, or a dictionary with NAME attribute.
+Dictionaries are described on the main help command output.
 
-To change or add the 'f1' column family in table 't1' from defaults
-to instead keep a maximum of 5 cell VERSIONS, do:
+For example, to change or add the 'f1' column family in table 't1' from 
+current value to keep a maximum of 5 cell VERSIONS, do:
 
   hbase> alter 't1', NAME => 'f1', VERSIONS => 5
 
-To delete the 'f1' column family in table 't1', do:
+You can operate on several column families:
 
-  hbase> alter 't1', NAME => 'f1', METHOD => 'delete'
+  hbase> alter 't1', 'f1', {NAME => 'f2', IN_MEMORY => true}, {NAME => 'f3', VERSIONS => 5}
 
-or a shorter version:
+To delete the 'f1' column family in table 't1', use one of:
 
+  hbase> alter 't1', NAME => 'f1', METHOD => 'delete'
   hbase> alter 't1', 'delete' => 'f1'
 
-You can also change table-scope attributes like MAX_FILESIZE
-MEMSTORE_FLUSHSIZE, READONLY, and DEFERRED_LOG_FLUSH.
-
-For example, to change the max size of a family to 128MB, do:
+You can also change table-scope attributes like MAX_FILESIZE, READONLY, 
+MEMSTORE_FLUSHSIZE, DEFERRED_LOG_FLUSH, etc. These can be put at the end;
+for example, to change the max size of a region to 128MB, do:
 
-  hbase> alter 't1', METHOD => 'table_att', MAX_FILESIZE => '134217728'
+  hbase> alter 't1', MAX_FILESIZE => '134217728'
 
 You can add a table coprocessor by setting a table coprocessor attribute:
 
-  hbase> alter 't1', METHOD => 'table_att',
+  hbase> alter 't1',
     'coprocessor'=>'hdfs:///foo.jar|com.foo.FooRegionObserver|1001|arg1=1,arg2=2'
 
 Since you can have multiple coprocessors configured for a table, a
@@ -69,7 +70,9 @@ You can also remove a table-scope attrib
 
 There could be more than one alteration in one command:
 
-  hbase> alter 't1', {NAME => 'f1'}, {NAME => 'f2', METHOD => 'delete'}
+  hbase> alter 't1', { NAME => 'f1', VERSIONS => 3 }, 
+   { MAX_FILESIZE => '134217728' }, { METHOD => 'delete', NAME => 'f2' },
+   OWNER => 'johndoe', CONFIG => { 'mykey' => 'myvalue' }
 EOF
       end
 

Modified: hbase/trunk/hbase-server/src/main/ruby/shell/commands/create.rb
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-server/src/main/ruby/shell/commands/create.rb?rev=1406800&r1=1406799&r2=1406800&view=diff
==============================================================================
--- hbase/trunk/hbase-server/src/main/ruby/shell/commands/create.rb (original)
+++ hbase/trunk/hbase-server/src/main/ruby/shell/commands/create.rb Wed Nov  7 21:00:32 2012
@@ -22,9 +22,11 @@ module Shell
     class Create < Command
       def help
         return <<-EOF
-Create table; pass table name, a dictionary of specifications per
-column family, and optionally a dictionary of table configuration.
-Dictionaries are described below in the GENERAL NOTES section.
+Creates a table. Pass a table name, and a set of column family
+specifications (at least one), and, optionally, table configuration.
+Column specification can be a simple string (name), or a dictionary
+(dictionaries are described below in main help output), necessarily 
+including NAME attribute. 
 Examples:
 
   hbase> create 't1', {NAME => 'f1', VERSIONS => 5}
@@ -32,18 +34,23 @@ Examples:
   hbase> # The above in shorthand would be the following:
   hbase> create 't1', 'f1', 'f2', 'f3'
   hbase> create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, BLOCKCACHE => true}
-  hbase> create 't1', 'f1', {SPLITS => ['10', '20', '30', '40']}
-  hbase> create 't1', 'f1', {SPLITS_FILE => 'splits.txt'}
+
+Table configuration options can be put at the end.
+Examples:
+
+  hbase> create 't1', 'f1', SPLITS => ['10', '20', '30', '40']
+  hbase> create 't1', 'f1', SPLITS_FILE => 'splits.txt', OWNER => 'johndoe'
+  hbase> create 't1', {NAME => 'f1', VERSIONS => 5}, CONFIG => { 'mykey' => 'myvalue' }
   hbase> # Optionally pre-split the table into NUMREGIONS, using
   hbase> # SPLITALGO ("HexStringSplit", "UniformSplit" or classname)
   hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
 
-  You can also keep around a reference to the created table:
+You can also keep around a reference to the created table:
 
   hbase> t1 = create 't1', 'f1'
 
-  Which gives you a reference to the table named 't1', on which you can then
-  call methods.
+Which gives you a reference to the table named 't1', on which you can then
+call methods.
 EOF
       end
 

Modified: hbase/trunk/hbase-server/src/test/ruby/hbase/admin_test.rb
URL: http://svn.apache.org/viewvc/hbase/trunk/hbase-server/src/test/ruby/hbase/admin_test.rb?rev=1406800&r1=1406799&r2=1406800&view=diff
==============================================================================
--- hbase/trunk/hbase-server/src/test/ruby/hbase/admin_test.rb (original)
+++ hbase/trunk/hbase-server/src/test/ruby/hbase/admin_test.rb Wed Nov  7 21:00:32 2012
@@ -148,6 +148,13 @@ module Hbase
         admin.create(@create_test_name)
       end
     end
+    
+    define_test "create should fail without columns when called with options" do
+      drop_test_table(@create_test_name)
+      assert_raise(ArgumentError) do
+        admin.create(@create_test_name, { OWNER => 'a' })
+      end
+    end
 
     define_test "create should work with string column args" do
       drop_test_table(@create_test_name)
@@ -160,12 +167,28 @@ module Hbase
       admin.create(@create_test_name, { NAME => 'a'}, { NAME => 'b'})
       assert_equal(['a:', 'b:'], table(@create_test_name).get_all_columns.sort)
     end
-
+    
+    define_test "create should be able to set table options" do
+      drop_test_table(@create_test_name)
+      admin.create(@create_test_name, 'a', 'b', 'MAX_FILESIZE' => 12345678, OWNER => '987654321')
+      assert_equal(['a:', 'b:'], table(@create_test_name).get_all_columns.sort)
+      assert_match(/12345678/, admin.describe(@create_test_name))
+      assert_match(/987654321/, admin.describe(@create_test_name))
+    end
+        
+    define_test "create should ignore table_att" do
+      drop_test_table(@create_test_name)
+      admin.create(@create_test_name, 'a', 'b', METHOD => 'table_att', OWNER => '987654321')
+      assert_equal(['a:', 'b:'], table(@create_test_name).get_all_columns.sort)
+      assert_match(/987654321/, admin.describe(@create_test_name))
+    end
+    
     define_test "create should work with SPLITALGO" do
       drop_test_table(@create_test_name)
       admin.create(@create_test_name, 'a', 'b', {NUMREGIONS => 10, SPLITALGO => 'HexStringSplit'})
       assert_equal(['a:', 'b:'], table(@create_test_name).get_all_columns.sort)
     end
+
     #-------------------------------------------------------------------------------
 
     define_test "describe should fail for non-existent tables" do
@@ -253,9 +276,10 @@ module Hbase
 
     define_test "alter should support more than one alteration in one call" do
       assert_equal(['x:', 'y:'], table(@test_name).get_all_columns.sort)
-      admin.alter(@test_name, true, { NAME => 'z' }, { METHOD => 'delete', NAME => 'y' })
+      admin.alter(@test_name, true, { NAME => 'z' }, { METHOD => 'delete', NAME => 'y' }, 'MAX_FILESIZE' => 12345678)
       admin.enable(@test_name)
       assert_equal(['x:', 'z:'], table(@test_name).get_all_columns.sort)
+      assert_match(/12345678/, admin.describe(@test_name))
     end
 
     define_test 'alter should support shortcut DELETE alter specs' do
@@ -269,6 +293,11 @@ module Hbase
       assert_match(/12345678/, admin.describe(@test_name))
     end
 
+    define_test "alter should be able to change table options w/o table_att" do
+      admin.alter(@test_name, true, 'MAX_FILESIZE' => 12345678)
+      assert_match(/12345678/, admin.describe(@test_name))
+    end
+    
     define_test "alter should be able to change coprocessor attributes" do
       drop_test_table(@test_name)
       create_test_table(@test_name)