All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.phoenix.index.IndexMaintainer Maven / Gradle / Ivy

There is a newer version: 5.1.0-HBase-2.0.0.2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.phoenix.index;

import static org.apache.phoenix.schema.PTable.QualifierEncodingScheme.NON_ENCODED_QUALIFIERS;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.KeyValue.Type;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Durability;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.ByteStringer;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Pair;
import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableUtils;
import org.apache.phoenix.compile.ColumnResolver;
import org.apache.phoenix.compile.FromCompiler;
import org.apache.phoenix.compile.IndexExpressionCompiler;
import org.apache.phoenix.compile.StatementContext;
import org.apache.phoenix.coprocessor.generated.ServerCachingProtos;
import org.apache.phoenix.coprocessor.generated.ServerCachingProtos.ColumnInfo;
import org.apache.phoenix.expression.CoerceExpression;
import org.apache.phoenix.expression.Expression;
import org.apache.phoenix.expression.ExpressionType;
import org.apache.phoenix.expression.KeyValueColumnExpression;
import org.apache.phoenix.expression.LiteralExpression;
import org.apache.phoenix.expression.SingleCellColumnExpression;
import org.apache.phoenix.expression.SingleCellConstructorExpression;
import org.apache.phoenix.expression.visitor.KeyValueExpressionVisitor;
import org.apache.phoenix.hbase.index.ValueGetter;
import org.apache.phoenix.hbase.index.covered.update.ColumnReference;
import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
import org.apache.phoenix.hbase.index.util.KeyValueBuilder;
import org.apache.phoenix.jdbc.PhoenixConnection;
import org.apache.phoenix.jdbc.PhoenixStatement;
import org.apache.phoenix.parse.FunctionParseNode;
import org.apache.phoenix.parse.ParseNode;
import org.apache.phoenix.parse.SQLParser;
import org.apache.phoenix.parse.StatelessTraverseAllParseNodeVisitor;
import org.apache.phoenix.parse.UDFParseNode;
import org.apache.phoenix.query.QueryConstants;
import org.apache.phoenix.schema.AmbiguousColumnException;
import org.apache.phoenix.schema.ColumnFamilyNotFoundException;
import org.apache.phoenix.schema.ColumnNotFoundException;
import org.apache.phoenix.schema.PColumn;
import org.apache.phoenix.schema.PColumnFamily;
import org.apache.phoenix.schema.PDatum;
import org.apache.phoenix.schema.PIndexState;
import org.apache.phoenix.schema.PTable;
import org.apache.phoenix.schema.PTable.ImmutableStorageScheme;
import org.apache.phoenix.schema.PTable.IndexType;
import org.apache.phoenix.schema.PTable.QualifierEncodingScheme;
import org.apache.phoenix.schema.PTableType;
import org.apache.phoenix.schema.RowKeySchema;
import org.apache.phoenix.schema.SaltingUtil;
import org.apache.phoenix.schema.SortOrder;
import org.apache.phoenix.schema.TableRef;
import org.apache.phoenix.schema.ValueSchema;
import org.apache.phoenix.schema.ValueSchema.Field;
import org.apache.phoenix.schema.tuple.BaseTuple;
import org.apache.phoenix.schema.tuple.ValueGetterTuple;
import org.apache.phoenix.schema.types.PDataType;
import org.apache.phoenix.transaction.TransactionFactory;
import org.apache.phoenix.util.BitSet;
import org.apache.phoenix.util.ByteUtil;
import org.apache.phoenix.util.EncodedColumnsUtil;
import org.apache.phoenix.util.ExpressionUtil;
import org.apache.phoenix.util.IndexUtil;
import org.apache.phoenix.util.MetaDataUtil;
import org.apache.phoenix.util.SchemaUtil;
import org.apache.phoenix.util.TrustedByteArrayOutputStream;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * 
 * Class that builds index row key from data row key and current state of
 * row and caches any covered columns. Client-side serializes into byte array using 
 * @link #serialize(PTable, ImmutableBytesWritable)}
 * and transmits to server-side through either the 
 * {@link org.apache.phoenix.index.PhoenixIndexCodec#INDEX_PROTO_MD}
 * Mutation attribute or as a separate RPC call using 
 * {@link org.apache.phoenix.cache.ServerCacheClient})
 * 
 * 
 * @since 2.1.0
 */
public class IndexMaintainer implements Writable, Iterable {

    private static final int EXPRESSION_NOT_PRESENT = -1;
    private static final int ESTIMATED_EXPRESSION_SIZE = 8;
    
    public static IndexMaintainer create(PTable dataTable, PTable index, PhoenixConnection connection) {
        if (dataTable.getType() == PTableType.INDEX || index.getType() != PTableType.INDEX || !dataTable.getIndexes().contains(index)) {
            throw new IllegalArgumentException();
        }
        IndexMaintainer maintainer = new IndexMaintainer(dataTable, index, connection);
        return maintainer;
    }
    
    private static boolean sendIndexMaintainer(PTable index) {
        PIndexState indexState = index.getIndexState();
        return ! ( PIndexState.DISABLE == indexState || PIndexState.PENDING_ACTIVE == indexState );
    }

