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 2008/08/31 06:48:58 UTC

svn commit: r690637 - in /hadoop/hbase/trunk: ./ src/java/org/apache/hadoop/hbase/ src/java/org/apache/hadoop/hbase/regionserver/ src/java/org/apache/hadoop/hbase/rest/ src/test/org/apache/hadoop/hbase/

Author: stack
Date: Sat Aug 30 21:48:57 2008
New Revision: 690637

URL: http://svn.apache.org/viewvc?rev=690637&view=rev
Log:
HBASE-840 More options on the row query in REST interface

Modified:
    hadoop/hbase/trunk/CHANGES.txt
    hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java
    hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java
    hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java
    hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java
    hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java
    hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java
    hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java

Modified: hadoop/hbase/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/CHANGES.txt?rev=690637&r1=690636&r2=690637&view=diff
==============================================================================
--- hadoop/hbase/trunk/CHANGES.txt (original)
+++ hadoop/hbase/trunk/CHANGES.txt Sat Aug 30 21:48:57 2008
@@ -67,6 +67,8 @@
    HBASE-784  Base hbase-0.3.0 on hadoop-0.18
    HBASE-841  Consolidate multiple overloaded methods in HRegionInterface,
               HRegionServer (Jean-Daniel Cryans via Jim Kellerman)
+   HBASE-840  More options on the row query in REST interface
+              (Sishen Freecity via Stack)
 
   NEW FEATURES
    HBASE-787  Postgresql to HBase table replication example (Tim Sell via Stack)

Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java?rev=690637&r1=690636&r2=690637&view=diff
==============================================================================
--- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java (original)
+++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java Sat Aug 30 21:48:57 2008
@@ -132,7 +132,10 @@
   static final byte [] ROOT_TABLE_NAME = Bytes.toBytes("-ROOT-");
 
   /** The META table's name. */
-  static final byte [] META_TABLE_NAME = Bytes.toBytes(".META.");
+  static final byte [] META_TABLE_NAME = Bytes.toBytes(".META.");  
+
+  /** delimiter used between portions of a region name */
+  public static final int META_ROW_DELIMITER = ',';
 
   // Defines for the column names used in both ROOT and META HBase 'meta' tables.
   

Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java?rev=690637&r1=690636&r2=690637&view=diff
==============================================================================
--- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java (original)
+++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java Sat Aug 30 21:48:57 2008
@@ -40,6 +40,12 @@
   private byte [] column = HConstants.EMPTY_BYTE_ARRAY;
   private long timestamp = Long.MAX_VALUE;
 
