@Override
 public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) {
   // This serializes the Map. The format is as follows
   // Map size(VInt ie. 1 to 5 bytes) +
   // ( key length [VInt ie. 1 to 5 bytes] + key bytes + value [VInt ie. 1 to 5 bytes] )*
   buffer = new byte[countMapSerializationSize()];
   int offset = 0;
   offset += ByteUtil.vintToBytes(buffer, offset, this.valueVsCount.size());
   for (Entry<ImmutableBytesPtr, Integer> entry : this.valueVsCount.entrySet()) {
     ImmutableBytesPtr key = entry.getKey();
     offset += ByteUtil.vintToBytes(buffer, offset, key.getLength());
     System.arraycopy(key.get(), key.getOffset(), buffer, offset, key.getLength());
     offset += key.getLength();
     offset += ByteUtil.vintToBytes(buffer, offset, entry.getValue().intValue());
   }
   ptr.set(buffer, 0, offset);
   return true;
 }
  @Test
  public void testNegativeCompareNegativeValue() throws Exception {
    String query = "SELECT string_key FROM HBASE_NATIVE WHERE uint_key > 100000";
    String url =
        PHOENIX_JDBC_URL
            + ";"
            + PhoenixRuntime.CURRENT_SCN_ATTRIB
            + "="
            + (ts + 7); // Run query at timestamp 7
    Properties props = new Properties(TEST_PROPERTIES);
    PhoenixConnection conn =
        DriverManager.getConnection(url, props).unwrap(PhoenixConnection.class);
    HTableInterface hTable =
        conn.getQueryServices()
            .getTable(SchemaUtil.getTableNameAsBytes(HBASE_NATIVE_SCHEMA_NAME, HBASE_NATIVE));

    List<Row> mutations = new ArrayList<Row>();
    byte[] family = Bytes.toBytes("1");
    byte[] uintCol = Bytes.toBytes("UINT_COL");
    byte[] ulongCol = Bytes.toBytes("ULONG_COL");
    byte[] key;
    Put put;

    // Need to use native APIs because the Phoenix APIs wouldn't let you insert a
    // negative number for an unsigned type
    key = ByteUtil.concat(Bytes.toBytes(-10), Bytes.toBytes(100L), Bytes.toBytes("e"));
    put = new Put(key);
    // Insert at later timestamp than other queries in this test are using, so that
    // we don't affect them
    put.add(family, uintCol, ts + 6, Bytes.toBytes(10));
    put.add(family, ulongCol, ts + 6, Bytes.toBytes(100L));
    put.add(family, QueryConstants.EMPTY_COLUMN_BYTES, ts + 6, ByteUtil.EMPTY_BYTE_ARRAY);
    mutations.add(put);
    hTable.batch(mutations);

    // Demonstrates weakness of HBase Bytes serialization. Negative numbers
    // show up as bigger than positive numbers
    PreparedStatement statement = conn.prepareStatement(query);
    ResultSet rs = statement.executeQuery();
    assertTrue(rs.next());
    assertEquals("e", rs.getString(1));
    assertFalse(rs.next());
  }
  @edu.umd.cs.findbugs.annotations.SuppressWarnings(
      value = "QBA_QUESTIONABLE_BOOLEAN_ASSIGNMENT",
      justification = "Assignment designed to work this way.")
  private ReturnCode navigate(
      final byte[] currentKey, int offset, int length, Terminate terminate) {
    int nSlots = slots.size();
    // First check to see if we're in-range until we reach our end key
    if (endKeyLength > 0) {
      if (Bytes.compareTo(currentKey, offset, length, endKey, 0, endKeyLength) < 0) {
        return ReturnCode.INCLUDE;
      }

      // If key range of last slot is a single key, we can increment our position
      // since we know we'll be past the current row after including it.
      if (slots.get(nSlots - 1).get(position[nSlots - 1]).isSingleKey()) {
        if (nextPosition(nSlots - 1) < 0) {
          // Current row will be included, but we have no more
          isDone = true;
          return ReturnCode.NEXT_ROW;
        }
      } else {
        // Reset the positions to zero from the next slot after the earliest ranged slot, since the
        // next key could be bigger at this ranged slot, and smaller than the current position of
        // less significant slots.
        int earliestRangeIndex = nSlots - 1;
        for (int i = 0; i < nSlots; i++) {
          if (!slots.get(i).get(position[i]).isSingleKey()) {
            earliestRangeIndex = i;
            break;
          }
        }
        Arrays.fill(position, earliestRangeIndex + 1, position.length, 0);
      }
    }
    endKeyLength = 0;

    // We could have included the previous
    if (isDone) {
      return ReturnCode.NEXT_ROW;
    }

    int i = 0;
    boolean seek = false;
    int earliestRangeIndex = nSlots - 1;
    ptr.set(currentKey, offset, length);
    schema.first(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET);
    while (true) {
      // Increment to the next range while the upper bound of our current slot is less than our
      // current key
      while (position[i] < slots.get(i).size()
          && slots.get(i).get(position[i]).compareUpperToLowerBound(ptr) < 0) {
        position[i]++;
      }
      Arrays.fill(position, i + 1, position.length, 0);
      if (position[i] >= slots.get(i).size()) {
        // Our current key is bigger than the last range of the current slot.
        // If navigating after current key, backtrack and increment the key of the previous slot
        // values.
        // If navigating to current key, just return
        if (terminate == Terminate.AT) {
          return ReturnCode.SEEK_NEXT_USING_HINT;
        }
        if (i == 0) {
          isDone = true;
          return ReturnCode.NEXT_ROW;
        }
        // Increment key and backtrack until in range. We know at this point that we'll be
        // issuing a seek next hint.
        seek = true;
        Arrays.fill(position, i, position.length, 0);
        i--;
        // If we're positioned at a single key, no need to copy the current key and get the next key
        // .
        // Instead, just increment to the next key and continue.
        boolean incremented = false;
        while (i >= 0
            && slots.get(i).get(position[i]).isSingleKey()
            && (incremented = true)
            && (position[i] = (position[i] + 1) % slots.get(i).size()) == 0) {
          i--;
          incremented = false;
        }
        if (i < 0) {
          isDone = true;
          return ReturnCode.NEXT_ROW;
        }
        if (incremented) {
          // Continue the loop after setting the start key, because our start key maybe smaller than
          // the current key, so we'll end up incrementing the start key until it's bigger than the
          // current key.
          setStartKey();
          ptr.set(ptr.get(), offset, length);
          // Reinitialize iterator to be positioned at previous slot position
          schema.setAccessor(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET);
        } else {
          int currentLength = setStartKey(ptr, offset, i + 1);
          // From here on, we use startKey as our buffer with offset reset to 0
          // We've copied the part of the current key above that we need into startKey
          ptr.set(startKey, offset = 0, length = startKeyLength);
          // Reinitialize iterator to be positioned at previous slot position
          // TODO: a schema.previous would potentially be more efficient
          schema.setAccessor(ptr, i, ValueBitSet.EMPTY_VALUE_BITSET);
          // Do nextKey after setting the accessor b/c otherwise the null byte may have
          // been incremented causing us not to find it
          ByteUtil.nextKey(startKey, currentLength);
        }
      } else if (slots.get(i).get(position[i]).compareLowerToUpperBound(ptr) > 0) {
        // Our current key is less than the lower range of the current position in the current slot.
        // Seek to the lower range, since it's bigger than the current key
        setStartKey(ptr, offset, i);
        return ReturnCode.SEEK_NEXT_USING_HINT;
      } else { // We're in range, check the next slot
        if (!slots.get(i).get(position[i]).isSingleKey() && i < earliestRangeIndex) {
          earliestRangeIndex = i;
        }
        i++;
        // If we're past the last slot or we know we're seeking to the next (in
        // which case the previously updated slot was verified to be within the
        // range, so we don't need to check the rest of the slots. If we were
        // to check the rest of the slots, we'd get into trouble because we may
        // have a null byte that was incremented which screws up our schema.next call)
        if (i >= nSlots || seek) {
          break;
        }
        // If we run out of slots in our key, it means we have a partial key. In this
        // case, we seek to the next full key after this one.
        // TODO: test for this
        if (schema.next(ptr, i, offset + length, ValueBitSet.EMPTY_VALUE_BITSET) == null) {
          setStartKey(ptr, offset, i);
          return ReturnCode.SEEK_NEXT_USING_HINT;
        }
      }
    }

    if (seek) {
      return ReturnCode.SEEK_NEXT_USING_HINT;
    }
    // Else, we're in range for all slots and can include this row plus all rows
    // up to the upper range of our last slot. We do this for ranges and single keys
    // since we potentially have multiple key values for the same row key.
    setEndKey(ptr, offset, slots.size() - 1);
    return ReturnCode.INCLUDE;
  }
  private static void initTableValues() throws Exception {
    ConnectionQueryServices services = driver.getConnectionQueryServices(getUrl(), TEST_PROPERTIES);
    HTableInterface hTable =
        services.getTable(SchemaUtil.getTableNameAsBytes(HBASE_NATIVE_SCHEMA_NAME, HBASE_NATIVE));
    try {
      // Insert rows using standard HBase mechanism with standard HBase "types"
      List<Row> mutations = new ArrayList<Row>();
      byte[] family = Bytes.toBytes("1");
      byte[] uintCol = Bytes.toBytes("UINT_COL");
      byte[] ulongCol = Bytes.toBytes("ULONG_COL");
      byte[] key, bKey;
      Put put;

      key = ByteUtil.concat(Bytes.toBytes(10), Bytes.toBytes(100L), Bytes.toBytes("a"));
      put = new Put(key);
      put.add(family, uintCol, ts - 2, Bytes.toBytes(5));
      put.add(family, ulongCol, ts - 2, Bytes.toBytes(50L));
      mutations.add(put);
      put = new Put(key);
      put.add(family, uintCol, ts, Bytes.toBytes(10));
      put.add(family, ulongCol, ts, Bytes.toBytes(100L));
      mutations.add(put);

      bKey = key = ByteUtil.concat(Bytes.toBytes(20), Bytes.toBytes(200L), Bytes.toBytes("b"));
      put = new Put(key);
      put.add(family, uintCol, ts - 4, Bytes.toBytes(5000));
      put.add(family, ulongCol, ts - 4, Bytes.toBytes(50000L));
      mutations.add(put);
      @SuppressWarnings(
          "deprecation") // FIXME: Remove when unintentionally deprecated method is fixed
                         // (HBASE-7870).
      // FIXME: the version of the Delete constructor without the lock args was introduced
      // in 0.94.4, thus if we try to use it here we can no longer use the 0.94.2 version
      // of the client.
      Delete del = new Delete(key, ts - 2, null);
      mutations.add(del);
      put = new Put(key);
      put.add(family, uintCol, ts, Bytes.toBytes(2000));
      put.add(family, ulongCol, ts, Bytes.toBytes(20000L));
      mutations.add(put);

      key = ByteUtil.concat(Bytes.toBytes(30), Bytes.toBytes(300L), Bytes.toBytes("c"));
      put = new Put(key);
      put.add(family, uintCol, ts, Bytes.toBytes(3000));
      put.add(family, ulongCol, ts, Bytes.toBytes(30000L));
      mutations.add(put);

      key = ByteUtil.concat(Bytes.toBytes(40), Bytes.toBytes(400L), Bytes.toBytes("d"));
      put = new Put(key);
      put.add(family, uintCol, ts, Bytes.toBytes(4000));
      put.add(family, ulongCol, ts, Bytes.toBytes(40000L));
      mutations.add(put);

      hTable.batch(mutations);

      Result r = hTable.get(new Get(bKey));
      assertFalse(r.isEmpty());
    } finally {
      hTable.close();
    }
    // Create Phoenix table after HBase table was created through the native APIs
    // The timestamp of the table creation must be later than the timestamp of the data
    ensureTableCreated(getUrl(), HBASE_NATIVE, null, ts + 1);
  }