    public static Iterator maintainedIndexes(Iterator indexes) {
        return Iterators.filter(indexes, new Predicate() {
            @Override
            public boolean apply(PTable index) {
                return sendIndexMaintainer(index);
            }
        });
    }
    
    public static Iterator maintainedGlobalIndexes(Iterator indexes) {
        return Iterators.filter(indexes, new Predicate() {
            @Override
            public boolean apply(PTable index) {
                return sendIndexMaintainer(index) && index.getIndexType() == IndexType.GLOBAL;
            }
        });
    }
    
    public static Iterator maintainedLocalIndexes(Iterator indexes) {
        return Iterators.filter(indexes, new Predicate() {
            @Override
            public boolean apply(PTable index) {
                return sendIndexMaintainer(index) && index.getIndexType() == IndexType.LOCAL;
            }
        });
    }
    
    /**
     * For client-side to serialize all IndexMaintainers for a given table
     * @param dataTable data table
     * @param ptr bytes pointer to hold returned serialized value
     */
    public static void serialize(PTable dataTable, ImmutableBytesWritable ptr, PhoenixConnection connection) {
        List indexes = dataTable.getIndexes();
        serialize(dataTable, ptr, indexes, connection);
    }

    /**
     * For client-side to serialize all IndexMaintainers for a given table
     * @param dataTable data table
     * @param ptr bytes pointer to hold returned serialized value
     * @param indexes indexes to serialize
     */
    public static void serialize(PTable dataTable, ImmutableBytesWritable ptr,
            List indexes, PhoenixConnection connection) {
        Iterator indexesItr = maintainedIndexes(indexes.iterator());
        if ((dataTable.isImmutableRows()) || !indexesItr.hasNext()) {
            indexesItr = maintainedLocalIndexes(indexesItr);
            if (!indexesItr.hasNext()) {
                ptr.set(ByteUtil.EMPTY_BYTE_ARRAY);
                return;
            }
        }
        int nIndexes = 0;
        while (indexesItr.hasNext()) {
            nIndexes++;
            indexesItr.next();
        }
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        DataOutputStream output = new DataOutputStream(stream);
        try {
            // Encode data table salting in sign of number of indexes
            WritableUtils.writeVInt(output, nIndexes * (dataTable.getBucketNum() == null ? 1 : -1));
            // Write out data row key schema once, since it's the same for all index maintainers
            dataTable.getRowKeySchema().write(output);
            indexesItr =
                    dataTable.isImmutableRows() ? maintainedLocalIndexes(indexes.iterator())
                            : maintainedIndexes(indexes.iterator());
            while (indexesItr.hasNext()) {
                    org.apache.phoenix.coprocessor.generated.ServerCachingProtos.IndexMaintainer proto = IndexMaintainer.toProto(indexesItr.next().getIndexMaintainer(dataTable, connection));
                    byte[] protoBytes = proto.toByteArray();
                    WritableUtils.writeVInt(output, protoBytes.length);
                    output.write(protoBytes);
            }
        } catch (IOException e) {
            throw new RuntimeException(e); // Impossible
        }
        ptr.set(stream.toByteArray(), 0, stream.size());
    }
    
    /**
     * For client-side to append serialized IndexMaintainers of keyValueIndexes
     * @param dataTable data table
     * @param indexMetaDataPtr bytes pointer to hold returned serialized value
     * @param keyValueIndexes indexes to serialize
     */
    public static void serializeAdditional(PTable table, ImmutableBytesWritable indexMetaDataPtr,
            List keyValueIndexes, PhoenixConnection connection) {
        int nMutableIndexes = indexMetaDataPtr.getLength() == 0 ? 0 : ByteUtil.vintFromBytes(indexMetaDataPtr);
        int nIndexes = nMutableIndexes + keyValueIndexes.size();
        int estimatedSize = indexMetaDataPtr.getLength() + 1; // Just in case new size increases buffer
        if (indexMetaDataPtr.getLength() == 0) {
            estimatedSize += table.getRowKeySchema().getEstimatedByteSize();
        }
        for (PTable index : keyValueIndexes) {
            estimatedSize += index.getIndexMaintainer(table, connection).getEstimatedByteSize();
        }
        TrustedByteArrayOutputStream stream = new TrustedByteArrayOutputStream(estimatedSize + 1);
        DataOutput output = new DataOutputStream(stream);
        try {
            // Encode data table salting in sign of number of indexes
            WritableUtils.writeVInt(output, nIndexes * (table.getBucketNum() == null ? 1 : -1));
            // Serialize current mutable indexes, subtracting the vint size from the length
            // as its still included
            if (indexMetaDataPtr.getLength() > 0) {
                output.write(indexMetaDataPtr.get(), indexMetaDataPtr.getOffset(), indexMetaDataPtr.getLength()-WritableUtils.getVIntSize(nMutableIndexes));
            } else {
                table.getRowKeySchema().write(output);
            }
            // Serialize mutable indexes afterwards
            for (PTable index : keyValueIndexes) {
                IndexMaintainer maintainer = index.getIndexMaintainer(table, connection);
                byte[] protoBytes = IndexMaintainer.toProto(maintainer).toByteArray();
                WritableUtils.writeVInt(output, protoBytes.length);
                output.write(protoBytes);
            }
        } catch (IOException e) {
            throw new RuntimeException(e); // Impossible
        }
        indexMetaDataPtr.set(stream.getBuffer(), 0, stream.size());
    }
    