+  /*
+   * regionInfo is only used as a hack to compare HSKs.
+   * It is not serialized.  See https://issues.apache.org/jira/browse/HBASE-832
+   */
+  private HRegionInfo regionInfo = null;
+
   /** Default constructor used in conjunction with Writable interface */
   public HStoreKey() {
     super();
@@ -47,8 +53,8 @@
   
   /**
    * Create an HStoreKey specifying only the row
-   * The column defaults to the empty string and the time stamp defaults to
-   * Long.MAX_VALUE
+   * The column defaults to the empty string, the time stamp defaults to
+   * Long.MAX_VALUE and the table defaults to empty string
    * 
    * @param row - row key
    */
@@ -58,8 +64,8 @@
 
   /**
    * Create an HStoreKey specifying only the row
-   * The column defaults to the empty string and the time stamp defaults to
-   * Long.MAX_VALUE
+   * The column defaults to the empty string, the time stamp defaults to
+   * Long.MAX_VALUE and the table defaults to empty string
    * 
    * @param row - row key
    */
@@ -69,7 +75,7 @@
 
   /**
    * Create an HStoreKey specifying the row and timestamp
-   * The column name defaults to the empty string
+   * The column and table names default to the empty string
    * 
    * @param row row key
    * @param timestamp timestamp value
@@ -80,29 +86,31 @@
 
   /**
    * Create an HStoreKey specifying the row and timestamp
-   * The column name defaults to the empty string
+   * The column and table names default to the empty string
    * 
    * @param row row key
    * @param timestamp timestamp value
    */
   public HStoreKey(final String row, long timestamp) {
-    this (row, "", timestamp);
+    this (row, "", timestamp, new HRegionInfo());
   }
 
   /**
    * Create an HStoreKey specifying the row and column names
    * The timestamp defaults to LATEST_TIMESTAMP
+   * and table name defaults to the empty string
    * 
    * @param row row key
    * @param column column key
    */
   public HStoreKey(final String row, final String column) {
-    this(row, column, HConstants.LATEST_TIMESTAMP);
+    this(row, column, HConstants.LATEST_TIMESTAMP, new HRegionInfo());
   }
 
   /**
    * Create an HStoreKey specifying the row and column names
    * The timestamp defaults to LATEST_TIMESTAMP
+   * and table name defaults to the empty string
    * 
    * @param row row key
    * @param column column key
@@ -110,6 +118,19 @@
   public HStoreKey(final byte [] row, final byte [] column) {
     this(row, column, HConstants.LATEST_TIMESTAMP);
   }
+  
+  /**
+   * Create an HStoreKey specifying the row, column names and table name
+   * The timestamp defaults to LATEST_TIMESTAMP
+   * 
+   * @param row row key
+   * @param column column key
+   * @param regionInfo region info
+   */
+  public HStoreKey(final byte [] row, 
+      final byte [] column, final HRegionInfo regionInfo) {
+    this(row, column, HConstants.LATEST_TIMESTAMP, regionInfo);
+  }
 
   /**
    * Create an HStoreKey specifying all the fields
@@ -118,13 +139,16 @@
    * @param row row key
    * @param column column key
    * @param timestamp timestamp value
+   * @param regionInfo region info
    */
-  public HStoreKey(final String row, final String column, long timestamp) {
-    this (Bytes.toBytes(row), Bytes.toBytes(column), timestamp);
+  public HStoreKey(final String row, 
+      final String column, long timestamp, final HRegionInfo regionInfo) {
+    this (Bytes.toBytes(row), Bytes.toBytes(column), 
+        timestamp, regionInfo);
   }
 
   /**
-   * Create an HStoreKey specifying all the fields
+   * Create an HStoreKey specifying all the fields with unspecified table
    * Does not make copies of the passed byte arrays. Presumes the passed 
    * arrays immutable.
    * @param row row key
@@ -132,10 +156,25 @@
    * @param timestamp timestamp value
    */
   public HStoreKey(final byte [] row, final byte [] column, long timestamp) {
+    this(row, column, timestamp, null);
+  }
+  
+  /**
+   * Create an HStoreKey specifying all the fields with specified table
+   * Does not make copies of the passed byte arrays. Presumes the passed 
+   * arrays immutable.
+   * @param row row key
+   * @param column column key
+   * @param timestamp timestamp value
+   * @param regionInfo region info
+   */
+  public HStoreKey(final byte [] row, 
+      final byte [] column, long timestamp, final HRegionInfo regionInfo) {
     // Make copies
     this.row = row;
     this.column = column;
     this.timestamp = timestamp;
+    this.regionInfo = regionInfo;
   }
   
   /** @return Approximate size in bytes of this key. */
@@ -205,6 +244,11 @@
     return this.timestamp;
   }
   
+  /** @return value of regioninfo */
+  public HRegionInfo getHRegionInfo() {
+    return this.regionInfo;
+  }
+  
   /**
    * Compares the row and column of two keys
    * @param other Key to compare against. Compares row and column.
@@ -274,7 +318,7 @@
   /** {@inheritDoc} */
   public int compareTo(Object o) {
     HStoreKey other = (HStoreKey)o;
-    int result = Bytes.compareTo(this.row, other.row);
+    int result = compareTwoRowKeys(this.regionInfo, this.row, other.row);
     if (result != 0) {
       return result;
     }
@@ -419,6 +463,66 @@
     return Bytes.add(hsk.getRow(), hsk.getColumn());
   }
   
+  /**
+   * Utility method to compare two row keys.
+   * This is required because of the meta delimiters.
+   * This is a hack.
+   * @param regioninfo
+   * @param rowA
+   * @param rowB
+   * @return value of the comparison
+   */
+  public static int compareTwoRowKeys(HRegionInfo regionInfo, 
+      byte[] rowA, byte[] rowB) {
+    if(regionInfo != null && (regionInfo.isMetaRegion() ||
+        regionInfo.isRootRegion())) {
+      byte[][] keysA = stripStartKeyMeta(rowA);
+      byte[][] KeysB = stripStartKeyMeta(rowB);
+      int rowCompare = Bytes.compareTo(keysA[0], KeysB[0]);
+      if(rowCompare == 0)
+        rowCompare = Bytes.compareTo(keysA[1], KeysB[1]);
+      return rowCompare;
+    } else {
+      return Bytes.compareTo(rowA, rowB);
+    }
+  }
+  
+  /**
+   * Utility method to check if two row keys are equal.
+   * This is required because of the meta delimiters
+   * This is a hack
+   * @param regioninfo
+   * @param rowA
+   * @param rowB
+   * @return if it's equal
+   */
+  public static boolean equalsTwoRowKeys(HRegionInfo regionInfo, 
+      byte[] rowA, byte[] rowB) {
+    return rowA == null && rowB == null? true:
+      rowA == null && rowB != null? false:
+        rowA != null && rowB == null? false:
+          rowA.length != rowB.length? false:
+        compareTwoRowKeys(regionInfo,rowA,rowB) == 0;
+  }
+  
+  private static byte[][] stripStartKeyMeta(byte[] rowKey) {
+    int offset = -1;
+    for (int i = rowKey.length - 1; i > 0; i--) {
+      if (rowKey[i] == HConstants.META_ROW_DELIMITER) {
+        offset = i;
+        break;
+      }
+    }
+    byte [] row = new byte[offset];
+    System.arraycopy(rowKey, 0, row, 0,offset);
+    byte [] timestamp = new byte[rowKey.length - offset - 1];
+    System.arraycopy(rowKey, offset+1, timestamp, 0,rowKey.length - offset - 1);
+    byte[][] elements = new byte[2][];
+    elements[0] = row;
+    elements[1] = timestamp;
+    return elements;
+  }
+  
   // Writable
 
   /** {@inheritDoc} */
@@ -434,4 +538,4 @@
     this.column = Bytes.readByteArray(in);
     this.timestamp = in.readLong();
   }
-}
\ No newline at end of file
+}

Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java?rev=690637&r1=690636&r2=690637&view=diff
==============================================================================
--- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java (original)
+++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java Sat Aug 30 21:48:57 2008
@@ -1246,13 +1246,14 @@
         // get the closest key
         byte [] closestKey = store.getRowKeyAtOrBefore(row);
         // if it happens to be an exact match, we can stop looping
-        if (Bytes.equals(row, closestKey)) {
+        if (HStoreKey.equalsTwoRowKeys(regionInfo,row, closestKey)) {
           key = new HStoreKey(closestKey);
           break;
         }
         // otherwise, we need to check if it's the max and move to the next
         if (closestKey != null 
-          && (key == null || Bytes.compareTo(closestKey, key.getRow()) > 0) ) {
+          && (key == null || HStoreKey.compareTwoRowKeys(
+              regionInfo,closestKey, key.getRow()) > 0) ) {
           key = new HStoreKey(closestKey);
         }
       }

Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java?rev=690637&r1=690636&r2=690637&view=diff
==============================================================================
--- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java (original)
+++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java Sat Aug 30 21:48:57 2008
@@ -37,6 +37,7 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HStoreKey;
 import org.apache.hadoop.hbase.io.Cell;
 import org.apache.hadoop.hbase.util.Bytes;
@@ -53,6 +54,8 @@
   private final Log LOG = LogFactory.getLog(this.getClass().getName());
   
   private final long ttl;
+  
+  private HRegionInfo regionInfo;
 
   // Note that since these structures are always accessed with a lock held,
   // so no additional synchronization is required.
@@ -72,14 +75,17 @@
    */
   public Memcache() {
     this.ttl = HConstants.FOREVER;
+    this.regionInfo = null;
   }
 
   /**
    * Constructor.
    * @param ttl The TTL for cache entries, in milliseconds.
+   * @param regionInfo The HRI for this cache 
    */
