Index: hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java =================================================================== --- hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java (revision 1352425) +++ hbase-common/src/main/java/org/apache/hadoop/hbase/KeyValue.java (working copy) @@ -2413,26 +2413,39 @@ } /** - * Compare column, timestamp, and key type (everything except the row). - * This method is used both in the normal comparator and the "same-prefix" - * comparator. Note that we are assuming that row portions of both KVs have - * already been parsed and found identical, and we don't validate that - * assumption here. - * @param commonPrefix the length of the common prefix of the two - * key-values being compared, including row length and row + * Compare columnFamily, qualifier, timestamp, and key type (everything + * except the row). This method is used both in the normal comparator and + * the "same-prefix" comparator. Note that we are assuming that row portions + * of both KVs have already been parsed and found identical, and we don't + * validate that assumption here. + * @param commonPrefix + * the length of the common prefix of the two key-values being + * compared, including row length and row */ private int compareWithoutRow(int commonPrefix, byte[] left, int loffset, int llength, byte[] right, int roffset, int rlength, short rowlength) { - // Compare column family. Start comparing past row and family length. - int lcolumnoffset = ROW_LENGTH_SIZE + FAMILY_LENGTH_SIZE + - rowlength + loffset; - int rcolumnoffset = ROW_LENGTH_SIZE + FAMILY_LENGTH_SIZE + - rowlength + roffset; - int lcolumnlength = llength - TIMESTAMP_TYPE_SIZE - - (lcolumnoffset - loffset); - int rcolumnlength = rlength - TIMESTAMP_TYPE_SIZE - - (rcolumnoffset - roffset); + /*** + * KeyValue Format and commonLength: + * |_keyLen_|_valLen_|_rowLen_|_rowKey_|_famiLen_|_fami_|_Quali_|.... + * ------------------|-------commonLength--------|-------------- + */ + int commonLength = ROW_LENGTH_SIZE + FAMILY_LENGTH_SIZE + rowlength; + int lfamilyoffset = commonLength + loffset; + int rfamilyoffset = commonLength + roffset; + // Column family length. + int lfamilylength = left[lfamilyoffset - 1]; + int rfamilylength = right[rfamilyoffset - 1]; + + int lqualifieroffset = lfamilyoffset + lfamilylength; + int rqualifieroffset = rfamilyoffset + rfamilylength; + + // Qualifier length. + int lqualifierlength = llength - TIMESTAMP_TYPE_SIZE - commonLength + - lfamilylength; + int rqualifierlength = rlength - TIMESTAMP_TYPE_SIZE - commonLength + - rfamilylength; + // If row matches, and no column in the 'left' AND put type is 'minimum', // then return that left is larger than right. @@ -2447,28 +2460,45 @@ // If the column is not specified, the "minimum" key type appears the // latest in the sorted order, regardless of the timestamp. This is used // for specifying the last key/value in a given row, because there is no - // "lexicographically last column" (it would be infinitely long). The + // "lexicographically last column" (it would be infinitely long). The // "maximum" key type does not need this behavior. - if (lcolumnlength == 0 && ltype == Type.Minimum.getCode()) { + if (lfamilylength == 0 && lqualifierlength == 0 + && ltype == Type.Minimum.getCode()) { // left is "bigger", i.e. it appears later in the sorted order return 1; } - if (rcolumnlength == 0 && rtype == Type.Minimum.getCode()) { + if (rfamilylength == 0 && rqualifierlength == 0 + && rtype == Type.Minimum.getCode()) { return -1; } - int common = 0; + int common = 0, commonFami = 0, commonQuali = 0; if (commonPrefix > 0) { - common = Math.max(0, commonPrefix - - rowlength - ROW_LENGTH_SIZE - FAMILY_LENGTH_SIZE); - common = Math.min(common, Math.min(lcolumnlength, rcolumnlength)); + common = Math.max(0, commonPrefix - rowlength - ROW_LENGTH_SIZE + - FAMILY_LENGTH_SIZE); + if (common > 0) { + commonFami = Math.min(common, Math.min(lfamilylength, rfamilylength)); + common -= commonFami; + if (common > 0) { + commonQuali = Math.min(common, + Math.min(lqualifierlength, rqualifierlength)); + } + } } + // Compare column family. + final int comparisonFamily = Bytes.compareTo(left, lfamilyoffset + + commonFami, lfamilylength - commonFami, right, rfamilyoffset + + commonFami, rfamilylength - commonFami); + if (comparisonFamily != 0) { + return comparisonFamily; + } - final int comparisonResult = Bytes.compareTo( - left, lcolumnoffset + common, lcolumnlength - common, - right, rcolumnoffset + common, rcolumnlength - common); - if (comparisonResult != 0) { - return comparisonResult; + // Compare qualifier. + final int comparisonQualifier = Bytes.compareTo(left, lqualifieroffset + + commonQuali, lqualifierlength - commonQuali, right, + rqualifieroffset + commonQuali, rqualifierlength - commonQuali); + if (comparisonQualifier != 0) { + return comparisonQualifier; } return compareTimestampAndType(left, loffset, llength, right, roffset, Index: hbase-server/src/test/java/org/apache/hadoop/hbase/TestKeyValue.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/TestKeyValue.java (revision 1352425) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/TestKeyValue.java (working copy) @@ -341,6 +341,65 @@ assertTrue(cmp > 0); } + private void assertKVLessWithoutRow(KeyValue.KeyComparator c, int common, KeyValue less, + KeyValue greater) { + int cmp = c.compareIgnoringPrefix(common, less.getBuffer(), less.getOffset() + + KeyValue.ROW_OFFSET, less.getKeyLength(), greater.getBuffer(), + greater.getOffset() + KeyValue.ROW_OFFSET, greater.getKeyLength()); + assertTrue(cmp < 0); + cmp = c.compareIgnoringPrefix(common, greater.getBuffer(), greater.getOffset() + + KeyValue.ROW_OFFSET, greater.getKeyLength(), less.getBuffer(), + less.getOffset() + KeyValue.ROW_OFFSET, less.getKeyLength()); + assertTrue(cmp > 0); + } + + public void testCompareWithoutRow() { + final KeyValue.KeyComparator c = KeyValue.KEY_COMPARATOR; + byte[] row = Bytes.toBytes("row"); + + byte[] fa = Bytes.toBytes("fa"); + byte[] fami = Bytes.toBytes("fami"); + byte[] fami1 = Bytes.toBytes("fami1"); + + byte[] qual0 = Bytes.toBytes(""); + byte[] qual1 = Bytes.toBytes("qf1"); + byte[] qual2 = Bytes.toBytes("qf2"); + long ts = 1; + + // 'fa:' + KeyValue kv_0 = new KeyValue(row, fa, qual0, ts, Type.Put); + // 'fami:' + KeyValue kv0_0 = new KeyValue(row, fami, qual0, ts, Type.Put); + // 'fami:qf1' + KeyValue kv0_1 = new KeyValue(row, fami, qual1, ts, Type.Put); + // 'fami:qf2' + KeyValue kv0_2 = new KeyValue(row, fami, qual2, ts, Type.Put); + // 'fami1:' + KeyValue kv1_0 = new KeyValue(row, fami1, qual0, ts, Type.Put); + + // 'fami:qf1' < 'fami:qf2' + assertKVLessWithoutRow(c, 0, kv0_1, kv0_2); + // 'fami:qf1' < 'fami1:' + assertKVLessWithoutRow(c, 0, kv0_1, kv1_0); + + // Test comparison by skipping the same prefix bytes. + /*** + * KeyValue Format and commonLength: + * |_keyLen_|_valLen_|_rowLen_|_rowKey_|_famiLen_|_fami_|_Quali_|.... + * ------------------|-------commonLength--------|-------------- + */ + int commonLength = KeyValue.ROW_LENGTH_SIZE + KeyValue.FAMILY_LENGTH_SIZE + + row.length; + // 'fa:' < 'fami:'. They have commonPrefix + 2 same prefix bytes. + assertKVLessWithoutRow(c, commonLength + 2, kv_0, kv0_0); + // 'fami:' < 'fami:qf1'. They have commonPrefix + 4 same prefix bytes. + assertKVLessWithoutRow(c, commonLength + 4, kv0_0, kv0_1); + // 'fami:qf1' < 'fami1:'. They have commonPrefix + 4 same prefix bytes. + assertKVLessWithoutRow(c, commonLength + 4, kv0_1, kv1_0); + // 'fami:qf1' < 'fami:qf2'. They have commonPrefix + 6 same prefix bytes. + assertKVLessWithoutRow(c, commonLength + 6, kv0_1, kv0_2); + } + public void testFirstLastOnRow() { final KVComparator c = KeyValue.COMPARATOR; long ts = 1;