/**
  * because the field is NULL and it's source is the incoming mutation, we still need to accumulate
  * it. We must be careful, however, to accumulate the proper null value.
  *
  * <p>In theory, we could use a sparse encoding here--just accumulate a length 0 entry, which will
  * allow us to use a very short row key to determine nullity. However, that doesn't work
  * correctly, because doubles and floats at the end of the index might decode the row key as a
  * double, resulting in goofball answers.
  *
  * <p>Instead, we must use the dense encoding approach here. That means that we must select the
  * proper dense type based on columnTypes[i]. For most data types, this is still a length-0 array,
  * but for floats and doubles it will put the proper type into place.
  */
 private void accumulateNull(EntryAccumulator keyAccumulator, int pos, int type) {
   if (typeProvider.isScalar(type))
     keyAccumulator.addScalar(pos, SIConstants.EMPTY_BYTE_ARRAY, 0, 0);
   else if (typeProvider.isDouble(type))
     keyAccumulator.addDouble(
         pos, Encoding.encodedNullDouble(), 0, Encoding.encodedNullDoubleLength());
   else if (typeProvider.isFloat(type))
     keyAccumulator.addDouble(
         pos, Encoding.encodedNullFloat(), 0, Encoding.encodedNullFloatLength());
   else keyAccumulator.add(pos, SIConstants.EMPTY_BYTE_ARRAY, 0, 0);
 }
  public KVPair writeDirectIndex(LocatedRow locatedRow) throws IOException, StandardException {
    assert locatedRow != null : "locatedRow passed in is null";
    ExecRow execRow = locatedRow.getRow();
    getSerializers(execRow);
    EntryAccumulator keyAccumulator = getKeyAccumulator();
    keyAccumulator.reset();
    boolean hasNullKeyFields = false;
    for (int i = 0; i < execRow.nColumns(); i++) {
      if (execRow.getColumn(i + 1) == null || execRow.getColumn(i + 1).isNull()) {
        hasNullKeyFields = true;
        accumulateNull(keyAccumulator, i, indexFormatIds[i]);
      } else {
        byte[] data = serializers[i].encodeDirect(execRow.getColumn(i + 1), false);
        accumulate(
            keyAccumulator, i, indexFormatIds[i], index.getDescColumns(i), data, 0, data.length);
      }
    }
    // add the row key to the end of the index key
    byte[] srcRowKey = Encoding.encodeBytesUnsorted(locatedRow.getRowLocation().getBytes());

    EntryEncoder rowEncoder = getRowEncoder();
    MultiFieldEncoder entryEncoder = rowEncoder.getEntryEncoder();
    entryEncoder.reset();
    entryEncoder.setRawBytes(srcRowKey);
    byte[] indexValue = rowEncoder.encode();
    byte[] indexRowKey;
    if (index.getUnique()) {
      boolean nonUnique =
          index.getUniqueWithDuplicateNulls() && (hasNullKeyFields || !keyAccumulator.isFinished());
      indexRowKey = getIndexRowKey(srcRowKey, nonUnique);
    } else indexRowKey = getIndexRowKey(srcRowKey, true);
    return new KVPair(indexRowKey, indexValue, KVPair.Type.INSERT);
  }
  @Test
  public void testAlwaysAcceptEntryWorks() throws Exception {
    BitSet fields = new BitSet();
    fields.set(0);
    fields.set(2);
    EntryPredicateFilter predicateFilter = new EntryPredicateFilter(fields);
    EntryAccumulator accumulator = new ByteEntryAccumulator(predicateFilter, false, null);
    byte[] encodedOne = Encoding.encode(1);
    accumulator.add(2, encodedOne, 0, encodedOne.length);
    byte[] encodedTwo = Encoding.encode(2);
    accumulator.add(0, encodedTwo, 0, encodedTwo.length);

    byte[] bytes = accumulator.finish();
    MultiFieldDecoder decoder = MultiFieldDecoder.wrap(bytes);
    Assert.assertEquals(2, decoder.decodeNextInt());
    Assert.assertEquals(1, decoder.decodeNextInt());
  }
  /**
   * Translate the given base table record mutation into its associated, referencing index record.
   * <br>
   * Encapsulates the logic required to create an index record for a given base table record with
   * all the required discriminating and encoding rules (column is part of a PK, value is null,
   * etc).
   *
   * @param mutation KVPair containing the rowKey of the base table record for which we want to
   *     translate to the associated index. This mutation should already have its requred {@link
   *     KVPair.Type Type} set.
   * @return A KVPair representing the index record of the given base table mutation. This KVPair is
   *     suitable for performing the required modification of the index record associated with this
   *     mutation.
   * @throws IOException for encoding/decoding problems.
   */
  public KVPair translate(KVPair mutation) throws IOException {
    if (mutation == null) {
      return null;
    }

    EntryAccumulator keyAccumulator = getKeyAccumulator();
    keyAccumulator.reset();
    boolean hasNullKeyFields = false;

    /*
     * Handle index columns from the source table's primary key.
     */
    if (table.getColumnOrderingCount() > 0) {
      // we have key columns to check
      MultiFieldDecoder keyDecoder = getSrcKeyDecoder();
      keyDecoder.set(mutation.getRowKey());
      for (int i = 0; i < table.getColumnOrderingCount(); i++) {
        int sourceKeyColumnPos = table.getColumnOrdering(i);

        int indexKeyPos =
            sourceKeyColumnPos < mainColToIndexPosMap.length
                ? mainColToIndexPosMap[sourceKeyColumnPos]
                : -1;
        int offset = keyDecoder.offset();
        boolean isNull = skip(keyDecoder, table.getFormatIds(sourceKeyColumnPos));
        if (!indexedCols.get(sourceKeyColumnPos)) continue;
        if (indexKeyPos >= 0) {
          /*
           * since primary keys have an implicit NOT NULL constraint here, we don't need to check for it,
           * and isNull==true would represent a programmer error, rather than an actual state the
           * system can be in.
           */
          assert !isNull : "Programmer error: Cannot update a primary key to a null value!";
          int length = keyDecoder.offset() - offset - 1;
          /*
           * A note about sort order:
           *
           * We are in the primary key section, which means that the element is ordered in
           * ASCENDING order. In an ideal world, that wouldn't matter because
           */
          accumulate(
              keyAccumulator,
              indexKeyPos,
              table.getFormatIds(sourceKeyColumnPos),
              index.getDescColumns(indexKeyPos),
              keyDecoder.array(),
              offset,
              length);
        }
      }
    }

    /*
     * Handle non-null index columns from the source tables non-primary key columns.
     *
     * this will set indexed columns with values taken from the incoming mutation (rather than
     * backfilling them with existing values, which would occur elsewhere).
     */
    EntryDecoder rowDecoder = getSrcValueDecoder();
    rowDecoder.set(mutation.getValue());
    BitIndex bitIndex = rowDecoder.getCurrentIndex();
    MultiFieldDecoder rowFieldDecoder = rowDecoder.getEntryDecoder();
    for (int i = bitIndex.nextSetBit(0); i >= 0; i = bitIndex.nextSetBit(i + 1)) {
      if (!indexedCols.get(i)) {
        // skip non-indexed columns
        rowDecoder.seekForward(rowFieldDecoder, i);
        continue;
      }
      int keyColumnPos = i < mainColToIndexPosMap.length ? mainColToIndexPosMap[i] : -1;
      if (keyColumnPos < 0) {
        rowDecoder.seekForward(rowFieldDecoder, i);
      } else {
        int offset = rowFieldDecoder.offset();
        boolean isNull = rowDecoder.seekForward(rowFieldDecoder, i);
        hasNullKeyFields = isNull || hasNullKeyFields;
        int length;
        if (!isNull) {
          length = rowFieldDecoder.offset() - offset - 1;
          accumulate(
              keyAccumulator,
              keyColumnPos,
              table.getFormatIds(i),
              index.getDescColumns(keyColumnPos),
              rowFieldDecoder.array(),
              offset,
              length);
        } else {
          /*
           * because the field is NULL and it's source is the incoming mutation, we
           * still need to accumulate it. We must be careful, however, to accumulate the
           * proper null value.
           *
           * In theory, we could use a sparse encoding here--just accumulate a length 0 entry,
           * which will allow us to use a very short row key to determine nullity. However, that
           * doesn't work correctly, because doubles and floats at the end of the index might decode
           * the row key as a double, resulting in goofball answers.
           *
           * Instead, we must use the dense encoding approach here. That means that we must
           * select the proper dense type based on columnTypes[i]. For most data types, this is still
           * a length-0 array, but for floats and doubles it will put the proper type into place.
           */
          accumulateNull(keyAccumulator, keyColumnPos, table.getFormatIds(i));
        }
      }
    }

    /*
     * Handle NULL index columns from the source tables non-primary key columns.
     */
    for (int srcColIndex = 0; srcColIndex < mainColToIndexPosMap.length; srcColIndex++) {
      /* position of the source column within the index encoding */
      int indexColumnPosition = mainColToIndexPosMap[srcColIndex];
      if (!isSourceColumnPrimaryKey(srcColIndex)
          && indexColumnPosition >= 0
          && !bitIndex.isSet(srcColIndex)) {
        hasNullKeyFields = true;
        keyAccumulator.add(indexColumnPosition, new byte[] {}, 0, 0);
      }
    }

    // add the row key to the end of the index key
    byte[] srcRowKey = Encoding.encodeBytesUnsorted(mutation.getRowKey());

    EntryEncoder rowEncoder = getRowEncoder();
    MultiFieldEncoder entryEncoder = rowEncoder.getEntryEncoder();
    entryEncoder.reset();
    entryEncoder.setRawBytes(srcRowKey);
    byte[] indexValue = rowEncoder.encode();

    byte[] indexRowKey;
    if (index.getUnique()) {
      boolean nonUnique =
          index.getUniqueWithDuplicateNulls() && (hasNullKeyFields || !keyAccumulator.isFinished());
      indexRowKey = getIndexRowKey(srcRowKey, nonUnique);
    } else indexRowKey = getIndexRowKey(srcRowKey, true);

    return new KVPair(indexRowKey, indexValue, mutation.getType());
  }