    public static List deserialize(ImmutableBytesWritable metaDataPtr,
            KeyValueBuilder builder, boolean useProtoForIndexMaintainer) {
        return deserialize(metaDataPtr.get(), metaDataPtr.getOffset(), metaDataPtr.getLength(), useProtoForIndexMaintainer);
    }
    
    public static List deserialize(byte[] buf, boolean useProtoForIndexMaintainer) {
        return deserialize(buf, 0, buf.length, useProtoForIndexMaintainer);
    }

    private static List deserialize(byte[] buf, int offset, int length, boolean useProtoForIndexMaintainer) {
        ByteArrayInputStream stream = new ByteArrayInputStream(buf, offset, length);
        DataInput input = new DataInputStream(stream);
        List maintainers = Collections.emptyList();
        try {
            int size = WritableUtils.readVInt(input);
            boolean isDataTableSalted = size < 0;
            size = Math.abs(size);
            RowKeySchema rowKeySchema = new RowKeySchema();
            rowKeySchema.readFields(input);
            maintainers = Lists.newArrayListWithExpectedSize(size);
            for (int i = 0; i < size; i++) {
                if (useProtoForIndexMaintainer) {
                  int protoSize = WritableUtils.readVInt(input);
                  byte[] b = new byte[protoSize];
                  input.readFully(b);
                  org.apache.phoenix.coprocessor.generated.ServerCachingProtos.IndexMaintainer proto = ServerCachingProtos.IndexMaintainer.parseFrom(b);
                  maintainers.add(IndexMaintainer.fromProto(proto, rowKeySchema, isDataTableSalted));
                } else {
                    IndexMaintainer maintainer = new IndexMaintainer(rowKeySchema, isDataTableSalted);
                    maintainer.readFields(input);
                    maintainers.add(maintainer);
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e); // Impossible
        }
        return maintainers;
    }
    
    private byte[] viewIndexId;
    private boolean isMultiTenant;
    // indexed expressions that are not present in the row key of the data table, the expression can also refer to a regular column
    private List indexedExpressions;
    // columns required to evaluate all expressions in indexedExpressions (this does not include columns in the data row key)
    private Set indexedColumns;
    
    // columns required to create index row i.e. indexedColumns + coveredColumns  (this does not include columns in the data row key)
    private Set allColumns;
    // TODO remove this in the next major release
    private List indexedColumnTypes;
    private RowKeyMetaData rowKeyMetaData;
    private byte[] indexTableName;
    private int nIndexSaltBuckets;
    private byte[] dataEmptyKeyValueCF;
    private ImmutableBytesPtr emptyKeyValueCFPtr;
    private int nDataCFs;
    private boolean indexWALDisabled;
    private boolean isLocalIndex;
    private boolean immutableRows;
    // Transient state
    private final boolean isDataTableSalted;
    private final RowKeySchema dataRowKeySchema;
    
    private int estimatedIndexRowKeyBytes;
    private int estimatedExpressionSize;
    private int[] dataPkPosition;
    private int maxTrailingNulls;
    private ColumnReference dataEmptyKeyValueRef;
    private boolean rowKeyOrderOptimizable;
    
    /**** START: New member variables added in 4.10 *****/ 
    private QualifierEncodingScheme encodingScheme;
    private ImmutableStorageScheme immutableStorageScheme;
    /*
     * Information for columns of data tables that are being indexed. The first part of the pair is column family name
     * and second part is the column name. The reason we need to track this state is because for certain storage schemes
     * like ImmutableStorageScheme#SINGLE_CELL_ARRAY_WITH_OFFSETS, the column for which we need to generate an index
     * table put/delete is different from the columns that are indexed in the phoenix schema. This information helps us
     * determine whether or not certain operations like DROP COLUMN should impact the index.
     */
    private Set> indexedColumnsInfo;
    /*
     * Map of covered columns where a key is column reference for a column in the data table
     * and value is column reference for corresponding column in the index table.
     */
    private Map coveredColumnsMap;
    /**** END: New member variables added in 4.10 *****/

    private IndexMaintainer(RowKeySchema dataRowKeySchema, boolean isDataTableSalted) {
        this.dataRowKeySchema = dataRowKeySchema;
        this.isDataTableSalted = isDataTableSalted;
    }
    
    private IndexMaintainer(final PTable dataTable, final PTable index, PhoenixConnection connection) {
        this(dataTable.getRowKeySchema(), dataTable.getBucketNum() != null);
        assert(dataTable.getType() == PTableType.SYSTEM || dataTable.getType() == PTableType.TABLE || dataTable.getType() == PTableType.VIEW);
        this.rowKeyOrderOptimizable = index.rowKeyOrderOptimizable();
        this.isMultiTenant = dataTable.isMultiTenant();
        this.viewIndexId = index.getViewIndexId() == null ? null : MetaDataUtil.getViewIndexIdDataType().toBytes(index.getViewIndexId());
        this.isLocalIndex = index.getIndexType() == IndexType.LOCAL;
        this.encodingScheme = index.getEncodingScheme();
        
        // null check for b/w compatibility
        this.encodingScheme = index.getEncodingScheme() == null ? QualifierEncodingScheme.NON_ENCODED_QUALIFIERS : index.getEncodingScheme();
        this.immutableStorageScheme = index.getImmutableStorageScheme() == null ? ImmutableStorageScheme.ONE_CELL_PER_COLUMN : index.getImmutableStorageScheme();
        
        byte[] indexTableName = index.getPhysicalName().getBytes();
        // Use this for the nDataSaltBuckets as we need this for local indexes
        // TODO: persist nDataSaltBuckets separately, but maintain b/w compat.
        Integer nIndexSaltBuckets = isLocalIndex ? dataTable.getBucketNum() : index.getBucketNum();
        boolean indexWALDisabled = index.isWALDisabled();
        int indexPosOffset = (index.getBucketNum() == null ? 0 : 1) + (this.isMultiTenant ? 1 : 0) + (this.viewIndexId == null ? 0 : 1);
        int nIndexColumns = index.getColumns().size() - indexPosOffset;
        int nIndexPKColumns = index.getPKColumns().size() - indexPosOffset;
        // number of expressions that are indexed that are not present in the row key of the data table
        int indexedExpressionCount = 0;
        for (int i = indexPosOffset; idataPKColumns = dataTable.getPKColumns();
            for (int i = dataPosOffset; i < dataPKColumns.size(); i++) {
                PColumn dataPKColumn = dataPKColumns.get(i);
                if (dataPKColumn.getViewConstant() != null) {
                    bitSet.set(i);
                    nDataPKColumns--;
                }
            }
        }
        this.indexTableName = indexTableName;
        this.indexedColumnTypes = Lists.newArrayListWithExpectedSize(nIndexPKColumns-nDataPKColumns);
        this.indexedExpressions = Lists.newArrayListWithExpectedSize(nIndexPKColumns-nDataPKColumns);
        this.coveredColumnsMap = Maps.newHashMapWithExpectedSize(nIndexColumns - nIndexPKColumns);
        this.nIndexSaltBuckets  = nIndexSaltBuckets == null ? 0 : nIndexSaltBuckets;
        this.dataEmptyKeyValueCF = SchemaUtil.getEmptyColumnFamily(dataTable);
        this.emptyKeyValueCFPtr = SchemaUtil.getEmptyColumnFamilyPtr(index);
        this.nDataCFs = dataTable.getColumnFamilies().size();
        this.indexWALDisabled = indexWALDisabled;
        // TODO: check whether index is immutable or not. Currently it's always false so checking
        // data table is with immutable rows or not.
        this.immutableRows = dataTable.isImmutableRows();
        int indexColByteSize = 0;
        ColumnResolver resolver = null;
        List parseNodes = new ArrayList(1);
        UDFParseNodeVisitor visitor = new UDFParseNodeVisitor();
        for (int i = indexPosOffset; i < index.getPKColumns().size(); i++) {
            PColumn indexColumn = index.getPKColumns().get(i);
            String expressionStr = IndexUtil.getIndexColumnExpressionStr(indexColumn);
            try {
                ParseNode parseNode  = SQLParser.parseCondition(expressionStr);
                parseNode.accept(visitor);
                parseNodes.add(parseNode);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        try {
            resolver = FromCompiler.getResolver(connection, new TableRef(dataTable), visitor.getUdfParseNodes());
        } catch (SQLException e) {
            throw new RuntimeException(e); // Impossible
        }
        StatementContext context = new StatementContext(new PhoenixStatement(connection), resolver);
        this.indexedColumnsInfo = Sets.newHashSetWithExpectedSize(nIndexColumns - nIndexPKColumns);
        
        IndexExpressionCompiler expressionIndexCompiler = new IndexExpressionCompiler(context);
        for (int i = indexPosOffset; i < index.getPKColumns().size(); i++) {
            PColumn indexColumn = index.getPKColumns().get(i);
            int indexPos = i - indexPosOffset;
            Expression expression = null;
            try {
                expressionIndexCompiler.reset();
                expression = parseNodes.get(indexPos).accept(expressionIndexCompiler);
            } catch (SQLException e) {
                throw new RuntimeException(e); // Impossible
            }
            if ( expressionIndexCompiler.getColumnRef()!=null ) {
            	// get the column of the data column that corresponds to this index column
	            PColumn column = IndexUtil.getDataColumn(dataTable, indexColumn.getName().getString());
	            boolean isPKColumn = SchemaUtil.isPKColumn(column);
	            if (isPKColumn) {
	                int dataPkPos = dataTable.getPKColumns().indexOf(column) - (dataTable.getBucketNum() == null ? 0 : 1) - (this.isMultiTenant ? 1 : 0);
	                this.rowKeyMetaData.setIndexPkPosition(dataPkPos, indexPos);
	                indexedColumnsInfo.add(new Pair<>((String)null, column.getName().getString()));
	            } else {
	                indexColByteSize += column.getDataType().isFixedWidth() ? SchemaUtil.getFixedByteSize(column) : ValueSchema.ESTIMATED_VARIABLE_LENGTH_SIZE;
	                try {
	                    // Surround constant with cast so that we can still know the original type. Otherwise, if we lose the type,
	                    // (for example when VARCHAR becomes CHAR), it can lead to problems in the type translation we do between data tables and indexes.
	                    if (column.isNullable() && ExpressionUtil.isConstant(expression)) {
	                        expression = CoerceExpression.create(expression, indexColumn.getDataType());
	                    }
                        this.indexedExpressions.add(expression);
                        indexedColumnsInfo.add(new Pair<>(column.getFamilyName().getString(), column.getName().getString()));
                    } catch (SQLException e) {
                        throw new RuntimeException(e); // Impossible
                    }
	            }
            }
            else {
            	indexColByteSize += expression.getDataType().isFixedWidth() ? SchemaUtil.getFixedByteSize(expression) : ValueSchema.ESTIMATED_VARIABLE_LENGTH_SIZE;
                this.indexedExpressions.add(expression);
                KeyValueExpressionVisitor kvVisitor = new KeyValueExpressionVisitor() {
                    @Override
                    public Void visit(KeyValueColumnExpression colExpression) {
                        return addDataColInfo(dataTable, colExpression);
                    }

                    @Override
                    public Void visit(SingleCellColumnExpression expression) {
                        return addDataColInfo(dataTable, expression);
                    }

                    private Void addDataColInfo(final PTable dataTable, Expression expression) {
                        Preconditions.checkArgument(expression instanceof SingleCellColumnExpression
                                || expression instanceof KeyValueColumnExpression);

                        KeyValueColumnExpression colExpression = null;
                        if (expression instanceof SingleCellColumnExpression) {
                            colExpression =
                                    ((SingleCellColumnExpression) expression).getKeyValueExpression();
                        } else {
                            colExpression = ((KeyValueColumnExpression) expression);
                        }
                        byte[] cf = colExpression.getColumnFamily();
                        byte[] cq = colExpression.getColumnQualifier();
                        try {
                            PColumn dataColumn =
                                    cf == null ? dataTable.getColumnForColumnQualifier(null, cq)
                                            : dataTable.getColumnFamily(cf)
                                                    .getPColumnForColumnQualifier(cq);
                            indexedColumnsInfo.add(new Pair<>(dataColumn.getFamilyName()
                                    .getString(), dataColumn.getName().getString()));
                        } catch (ColumnNotFoundException | ColumnFamilyNotFoundException
                                | AmbiguousColumnException e) {
                            throw new RuntimeException(e);
                        }
                        return null;
                    }
                };
                expression.accept(kvVisitor);
            }
            // set the sort order of the expression correctly
            if (indexColumn.getSortOrder() == SortOrder.DESC) {
                this.rowKeyMetaData.getDescIndexColumnBitSet().set(indexPos);
            }
        }
        this.estimatedExpressionSize = expressionIndexCompiler.getTotalNodeCount() * ESTIMATED_EXPRESSION_SIZE;
        for (int i = 0; i < index.getColumnFamilies().size(); i++) {
            PColumnFamily family = index.getColumnFamilies().get(i);
            for (PColumn indexColumn : family.getColumns()) {
                PColumn dataColumn = IndexUtil.getDataColumn(dataTable, indexColumn.getName().getString());
                byte[] dataColumnCq = dataColumn.getColumnQualifierBytes();
                byte[] indexColumnCq = indexColumn.getColumnQualifierBytes();
                this.coveredColumnsMap.put(new ColumnReference(dataColumn.getFamilyName().getBytes(), dataColumnCq), 
                        new ColumnReference(indexColumn.getFamilyName().getBytes(), indexColumnCq));
            }
        }
        this.estimatedIndexRowKeyBytes = estimateIndexRowKeyByteSize(indexColByteSize);
        initCachedState();
    }
    
    public byte[] buildRowKey(ValueGetter valueGetter, ImmutableBytesWritable rowKeyPtr, byte[] regionStartKey, byte[] regionEndKey, long ts)  {
        ImmutableBytesWritable ptr = new ImmutableBytesWritable();
        boolean prependRegionStartKey = isLocalIndex && regionStartKey != null;
        boolean isIndexSalted = !isLocalIndex && nIndexSaltBuckets > 0;
        int prefixKeyLength =
                prependRegionStartKey ? (regionStartKey.length != 0 ? regionStartKey.length
                        : regionEndKey.length) : 0; 
        TrustedByteArrayOutputStream stream = new TrustedByteArrayOutputStream(estimatedIndexRowKeyBytes + (prependRegionStartKey ? prefixKeyLength : 0));
        DataOutput output = new DataOutputStream(stream);
        try {
            // For local indexes, we must prepend the row key with the start region key
            if (prependRegionStartKey) {
                if (regionStartKey.length == 0) {
                    output.write(new byte[prefixKeyLength]);
                } else {
                    output.write(regionStartKey);
                }
            }
            if (isIndexSalted) {
                output.write(0); // will be set at end to index salt byte
            }
            // The dataRowKeySchema includes the salt byte field,
            // so we must adjust for that here.
            int dataPosOffset = isDataTableSalted ? 1 : 0 ;
            BitSet viewConstantColumnBitSet = this.rowKeyMetaData.getViewConstantColumnBitSet();
            int nIndexedColumns = getIndexPkColumnCount() - getNumViewConstants();
            int[][] dataRowKeyLocator = new int[2][nIndexedColumns];
            // Skip data table salt byte
            int maxRowKeyOffset = rowKeyPtr.getOffset() + rowKeyPtr.getLength();
            dataRowKeySchema.iterator(rowKeyPtr, ptr, dataPosOffset);
            
            if (viewIndexId != null) {
                output.write(viewIndexId);
            }
            
            if (isMultiTenant) {
                dataRowKeySchema.next(ptr, dataPosOffset, maxRowKeyOffset);
                output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
                if (!dataRowKeySchema.getField(dataPosOffset).getDataType().isFixedWidth()) {
                    output.writeByte(SchemaUtil.getSeparatorByte(rowKeyOrderOptimizable, ptr.getLength()==0, dataRowKeySchema.getField(dataPosOffset)));
                }
                dataPosOffset++;
            }
            
            // Write index row key
            for (int i = dataPosOffset; i < dataRowKeySchema.getFieldCount(); i++) {
                Boolean hasValue=dataRowKeySchema.next(ptr, i, maxRowKeyOffset);
                // Ignore view constants from the data table, as these
                // don't need to appear in the index (as they're the
                // same for all rows in this index)
                if (!viewConstantColumnBitSet.get(i)) {
                    int pos = rowKeyMetaData.getIndexPkPosition(i-dataPosOffset);
                    if (Boolean.TRUE.equals(hasValue)) {
                        dataRowKeyLocator[0][pos] = ptr.getOffset();
                        dataRowKeyLocator[1][pos] = ptr.getLength();
                    } else {
                        dataRowKeyLocator[0][pos] = 0;
                        dataRowKeyLocator[1][pos] = 0;
                    }
                } 
            }
            BitSet descIndexColumnBitSet = rowKeyMetaData.getDescIndexColumnBitSet();
            Iterator expressionIterator = indexedExpressions.iterator();
            for (int i = 0; i < nIndexedColumns; i++) {
                PDataType dataColumnType;
                boolean isNullable;
                SortOrder dataSortOrder;
                if (dataPkPosition[i] == EXPRESSION_NOT_PRESENT) {
                	Expression expression = expressionIterator.next();
                	dataColumnType = expression.getDataType();
                	dataSortOrder = expression.getSortOrder();
                    isNullable = expression.isNullable();
			expression.evaluate(new ValueGetterTuple(valueGetter, ts), ptr);
                }
                else {
                    Field field = dataRowKeySchema.getField(dataPkPosition[i]);
                    dataColumnType = field.getDataType();
                    ptr.set(rowKeyPtr.get(), dataRowKeyLocator[0][i], dataRowKeyLocator[1][i]);
                    dataSortOrder = field.getSortOrder();
                    isNullable = field.isNullable();
                }
                boolean isDataColumnInverted = dataSortOrder != SortOrder.ASC;
                PDataType indexColumnType = IndexUtil.getIndexColumnDataType(isNullable, dataColumnType);
                boolean isBytesComparable = dataColumnType.isBytesComparableWith(indexColumnType);
                boolean isIndexColumnDesc = descIndexColumnBitSet.get(i);
                if (isBytesComparable && isDataColumnInverted == isIndexColumnDesc) {
                    output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
                } else {
                    if (!isBytesComparable)  {
                        indexColumnType.coerceBytes(ptr, dataColumnType, dataSortOrder, SortOrder.getDefault());
                    }
                    if (isDataColumnInverted != isIndexColumnDesc) {
                        writeInverted(ptr.get(), ptr.getOffset(), ptr.getLength(), output);
                    } else {
                        output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
                    }
                }
                if (!indexColumnType.isFixedWidth()) {
                    output.writeByte(SchemaUtil.getSeparatorByte(rowKeyOrderOptimizable, ptr.getLength() == 0, isIndexColumnDesc ? SortOrder.DESC : SortOrder.ASC));
                }
            }
            int length = stream.size();
            int minLength = length - maxTrailingNulls;
            byte[] indexRowKey = stream.getBuffer();
            // Remove trailing nulls
            while (length > minLength && indexRowKey[length-1] == QueryConstants.SEPARATOR_BYTE) {
                length--;
            }
            if (isIndexSalted) {
                // Set salt byte
                byte saltByte = SaltingUtil.getSaltingByte(indexRowKey, SaltingUtil.NUM_SALTING_BYTES, length-SaltingUtil.NUM_SALTING_BYTES, nIndexSaltBuckets);
                indexRowKey[0] = saltByte;
            }
            return indexRowKey.length == length ? indexRowKey : Arrays.copyOf(indexRowKey, length);
        } catch (IOException e) {
            throw new RuntimeException(e); // Impossible
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
                throw new RuntimeException(e); // Impossible
            }
        }
    }

    /*
     * Build the data row key from the index row key
     */
    public byte[] buildDataRowKey(ImmutableBytesWritable indexRowKeyPtr, byte[][] viewConstants)  {
        RowKeySchema indexRowKeySchema = getIndexRowKeySchema();
        ImmutableBytesWritable ptr = new ImmutableBytesWritable();
        TrustedByteArrayOutputStream stream = new TrustedByteArrayOutputStream(estimatedIndexRowKeyBytes);
        DataOutput output = new DataOutputStream(stream);
        // Increment dataPosOffset until all have been written
        int dataPosOffset = 0;
        int viewConstantsIndex = 0;
        try {
            int indexPosOffset = !isLocalIndex && nIndexSaltBuckets > 0 ? 1 : 0;
            int maxRowKeyOffset = indexRowKeyPtr.getOffset() + indexRowKeyPtr.getLength();
            indexRowKeySchema.iterator(indexRowKeyPtr, ptr, indexPosOffset);
            if (isDataTableSalted) {
                dataPosOffset++;
                output.write(0); // will be set at end to salt byte
            }
            if (viewIndexId != null) {
                indexRowKeySchema.next(ptr, indexPosOffset++, maxRowKeyOffset);
            }
            if (isMultiTenant) {
                indexRowKeySchema.next(ptr, indexPosOffset, maxRowKeyOffset);
                output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
                if (!dataRowKeySchema.getField(dataPosOffset).getDataType().isFixedWidth()) {
                    output.writeByte(SchemaUtil.getSeparatorByte(rowKeyOrderOptimizable, ptr.getLength() == 0, dataRowKeySchema.getField(dataPosOffset)));
                }
                indexPosOffset++;
                dataPosOffset++;
            }
            indexPosOffset = (!isLocalIndex && nIndexSaltBuckets > 0 ? 1 : 0) + (isMultiTenant ? 1 : 0) + (viewIndexId == null ? 0 : 1);
            BitSet viewConstantColumnBitSet = this.rowKeyMetaData.getViewConstantColumnBitSet();
            BitSet descIndexColumnBitSet = rowKeyMetaData.getDescIndexColumnBitSet();
            for (int i = dataPosOffset; i < dataRowKeySchema.getFieldCount(); i++) {
                // Write view constants from the data table, as these
                // won't appear in the index (as they're the
                // same for all rows in this index)
                if (viewConstantColumnBitSet.get(i)) {
                    output.write(viewConstants[viewConstantsIndex++]);
                } else {
                    int pos = rowKeyMetaData.getIndexPkPosition(i-dataPosOffset);
                    Boolean hasValue=indexRowKeySchema.iterator(indexRowKeyPtr, ptr, pos + indexPosOffset+1);
                    if (Boolean.TRUE.equals(hasValue)) {
                        // Write data row key value taking into account coercion and inversion
                        // if necessary
                        Field dataField = dataRowKeySchema.getField(i);
                        Field indexField = indexRowKeySchema.getField(pos + indexPosOffset);
                        PDataType indexColumnType = indexField.getDataType();
                        PDataType dataColumnType = dataField.getDataType();
                        SortOrder dataSortOrder = dataField.getSortOrder();
                        SortOrder indexSortOrder = indexField.getSortOrder();
                        boolean isDataColumnInverted = dataSortOrder != SortOrder.ASC;
                        boolean isBytesComparable = dataColumnType.isBytesComparableWith(indexColumnType) ;
                        if (isBytesComparable && isDataColumnInverted == descIndexColumnBitSet.get(pos)) {
                            output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
                        } else {
                            if (!isBytesComparable)  {
                                dataColumnType.coerceBytes(ptr, indexColumnType, indexSortOrder, SortOrder.getDefault());
                            }
                            if (descIndexColumnBitSet.get(pos) != isDataColumnInverted) {
                                writeInverted(ptr.get(), ptr.getOffset(), ptr.getLength(), output);
                            } else {
                                output.write(ptr.get(), ptr.getOffset(), ptr.getLength());
                            }
                        }
                    }
                }
                // Write separator byte if variable length unless it's the last field in the schema
                // (but we still need to write it if it's DESC to ensure sort order is correct).
                byte sepByte = SchemaUtil.getSeparatorByte(rowKeyOrderOptimizable, ptr.getLength() == 0, dataRowKeySchema.getField(i));
                if (!dataRowKeySchema.getField(i).getDataType().isFixedWidth() && (((i+1) !=  dataRowKeySchema.getFieldCount()) || sepByte == QueryConstants.DESC_SEPARATOR_BYTE)) {
                    output.writeByte(sepByte);
                }
            }
            int length = stream.size();
            int minLength = length - maxTrailingNulls;
            byte[] dataRowKey = stream.getBuffer();
            // Remove trailing nulls
            while (length > minLength && dataRowKey[length-1] == QueryConstants.SEPARATOR_BYTE) {
                length--;
            }
            // TODO: need to capture nDataSaltBuckets instead of just a boolean. For now,
            // we store this in nIndexSaltBuckets, as we only use this function for local indexes
            // in which case nIndexSaltBuckets would never be used. Note that when we do add this
            // to be serialized, we have to add it at the end and allow for the value not being
            // there to maintain compatibility between an old client and a new server.
            if (isDataTableSalted) {
                // Set salt byte
                byte saltByte = SaltingUtil.getSaltingByte(dataRowKey, SaltingUtil.NUM_SALTING_BYTES, length-SaltingUtil.NUM_SALTING_BYTES, nIndexSaltBuckets);
                dataRowKey[0] = saltByte;
            }
            return dataRowKey.length == length ? dataRowKey : Arrays.copyOf(dataRowKey, length);
        } catch (IOException e) {
            throw new RuntimeException(e); // Impossible
        } finally {
            try {
                stream.close();
            } catch (IOException e) {
                throw new RuntimeException(e); // Impossible
            }
        }
    }
    
    /*
     * return the view index id from the index row key
     */
    public byte[] getViewIndexIdFromIndexRowKey(ImmutableBytesWritable indexRowKeyPtr) {
        assert (isLocalIndex);
        ImmutableBytesPtr ptr =
                new ImmutableBytesPtr(indexRowKeyPtr.get(),( indexRowKeyPtr.getOffset()
                        + (!isLocalIndex && nIndexSaltBuckets > 0 ? 1 : 0)), viewIndexId.length);
        return ptr.copyBytesIfNecessary();
    }

    private volatile RowKeySchema indexRowKeySchema;
    
    // We have enough information to generate the index row key schema
    private RowKeySchema generateIndexRowKeySchema() {
        int nIndexedColumns = getIndexPkColumnCount() + (isMultiTenant ? 1 : 0) + (!isLocalIndex && nIndexSaltBuckets > 0 ? 1 : 0) + (viewIndexId != null ? 1 : 0) - getNumViewConstants();
        RowKeySchema.RowKeySchemaBuilder builder = new RowKeySchema.RowKeySchemaBuilder(nIndexedColumns);
        builder.rowKeyOrderOptimizable(rowKeyOrderOptimizable);
        if (!isLocalIndex && nIndexSaltBuckets > 0) {
            builder.addField(SaltingUtil.SALTING_COLUMN, false, SortOrder.ASC);
            nIndexedColumns--;
        }
        int dataPosOffset = isDataTableSalted ? 1 : 0 ;
        if (viewIndexId != null) {
            nIndexedColumns--;
            builder.addField(new PDatum() {

                @Override
                public boolean isNullable() {
                    return false;
                }

                @Override
                public PDataType getDataType() {
                    return MetaDataUtil.getViewIndexIdDataType();
                }

                @Override
                public Integer getMaxLength() {
                    return null;
                }

                @Override
                public Integer getScale() {
                    return null;
                }

                @Override
                public SortOrder getSortOrder() {
                    return SortOrder.getDefault();
                }
                
            }, false, SortOrder.getDefault());
        }
        if (isMultiTenant) {
            Field field = dataRowKeySchema.getField(dataPosOffset++);
            builder.addField(field, field.isNullable(), field.getSortOrder());
            nIndexedColumns--;
        }
        
        Field[] indexFields = new Field[nIndexedColumns];
        BitSet viewConstantColumnBitSet = this.rowKeyMetaData.getViewConstantColumnBitSet();
        // Add Field for all data row pk columns
        for (int i = dataPosOffset; i < dataRowKeySchema.getFieldCount(); i++) {
            // Ignore view constants from the data table, as these
            // don't need to appear in the index (as they're the
            // same for all rows in this index)
            if (!viewConstantColumnBitSet.get(i)) {
                int pos = rowKeyMetaData.getIndexPkPosition(i-dataPosOffset);
                indexFields[pos] = 
                        dataRowKeySchema.getField(i);
            } 
        }
        BitSet descIndexColumnBitSet = rowKeyMetaData.getDescIndexColumnBitSet();
        Iterator expressionItr = indexedExpressions.iterator();
        for (int i = 0; i < indexFields.length; i++) {
            Field indexField = indexFields[i];
            PDataType dataTypeToBe;
            SortOrder sortOrderToBe;
            boolean isNullableToBe;
            Integer maxLengthToBe;
            Integer scaleToBe;
            if (indexField == null) {
                Expression e = expressionItr.next();
                isNullableToBe = e.isNullable();
                dataTypeToBe = IndexUtil.getIndexColumnDataType(isNullableToBe, e.getDataType());
                sortOrderToBe = descIndexColumnBitSet.get(i) ? SortOrder.DESC : SortOrder.ASC;
                maxLengthToBe = e.getMaxLength();
                scaleToBe = e.getScale();
            } else {
                isNullableToBe = indexField.isNullable();
                dataTypeToBe = IndexUtil.getIndexColumnDataType(isNullableToBe, indexField.getDataType());
                sortOrderToBe = descIndexColumnBitSet.get(i) ? SortOrder.DESC : SortOrder.ASC;
                maxLengthToBe = indexField.getMaxLength();
                scaleToBe = indexField.getScale();
            }
            final PDataType dataType = dataTypeToBe;
            final SortOrder sortOrder = sortOrderToBe;
            final boolean isNullable = isNullableToBe;
            final Integer maxLength = maxLengthToBe;
            final Integer scale = scaleToBe;
            builder.addField(new PDatum() {

                @Override
                public boolean isNullable() {
                    return isNullable;
                }

                @Override
                public PDataType getDataType() {
                    return dataType;
                }

                @Override
                public Integer getMaxLength() {
                    return maxLength;
                }

                @Override
                public Integer getScale() {
                    return scale;
                }

                @Override
                public SortOrder getSortOrder() {
                    return sortOrder;
                }
                
            }, true, sortOrder);
        }
        return builder.build();
    }
    
    private int getNumViewConstants() {
        BitSet bitSet = this.rowKeyMetaData.getViewConstantColumnBitSet();
        int num = 0;
        for(int i = 0; i




© 2015 - 2025 Weber Informatics LLC | Privacy Policy