/**
  * Do we need to update the index, i.e. did any of the values change?
  *
  * @param mutation
  * @param indexedColumns
  * @return
  */
 public boolean areIndexKeysModified(KVPair mutation, BitSet indexedColumns) {
   EntryDecoder newPutDecoder = new EntryDecoder();
   newPutDecoder.set(mutation.getValue());
   BitIndex updateIndex = newPutDecoder.getCurrentIndex();
   for (int i = updateIndex.nextSetBit(0); i >= 0; i = updateIndex.nextSetBit(i + 1)) {
     if (indexedColumns.get(i)) return true;
   }
   return false;
 }
  private DataResult fetchBaseRow(KVPair mutation, WriteContext ctx, BitSet indexedColumns)
      throws IOException {
    baseGet =
        SIDriver.driver()
            .getOperationFactory()
            .newDataGet(ctx.getTxn(), mutation.getRowKey(), baseGet);

    EntryPredicateFilter epf;
    if (indexedColumns != null && indexedColumns.size() > 0) {
      epf = new EntryPredicateFilter(indexedColumns);
    } else epf = EntryPredicateFilter.emptyPredicate();

    TransactionalRegion region = ctx.txnRegion();
    TxnFilter txnFilter = region.packedFilter(ctx.getTxn(), epf, false);
    baseGet.setFilter(txnFilter);
    baseResult = ctx.getRegion().get(baseGet, baseResult);
    return baseResult;
  }
  /**
   * 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());
  }