/** * 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; }
/** * 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()); }