-  public Memcache(final long ttl) {
+  public Memcache(final long ttl, HRegionInfo regionInfo) {
     this.ttl = ttl;
+    this.regionInfo = regionInfo;
   }
 
   /*
@@ -383,7 +389,8 @@
     // the search key, or a range of values between the first candidate key
     // and the ultimate search key (or the end of the cache)
     if (!tailMap.isEmpty() &&
-        Bytes.compareTo(tailMap.firstKey().getRow(), search_key.getRow()) <= 0) {
+        HStoreKey.compareTwoRowKeys(regionInfo, 
+            tailMap.firstKey().getRow(), search_key.getRow()) <= 0) {
       Iterator<HStoreKey> key_iterator = tailMap.keySet().iterator();
 
       // Keep looking at cells as long as they are no greater than the 
@@ -391,9 +398,11 @@
       HStoreKey deletedOrExpiredRow = null;
       for (HStoreKey found_key = null; key_iterator.hasNext() &&
           (found_key == null ||
-            Bytes.compareTo(found_key.getRow(), row) <= 0);) {
+            HStoreKey.compareTwoRowKeys(regionInfo, 
+                found_key.getRow(), row) <= 0);) {
         found_key = key_iterator.next();
-        if (Bytes.compareTo(found_key.getRow(), row) <= 0) {
+        if (HStoreKey.compareTwoRowKeys(regionInfo, 
+            found_key.getRow(), row) <= 0) {
           if (HLogEdit.isDeleted(tailMap.get(found_key))) {
             HStore.handleDeleted(found_key, candidateKeys, deletes);
             if (deletedOrExpiredRow == null) {

Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java?rev=690637&r1=690636&r2=690637&view=diff
==============================================================================
--- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java (original)
+++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java Sat Aug 30 21:48:57 2008
@@ -55,6 +55,7 @@
   protected static final String CONTENT_TYPE = "content-type";
   protected static final String ROW = "row";
   protected static final String REGIONS = "regions";
+  protected static final String VERSION = "version";
   
   protected final Log LOG = LogFactory.getLog(this.getClass());
 
@@ -233,13 +234,32 @@
       outputter.startTag(COLUMN);
       doElement(outputter, "name", 
         org.apache.hadoop.hbase.util.Base64.encodeBytes(e.getKey()));
-      // We don't know String from binary data so we always base64 encode.
-      doElement(outputter, "value",
-        org.apache.hadoop.hbase.util.Base64.encodeBytes(e.getValue().getValue()));
+      outputCellXml(outputter, e.getValue());
       outputter.endTag();
     }
   }
 
+  protected void outputColumnsWithMultiVersionsXml(final XMLOutputter outputter,
+    final Map<byte [], Cell[]> m)
+  throws IllegalStateException, IllegalArgumentException, IOException {
+    for (Map.Entry<byte [], Cell[]> e: m.entrySet()) {
+      for (Cell c : e.getValue()) {
+        outputter.startTag(COLUMN);
+        doElement(outputter, "name", 
+            org.apache.hadoop.hbase.util.Base64.encodeBytes(e.getKey())); 
+        outputCellXml(outputter, c);
+        outputter.endTag();       
+      }
+    }
+  }
+  
+  protected void outputCellXml(final XMLOutputter outputter, Cell c) 
+  throws IllegalStateException, IllegalArgumentException, IOException {
+    // We don't know String from binary data so we always base64 encode.
+    doElement(outputter, "value",
+        org.apache.hadoop.hbase.util.Base64.encodeBytes(c.getValue()));
+    doElement(outputter, "timestamp", String.valueOf(c.getTimestamp()));    
+  }
 //  Commented - multipart support is currently nonexistant.
 //  protected void outputColumnsMime(final MultiPartResponse mpr,
 //     final Map<Text, Cell> m)

Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java?rev=690637&r1=690636&r2=690637&view=diff
==============================================================================
--- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java (original)
+++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java Sat Aug 30 21:48:57 2008
@@ -2,9 +2,9 @@
 
 import java.io.IOException;
 import java.net.URLDecoder;
-import java.util.HashSet;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
-import java.util.Set;
 import java.util.TreeMap;
 
 import javax.servlet.ServletException;
@@ -72,7 +72,7 @@
     final HttpServletResponse response, final String [] pathSegments)
   throws IOException {
     // pull the row key out of the path
-    String row = URLDecoder.decode(pathSegments[2], HConstants.UTF8_ENCODING);
+    byte[] row = Bytes.toBytes(URLDecoder.decode(pathSegments[2], HConstants.UTF8_ENCODING));
 
     String timestampStr = null;
     if (pathSegments.length == 4) {
@@ -85,64 +85,63 @@
       }
     }
     
-    String[] columns = request.getParameterValues(COLUMN);
-        
-    if (columns == null || columns.length == 0) {
-      // They want full row returned. 
-
-      // Presumption is that this.table has already been focused on target table.
-      Map<byte [], Cell> result = timestampStr == null ? 
-        table.getRow(Bytes.toBytes(row)) 
-        : table.getRow(Bytes.toBytes(row), Long.parseLong(timestampStr));
-        
+    String[] column_params = request.getParameterValues(COLUMN);
+    
+    byte[][] columns = null;
+    
+    if (column_params != null && column_params.length > 0) {
+      List<String> available_columns = new ArrayList<String>();
+      for (String column_param : column_params) {
+        if (column_param.length() > 0 && table.getTableDescriptor().hasFamily(Bytes.toBytes(column_param))) {
+          available_columns.add(column_param);
+        }
+      }
+      columns = Bytes.toByteArrays(available_columns.toArray(new String[0]));
+    }
+    
+    String[] version_params = request.getParameterValues(VERSION);
+    int version = 0;
+    if (version_params != null && version_params.length == 1) {
+      version = Integer.parseInt(version_params[0]);
+    }
+    
+    if (version > 0 && columns != null) {
+      Map<byte[], Cell[]> result = new TreeMap<byte [], Cell[]>(Bytes.BYTES_COMPARATOR);      
+      
+      for (byte[] col : columns) {
+        Cell[] cells = timestampStr == null ? table.get(row, col, version)
+                      : table.get(row, col, Long.parseLong(timestampStr), version);
+        if (cells != null) {
+          result.put(col, cells);
+        }
+      }
+      
       if (result == null || result.size() == 0) {
         doNotFound(response, "Row not found!");
       } else {
         switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
         case XML:
-          outputRowXml(response, result);
+          outputRowWithMultiVersionsXml(response, result);
           break;
         case MIME:
         default:
-          doNotAcceptable(response, "Unsupported Accept Header Content: " +
-            request.getHeader(CONTENT_TYPE));
+          doNotAcceptable(response, "Unsupported Accept Header Content: "
+              + request.getHeader(CONTENT_TYPE));
         }
-      }
+      }      
     } else {
-      Map<byte [], Cell> prefiltered_result = table.getRow(Bytes.toBytes(row));
-    
-      if (prefiltered_result == null || prefiltered_result.size() == 0) {
+      Map<byte[], Cell> result = timestampStr == null ? table.getRow(row, columns) : table.getRow(row, columns, Long.parseLong(timestampStr));
+      if (result == null || result.size() == 0) {
         doNotFound(response, "Row not found!");
       } else {
-        // create a Set from the columns requested so we can
-        // efficiently filter the actual found columns
-        Set<String> requested_columns_set = new HashSet<String>();
-        for(int i = 0; i < columns.length; i++){
-          requested_columns_set.add(columns[i]);
-        }
-  
-        // output map that will contain the filtered results
-        Map<byte [], Cell> m =
-          new TreeMap<byte [], Cell>(Bytes.BYTES_COMPARATOR);
-
-        // get an array of all the columns retrieved
-        Set<byte []> columns_retrieved = prefiltered_result.keySet();
-
-        // copy over those cells with requested column names
-        for(byte [] current_column: columns_retrieved) {
-          if (requested_columns_set.contains(Bytes.toString(current_column))) {
-            m.put(current_column, prefiltered_result.get(current_column));            
-          }
-        }
-        
         switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
-          case XML:
-            outputRowXml(response, m);
-            break;
-          case MIME:
-          default:
-            doNotAcceptable(response, "Unsupported Accept Header Content: " +
-              request.getHeader(CONTENT_TYPE));
+        case XML:
+          outputRowXml(response, result);
+          break;
+        case MIME:
+        default:
+          doNotAcceptable(response, "Unsupported Accept Header Content: "
+              + request.getHeader(CONTENT_TYPE));
         }
       }
     }
@@ -167,6 +166,18 @@
     outputter.getWriter().close();
   }
   
+  private void outputRowWithMultiVersionsXml(final HttpServletResponse response,
+      final Map<byte[], Cell[]> result) 
+  throws IOException {
+    setResponseHeader(response, result.size() > 0? 200: 204,
+        ContentType.XML.toString());
+    XMLOutputter outputter = getXMLOutputter(response.getWriter());
+    outputter.startTag(ROW);
+    outputColumnsWithMultiVersionsXml(outputter, result);
+    outputter.endTag();
+    outputter.endDocument();
+    outputter.getWriter().close();   
+  }
   /*
    * @param response
    * @param result

Modified: hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java
URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java?rev=690637&r1=690636&r2=690637&view=diff
==============================================================================
--- hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java (original)
+++ hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java Sat Aug 30 21:48:57 2008
@@ -54,6 +54,43 @@
   }
   
   /**
+   * Tests cases where rows keys have characters below the ','.
+   * See HBASE-832
+   */
+  public void testHStoreKeyBorderCases() {
+    HRegionInfo info = new HRegionInfo(new HTableDescriptor("testtable"),
+        HConstants.EMPTY_BYTE_ARRAY,HConstants.EMPTY_BYTE_ARRAY);
+    HStoreKey rowA = new HStoreKey("testtable,www.hbase.org/,1234",
+        "", Long.MAX_VALUE, info);
+    HStoreKey rowB = new HStoreKey("testtable,www.hbase.org/%20,99999",
+        "", Long.MAX_VALUE, info);
+
+    assertTrue(rowA.compareTo(rowB) > 0);
+
+    rowA = new HStoreKey("testtable,www.hbase.org/,1234",
+        "", Long.MAX_VALUE, HRegionInfo.FIRST_META_REGIONINFO);
+    rowB = new HStoreKey("testtable,www.hbase.org/%20,99999",
+        "", Long.MAX_VALUE, HRegionInfo.FIRST_META_REGIONINFO);
+
+    assertTrue(rowA.compareTo(rowB) < 0);
+
+    rowA = new HStoreKey("testtable,,1234",
+        "", Long.MAX_VALUE, HRegionInfo.FIRST_META_REGIONINFO);
+    rowB = new HStoreKey("testtable,$www.hbase.org/,99999",
+        "", Long.MAX_VALUE, HRegionInfo.FIRST_META_REGIONINFO);
+
+    assertTrue(rowA.compareTo(rowB) < 0);
+
+    rowA = new HStoreKey(".META.,testtable,www.hbase.org/,1234,4321",
+        "", Long.MAX_VALUE, HRegionInfo.ROOT_REGIONINFO);
+    rowB = new HStoreKey(".META.,testtable,www.hbase.org/%20,99999,99999",
+        "", Long.MAX_VALUE, HRegionInfo.ROOT_REGIONINFO);
+
+    assertTrue(rowA.compareTo(rowB) > 0);
+  }
+
+  
+  /**
    * Sort of HRegionInfo.
    */
   public void testHRegionInfo() {



Re: svn commit: r690637 - in /hadoop/hbase/trunk: ./ src/java/org/apache/hadoop/hbase/ src/java/org/apache/hadoop/hbase/regionserver/ src/java/org/apache/hadoop/hbase/rest/ src/test/org/apache/hadoop/hbase/

Posted by stack <st...@duboce.net>.
I committed 832 below along with 840.  Meant to do it after 840 rather 
than as part of 840.
St.Ack

stack@apache.org wrote:
> Author: stack
> Date: Sat Aug 30 21:48:57 2008
> New Revision: 690637
>
> URL: http://svn.apache.org/viewvc?rev=690637&view=rev
> Log:
> HBASE-840 More options on the row query in REST interface
>
> Modified:
>     hadoop/hbase/trunk/CHANGES.txt
>     hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java
>     hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java
>     hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java
>     hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java
>     hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java
>     hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java
>     hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java
>
> Modified: hadoop/hbase/trunk/CHANGES.txt
> URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/CHANGES.txt?rev=690637&r1=690636&r2=690637&view=diff
> ==============================================================================
> --- hadoop/hbase/trunk/CHANGES.txt (original)
> +++ hadoop/hbase/trunk/CHANGES.txt Sat Aug 30 21:48:57 2008
> @@ -67,6 +67,8 @@
>     HBASE-784  Base hbase-0.3.0 on hadoop-0.18
>     HBASE-841  Consolidate multiple overloaded methods in HRegionInterface,
>                HRegionServer (Jean-Daniel Cryans via Jim Kellerman)
> +   HBASE-840  More options on the row query in REST interface
> +              (Sishen Freecity via Stack)
>  
>    NEW FEATURES
>     HBASE-787  Postgresql to HBase table replication example (Tim Sell via Stack)
>
> Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java
> URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java?rev=690637&r1=690636&r2=690637&view=diff
> ==============================================================================
> --- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java (original)
> +++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HConstants.java Sat Aug 30 21:48:57 2008
> @@ -132,7 +132,10 @@
>    static final byte [] ROOT_TABLE_NAME = Bytes.toBytes("-ROOT-");
>  
>    /** The META table's name. */
> -  static final byte [] META_TABLE_NAME = Bytes.toBytes(".META.");
> +  static final byte [] META_TABLE_NAME = Bytes.toBytes(".META.");  
> +
> +  /** delimiter used between portions of a region name */
> +  public static final int META_ROW_DELIMITER = ',';
>  
>    // Defines for the column names used in both ROOT and META HBase 'meta' tables.
>    
>
> Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java
> URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java?rev=690637&r1=690636&r2=690637&view=diff
> ==============================================================================
> --- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java (original)
> +++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/HStoreKey.java Sat Aug 30 21:48:57 2008
> @@ -40,6 +40,12 @@
>    private byte [] column = HConstants.EMPTY_BYTE_ARRAY;
>    private long timestamp = Long.MAX_VALUE;
>  
> +  /*
> +   * regionInfo is only used as a hack to compare HSKs.
> +   * It is not serialized.  See https://issues.apache.org/jira/browse/HBASE-832
> +   */
> +  private HRegionInfo regionInfo = null;
> +
>    /** Default constructor used in conjunction with Writable interface */
>    public HStoreKey() {
>      super();
> @@ -47,8 +53,8 @@
>    
>    /**
>     * Create an HStoreKey specifying only the row
> -   * The column defaults to the empty string and the time stamp defaults to
> -   * Long.MAX_VALUE
> +   * The column defaults to the empty string, the time stamp defaults to
> +   * Long.MAX_VALUE and the table defaults to empty string
>     * 
>     * @param row - row key
>     */
> @@ -58,8 +64,8 @@
>  
>    /**
>     * Create an HStoreKey specifying only the row
> -   * The column defaults to the empty string and the time stamp defaults to
> -   * Long.MAX_VALUE
> +   * The column defaults to the empty string, the time stamp defaults to
> +   * Long.MAX_VALUE and the table defaults to empty string
>     * 
>     * @param row - row key
>     */
> @@ -69,7 +75,7 @@
>  
>    /**
>     * Create an HStoreKey specifying the row and timestamp
> -   * The column name defaults to the empty string
> +   * The column and table names default to the empty string
>     * 
>     * @param row row key
>     * @param timestamp timestamp value
> @@ -80,29 +86,31 @@
>  
>    /**
>     * Create an HStoreKey specifying the row and timestamp
> -   * The column name defaults to the empty string
> +   * The column and table names default to the empty string
>     * 
>     * @param row row key
>     * @param timestamp timestamp value
>     */
>    public HStoreKey(final String row, long timestamp) {
> -    this (row, "", timestamp);
> +    this (row, "", timestamp, new HRegionInfo());
>    }
>  
>    /**
>     * Create an HStoreKey specifying the row and column names
>     * The timestamp defaults to LATEST_TIMESTAMP
> +   * and table name defaults to the empty string
>     * 
>     * @param row row key
>     * @param column column key
>     */
>    public HStoreKey(final String row, final String column) {
> -    this(row, column, HConstants.LATEST_TIMESTAMP);
> +    this(row, column, HConstants.LATEST_TIMESTAMP, new HRegionInfo());
>    }
>  
>    /**
>     * Create an HStoreKey specifying the row and column names
>     * The timestamp defaults to LATEST_TIMESTAMP
> +   * and table name defaults to the empty string
>     * 
>     * @param row row key
>     * @param column column key
> @@ -110,6 +118,19 @@
>    public HStoreKey(final byte [] row, final byte [] column) {
>      this(row, column, HConstants.LATEST_TIMESTAMP);
>    }
> +  
> +  /**
> +   * Create an HStoreKey specifying the row, column names and table name
> +   * The timestamp defaults to LATEST_TIMESTAMP
> +   * 
> +   * @param row row key
> +   * @param column column key
> +   * @param regionInfo region info
> +   */
> +  public HStoreKey(final byte [] row, 
> +      final byte [] column, final HRegionInfo regionInfo) {
> +    this(row, column, HConstants.LATEST_TIMESTAMP, regionInfo);
> +  }
>  
>    /**
>     * Create an HStoreKey specifying all the fields
> @@ -118,13 +139,16 @@
>     * @param row row key
>     * @param column column key
>     * @param timestamp timestamp value
> +   * @param regionInfo region info
>     */
> -  public HStoreKey(final String row, final String column, long timestamp) {
> -    this (Bytes.toBytes(row), Bytes.toBytes(column), timestamp);
> +  public HStoreKey(final String row, 
> +      final String column, long timestamp, final HRegionInfo regionInfo) {
> +    this (Bytes.toBytes(row), Bytes.toBytes(column), 
> +        timestamp, regionInfo);
>    }
>  
>    /**
> -   * Create an HStoreKey specifying all the fields
> +   * Create an HStoreKey specifying all the fields with unspecified table
>     * Does not make copies of the passed byte arrays. Presumes the passed 
>     * arrays immutable.
>     * @param row row key
> @@ -132,10 +156,25 @@
>     * @param timestamp timestamp value
>     */
>    public HStoreKey(final byte [] row, final byte [] column, long timestamp) {
> +    this(row, column, timestamp, null);
> +  }
> +  
> +  /**
> +   * Create an HStoreKey specifying all the fields with specified table
> +   * Does not make copies of the passed byte arrays. Presumes the passed 
> +   * arrays immutable.
> +   * @param row row key
> +   * @param column column key
> +   * @param timestamp timestamp value
> +   * @param regionInfo region info
> +   */
> +  public HStoreKey(final byte [] row, 
> +      final byte [] column, long timestamp, final HRegionInfo regionInfo) {
>      // Make copies
>      this.row = row;
>      this.column = column;
>      this.timestamp = timestamp;
> +    this.regionInfo = regionInfo;
>    }
>    
>    /** @return Approximate size in bytes of this key. */
> @@ -205,6 +244,11 @@
>      return this.timestamp;
>    }
>    
> +  /** @return value of regioninfo */
> +  public HRegionInfo getHRegionInfo() {
> +    return this.regionInfo;
> +  }
> +  
>    /**
>     * Compares the row and column of two keys
>     * @param other Key to compare against. Compares row and column.
> @@ -274,7 +318,7 @@
>    /** {@inheritDoc} */
>    public int compareTo(Object o) {
>      HStoreKey other = (HStoreKey)o;
> -    int result = Bytes.compareTo(this.row, other.row);
> +    int result = compareTwoRowKeys(this.regionInfo, this.row, other.row);
>      if (result != 0) {
>        return result;
>      }
> @@ -419,6 +463,66 @@
>      return Bytes.add(hsk.getRow(), hsk.getColumn());
>    }
>    
> +  /**
> +   * Utility method to compare two row keys.
> +   * This is required because of the meta delimiters.
> +   * This is a hack.
> +   * @param regioninfo
> +   * @param rowA
> +   * @param rowB
> +   * @return value of the comparison
> +   */
> +  public static int compareTwoRowKeys(HRegionInfo regionInfo, 
> +      byte[] rowA, byte[] rowB) {
> +    if(regionInfo != null && (regionInfo.isMetaRegion() ||
> +        regionInfo.isRootRegion())) {
> +      byte[][] keysA = stripStartKeyMeta(rowA);
> +      byte[][] KeysB = stripStartKeyMeta(rowB);
> +      int rowCompare = Bytes.compareTo(keysA[0], KeysB[0]);
> +      if(rowCompare == 0)
> +        rowCompare = Bytes.compareTo(keysA[1], KeysB[1]);
> +      return rowCompare;
> +    } else {
> +      return Bytes.compareTo(rowA, rowB);
> +    }
> +  }
> +  
> +  /**
> +   * Utility method to check if two row keys are equal.
> +   * This is required because of the meta delimiters
> +   * This is a hack
> +   * @param regioninfo
> +   * @param rowA
> +   * @param rowB
> +   * @return if it's equal
> +   */
> +  public static boolean equalsTwoRowKeys(HRegionInfo regionInfo, 
> +      byte[] rowA, byte[] rowB) {
> +    return rowA == null && rowB == null? true:
> +      rowA == null && rowB != null? false:
> +        rowA != null && rowB == null? false:
> +          rowA.length != rowB.length? false:
> +        compareTwoRowKeys(regionInfo,rowA,rowB) == 0;
> +  }
> +  
> +  private static byte[][] stripStartKeyMeta(byte[] rowKey) {
> +    int offset = -1;
> +    for (int i = rowKey.length - 1; i > 0; i--) {
> +      if (rowKey[i] == HConstants.META_ROW_DELIMITER) {
> +        offset = i;
> +        break;
> +      }
> +    }
> +    byte [] row = new byte[offset];
> +    System.arraycopy(rowKey, 0, row, 0,offset);
> +    byte [] timestamp = new byte[rowKey.length - offset - 1];
> +    System.arraycopy(rowKey, offset+1, timestamp, 0,rowKey.length - offset - 1);
> +    byte[][] elements = new byte[2][];
> +    elements[0] = row;
> +    elements[1] = timestamp;
> +    return elements;
> +  }
> +  
>    // Writable
>  
>    /** {@inheritDoc} */
> @@ -434,4 +538,4 @@
>      this.column = Bytes.readByteArray(in);
>      this.timestamp = in.readLong();
>    }
> -}
> \ No newline at end of file
> +}
>
> Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java
> URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java?rev=690637&r1=690636&r2=690637&view=diff
> ==============================================================================
> --- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java (original)
> +++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/HRegion.java Sat Aug 30 21:48:57 2008
> @@ -1246,13 +1246,14 @@
>          // get the closest key
>          byte [] closestKey = store.getRowKeyAtOrBefore(row);
>          // if it happens to be an exact match, we can stop looping
> -        if (Bytes.equals(row, closestKey)) {
> +        if (HStoreKey.equalsTwoRowKeys(regionInfo,row, closestKey)) {
>            key = new HStoreKey(closestKey);
>            break;
>          }
>          // otherwise, we need to check if it's the max and move to the next
>          if (closestKey != null 
> -          && (key == null || Bytes.compareTo(closestKey, key.getRow()) > 0) ) {
> +          && (key == null || HStoreKey.compareTwoRowKeys(
> +              regionInfo,closestKey, key.getRow()) > 0) ) {
>            key = new HStoreKey(closestKey);
>          }
>        }
>
> Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java
> URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java?rev=690637&r1=690636&r2=690637&view=diff
> ==============================================================================
> --- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java (original)
> +++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/regionserver/Memcache.java Sat Aug 30 21:48:57 2008
> @@ -37,6 +37,7 @@
>  import org.apache.commons.logging.Log;
>  import org.apache.commons.logging.LogFactory;
>  import org.apache.hadoop.hbase.HConstants;
> +import org.apache.hadoop.hbase.HRegionInfo;
>  import org.apache.hadoop.hbase.HStoreKey;
>  import org.apache.hadoop.hbase.io.Cell;
>  import org.apache.hadoop.hbase.util.Bytes;
> @@ -53,6 +54,8 @@
>    private final Log LOG = LogFactory.getLog(this.getClass().getName());
>    
>    private final long ttl;
> +  
> +  private HRegionInfo regionInfo;
>  
>    // Note that since these structures are always accessed with a lock held,
>    // so no additional synchronization is required.
> @@ -72,14 +75,17 @@
>     */
>    public Memcache() {
>      this.ttl = HConstants.FOREVER;
> +    this.regionInfo = null;
>    }
>  
>    /**
>     * Constructor.
>     * @param ttl The TTL for cache entries, in milliseconds.
> +   * @param regionInfo The HRI for this cache 
>     */
> -  public Memcache(final long ttl) {
> +  public Memcache(final long ttl, HRegionInfo regionInfo) {
>      this.ttl = ttl;
> +    this.regionInfo = regionInfo;
>    }
>  
>    /*
> @@ -383,7 +389,8 @@
>      // the search key, or a range of values between the first candidate key
>      // and the ultimate search key (or the end of the cache)
>      if (!tailMap.isEmpty() &&
> -        Bytes.compareTo(tailMap.firstKey().getRow(), search_key.getRow()) <= 0) {
> +        HStoreKey.compareTwoRowKeys(regionInfo, 
> +            tailMap.firstKey().getRow(), search_key.getRow()) <= 0) {
>        Iterator<HStoreKey> key_iterator = tailMap.keySet().iterator();
>  
>        // Keep looking at cells as long as they are no greater than the 
> @@ -391,9 +398,11 @@
>        HStoreKey deletedOrExpiredRow = null;
>        for (HStoreKey found_key = null; key_iterator.hasNext() &&
>            (found_key == null ||
> -            Bytes.compareTo(found_key.getRow(), row) <= 0);) {
> +            HStoreKey.compareTwoRowKeys(regionInfo, 
> +                found_key.getRow(), row) <= 0);) {
>          found_key = key_iterator.next();
> -        if (Bytes.compareTo(found_key.getRow(), row) <= 0) {
> +        if (HStoreKey.compareTwoRowKeys(regionInfo, 
> +            found_key.getRow(), row) <= 0) {
>            if (HLogEdit.isDeleted(tailMap.get(found_key))) {
>              HStore.handleDeleted(found_key, candidateKeys, deletes);
>              if (deletedOrExpiredRow == null) {
>
> Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java
> URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java?rev=690637&r1=690636&r2=690637&view=diff
> ==============================================================================
> --- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java (original)
> +++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/GenericHandler.java Sat Aug 30 21:48:57 2008
> @@ -55,6 +55,7 @@
>    protected static final String CONTENT_TYPE = "content-type";
>    protected static final String ROW = "row";
>    protected static final String REGIONS = "regions";
> +  protected static final String VERSION = "version";
>    
>    protected final Log LOG = LogFactory.getLog(this.getClass());
>  
> @@ -233,13 +234,32 @@
>        outputter.startTag(COLUMN);
>        doElement(outputter, "name", 
>          org.apache.hadoop.hbase.util.Base64.encodeBytes(e.getKey()));
> -      // We don't know String from binary data so we always base64 encode.
> -      doElement(outputter, "value",
> -        org.apache.hadoop.hbase.util.Base64.encodeBytes(e.getValue().getValue()));
> +      outputCellXml(outputter, e.getValue());
>        outputter.endTag();
>      }
>    }
>  
> +  protected void outputColumnsWithMultiVersionsXml(final XMLOutputter outputter,
> +    final Map<byte [], Cell[]> m)
> +  throws IllegalStateException, IllegalArgumentException, IOException {
> +    for (Map.Entry<byte [], Cell[]> e: m.entrySet()) {
> +      for (Cell c : e.getValue()) {
> +        outputter.startTag(COLUMN);
> +        doElement(outputter, "name", 
> +            org.apache.hadoop.hbase.util.Base64.encodeBytes(e.getKey())); 
> +        outputCellXml(outputter, c);
> +        outputter.endTag();       
> +      }
> +    }
> +  }
> +  
> +  protected void outputCellXml(final XMLOutputter outputter, Cell c) 
> +  throws IllegalStateException, IllegalArgumentException, IOException {
> +    // We don't know String from binary data so we always base64 encode.
> +    doElement(outputter, "value",
> +        org.apache.hadoop.hbase.util.Base64.encodeBytes(c.getValue()));
> +    doElement(outputter, "timestamp", String.valueOf(c.getTimestamp()));    
> +  }
>  //  Commented - multipart support is currently nonexistant.
>  //  protected void outputColumnsMime(final MultiPartResponse mpr,
>  //     final Map<Text, Cell> m)
>
> Modified: hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java
> URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java?rev=690637&r1=690636&r2=690637&view=diff
> ==============================================================================
> --- hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java (original)
> +++ hadoop/hbase/trunk/src/java/org/apache/hadoop/hbase/rest/RowHandler.java Sat Aug 30 21:48:57 2008
> @@ -2,9 +2,9 @@
>  
>  import java.io.IOException;
>  import java.net.URLDecoder;
> -import java.util.HashSet;
> +import java.util.ArrayList;
> +import java.util.List;
>  import java.util.Map;
> -import java.util.Set;
>  import java.util.TreeMap;
>  
>  import javax.servlet.ServletException;
> @@ -72,7 +72,7 @@
>      final HttpServletResponse response, final String [] pathSegments)
>    throws IOException {
>      // pull the row key out of the path
> -    String row = URLDecoder.decode(pathSegments[2], HConstants.UTF8_ENCODING);
> +    byte[] row = Bytes.toBytes(URLDecoder.decode(pathSegments[2], HConstants.UTF8_ENCODING));
>  
>      String timestampStr = null;
>      if (pathSegments.length == 4) {
> @@ -85,64 +85,63 @@
>        }
>      }
>      
> -    String[] columns = request.getParameterValues(COLUMN);
> -        
> -    if (columns == null || columns.length == 0) {
> -      // They want full row returned. 
> -
> -      // Presumption is that this.table has already been focused on target table.
> -      Map<byte [], Cell> result = timestampStr == null ? 
> -        table.getRow(Bytes.toBytes(row)) 
> -        : table.getRow(Bytes.toBytes(row), Long.parseLong(timestampStr));
> -        
> +    String[] column_params = request.getParameterValues(COLUMN);
> +    
> +    byte[][] columns = null;
> +    
> +    if (column_params != null && column_params.length > 0) {
> +      List<String> available_columns = new ArrayList<String>();
> +      for (String column_param : column_params) {
> +        if (column_param.length() > 0 && table.getTableDescriptor().hasFamily(Bytes.toBytes(column_param))) {
> +          available_columns.add(column_param);
> +        }
> +      }
> +      columns = Bytes.toByteArrays(available_columns.toArray(new String[0]));
> +    }
> +    
> +    String[] version_params = request.getParameterValues(VERSION);
> +    int version = 0;
> +    if (version_params != null && version_params.length == 1) {
> +      version = Integer.parseInt(version_params[0]);
> +    }
> +    
> +    if (version > 0 && columns != null) {
> +      Map<byte[], Cell[]> result = new TreeMap<byte [], Cell[]>(Bytes.BYTES_COMPARATOR);      
> +      
> +      for (byte[] col : columns) {
> +        Cell[] cells = timestampStr == null ? table.get(row, col, version)
> +                      : table.get(row, col, Long.parseLong(timestampStr), version);
> +        if (cells != null) {
> +          result.put(col, cells);
> +        }
> +      }
> +      
>        if (result == null || result.size() == 0) {
>          doNotFound(response, "Row not found!");
>        } else {
>          switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
>          case XML:
> -          outputRowXml(response, result);
> +          outputRowWithMultiVersionsXml(response, result);
>            break;
>          case MIME:
>          default:
> -          doNotAcceptable(response, "Unsupported Accept Header Content: " +
> -            request.getHeader(CONTENT_TYPE));
> +          doNotAcceptable(response, "Unsupported Accept Header Content: "
> +              + request.getHeader(CONTENT_TYPE));
>          }
> -      }
> +      }      
>      } else {
> -      Map<byte [], Cell> prefiltered_result = table.getRow(Bytes.toBytes(row));
> -    
> -      if (prefiltered_result == null || prefiltered_result.size() == 0) {
> +      Map<byte[], Cell> result = timestampStr == null ? table.getRow(row, columns) : table.getRow(row, columns, Long.parseLong(timestampStr));
> +      if (result == null || result.size() == 0) {
>          doNotFound(response, "Row not found!");
>        } else {
> -        // create a Set from the columns requested so we can
> -        // efficiently filter the actual found columns
> -        Set<String> requested_columns_set = new HashSet<String>();
> -        for(int i = 0; i < columns.length; i++){
> -          requested_columns_set.add(columns[i]);
> -        }
> -  
> -        // output map that will contain the filtered results
> -        Map<byte [], Cell> m =
> -          new TreeMap<byte [], Cell>(Bytes.BYTES_COMPARATOR);
> -
> -        // get an array of all the columns retrieved
> -        Set<byte []> columns_retrieved = prefiltered_result.keySet();
> -
> -        // copy over those cells with requested column names
> -        for(byte [] current_column: columns_retrieved) {
> -          if (requested_columns_set.contains(Bytes.toString(current_column))) {
> -            m.put(current_column, prefiltered_result.get(current_column));            
> -          }
> -        }
> -        
>          switch (ContentType.getContentType(request.getHeader(ACCEPT))) {
> -          case XML:
> -            outputRowXml(response, m);
> -            break;
> -          case MIME:
> -          default:
> -            doNotAcceptable(response, "Unsupported Accept Header Content: " +
> -              request.getHeader(CONTENT_TYPE));
> +        case XML:
> +          outputRowXml(response, result);
> +          break;
> +        case MIME:
> +        default:
> +          doNotAcceptable(response, "Unsupported Accept Header Content: "
> +              + request.getHeader(CONTENT_TYPE));
>          }
>        }
>      }
> @@ -167,6 +166,18 @@
>      outputter.getWriter().close();
>    }
>    
> +  private void outputRowWithMultiVersionsXml(final HttpServletResponse response,
> +      final Map<byte[], Cell[]> result) 
> +  throws IOException {
> +    setResponseHeader(response, result.size() > 0? 200: 204,
> +        ContentType.XML.toString());
> +    XMLOutputter outputter = getXMLOutputter(response.getWriter());
> +    outputter.startTag(ROW);
> +    outputColumnsWithMultiVersionsXml(outputter, result);
> +    outputter.endTag();
> +    outputter.endDocument();
> +    outputter.getWriter().close();   
> +  }
>    /*
>     * @param response
>     * @param result
>
> Modified: hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java
> URL: http://svn.apache.org/viewvc/hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java?rev=690637&r1=690636&r2=690637&view=diff
> ==============================================================================
> --- hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java (original)
> +++ hadoop/hbase/trunk/src/test/org/apache/hadoop/hbase/TestCompare.java Sat Aug 30 21:48:57 2008
> @@ -54,6 +54,43 @@
>    }
>    
>    /**
> +   * Tests cases where rows keys have characters below the ','.
> +   * See HBASE-832
> +   */
> +  public void testHStoreKeyBorderCases() {
> +    HRegionInfo info = new HRegionInfo(new HTableDescriptor("testtable"),
> +        HConstants.EMPTY_BYTE_ARRAY,HConstants.EMPTY_BYTE_ARRAY);
> +    HStoreKey rowA = new HStoreKey("testtable,www.hbase.org/,1234",
> +        "", Long.MAX_VALUE, info);
> +    HStoreKey rowB = new HStoreKey("testtable,www.hbase.org/%20,99999",
> +        "", Long.MAX_VALUE, info);
> +
> +    assertTrue(rowA.compareTo(rowB) > 0);
> +
> +    rowA = new HStoreKey("testtable,www.hbase.org/,1234",
> +        "", Long.MAX_VALUE, HRegionInfo.FIRST_META_REGIONINFO);
> +    rowB = new HStoreKey("testtable,www.hbase.org/%20,99999",
> +        "", Long.MAX_VALUE, HRegionInfo.FIRST_META_REGIONINFO);
> +
> +    assertTrue(rowA.compareTo(rowB) < 0);
> +
> +    rowA = new HStoreKey("testtable,,1234",
> +        "", Long.MAX_VALUE, HRegionInfo.FIRST_META_REGIONINFO);
> +    rowB = new HStoreKey("testtable,$www.hbase.org/,99999",
> +        "", Long.MAX_VALUE, HRegionInfo.FIRST_META_REGIONINFO);
> +
> +    assertTrue(rowA.compareTo(rowB) < 0);
> +
> +    rowA = new HStoreKey(".META.,testtable,www.hbase.org/,1234,4321",
> +        "", Long.MAX_VALUE, HRegionInfo.ROOT_REGIONINFO);
> +    rowB = new HStoreKey(".META.,testtable,www.hbase.org/%20,99999,99999",
> +        "", Long.MAX_VALUE, HRegionInfo.ROOT_REGIONINFO);
> +
> +    assertTrue(rowA.compareTo(rowB) > 0);
> +  }
> +
> +  
> +  /**
>     * Sort of HRegionInfo.
>     */
>    public void testHRegionInfo() {
>
>
>