/** * Copyright 2011 The Apache Software Foundation * * 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.hadoop.hbase.index; import java.io.DataInput; import java.io.DataOutput; import java.io.EOFException; import java.io.IOException; import java.util.LinkedHashSet; import java.util.Set; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.index.ColumnQualifier.ValueType; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.io.WritableComparable; import org.mortbay.log.Log; /** * An IndexSpecification should have a unique name and can be created on 1 or more columns (Here * column refers to columnfamily + qualifier) For each of such column a ColumnQualifier is provided * which takes the column details. This includes the column family name and qualifier name. The * additional columns are those columns in the main table whose value also will be captured in the * secondary index table. Index Specfication name should not start with '.' and '-'. Can contain * alphanumerics and '-','.','_'. */ public class IndexSpecification implements WritableComparable<IndexSpecification> { private byte[] name; private Set<ColumnQualifier> indexColumns = new LinkedHashSet<ColumnQualifier>(1); private ColumnQualifier lastColumn = null; private int totalValueLength = 0; private long ttl = -1; private int maxVersions = -1; // Empty constructor for serialization and deserialization. public IndexSpecification() { } /** * @param name should not start with '.' and '-'. Can contain alphanumerics and '-','.','_'. * @throws IllegalArgumentException if invalid table name is provided */ public IndexSpecification(String name) { validateIndexSpecification(Bytes.toBytes(name)); this.name = Bytes.toBytes(name); } private void validateIndexSpecification(byte[] indexSpecName) { // throws IllegalArgException if invalid table name is provided HTableDescriptor.isLegalTableName(indexSpecName); } /** * @param name should not start with '.' and '-'. Can contain alphanumerics and '-','.','_'. * @throws IllegalArgumentException if invalid table name is provided */ public IndexSpecification(byte[] name) { validateIndexSpecification(name); this.name = name; } /** * @return index name */ public String getName() { return Bytes.toString(this.name); } /** * @param cf column family * @param qualifier * @param type - If type is specified as null then by default ValueType will be taken as String. * @param maxValueLength * @throws IllegalArgumentException If column family name and/or qualifier is null or blanks.<br/> * If column family name starts with '.',contains control characters or colons. * @see ValueType */ public void addIndexColumn(HColumnDescriptor cf, String qualifier, ValueType type, int maxValueLength) throws IllegalArgumentException { type = checkForType(type); isValidFamilyAndQualifier(cf, qualifier); maxValueLength = getMaxLength(type, maxValueLength); ColumnQualifier cq = new ColumnQualifier(cf.getNameAsString(), qualifier, type, maxValueLength); isNotDuplicateEntry(cq); formMinTTL(cf); formMaxVersions(cf); internalAdd(cq); } private ValueType checkForType(ValueType type) { if (type == null) { type = ValueType.String; } return type; } private int getMaxLength(ValueType type, int maxValueLength) { if ((type == ValueType.Int || type == ValueType.Float) && maxValueLength != 4) { Log.warn("With integer or float datatypes, the maxValueLength has to be 4 bytes"); return 4; } if ((type == ValueType.Double || type == ValueType.Long) && maxValueLength != 8) { Log.warn("With Double and Long datatypes, the maxValueLength has to be 8 bytes"); return 8; } if ((type == ValueType.Short || type == ValueType.Char) && maxValueLength != 2) { Log.warn("With Short and Char datatypes, the maxValueLength has to be 2 bytes"); return 2; } if (type == ValueType.Byte && maxValueLength != 1) { Log.warn("With Byte datatype, the maxValueLength has to be 1 bytes"); return 1; } if (type == ValueType.String && maxValueLength == 0) { Log.warn("With String datatype, the minimun value length is 2"); maxValueLength = 2; } return maxValueLength; } /** * @param cf Column Family * @param qualifier Column Qualifier * @param ValuePortion vp * @param type Data Type * @param maxValueLength * @throws IllegalArgumentException */ public void addIndexColumn(HColumnDescriptor cf, String qualifier, ValuePartition vp, ValueType type, int maxValueLength) throws IllegalArgumentException { checkForType(type); isValidFamilyAndQualifier(cf, qualifier); maxValueLength = getMaxLength(type, maxValueLength); ColumnQualifier cq = new ColumnQualifier(cf.getNameAsString(), qualifier, type, maxValueLength, vp); isNotDuplicateEntry(cq); formMinTTL(cf); formMaxVersions(cf); internalAdd(cq); } private void formMinTTL(HColumnDescriptor cf) { int timeToLive = cf.getTimeToLive(); if (ttl == -1) { ttl = timeToLive; } else if (timeToLive != HConstants.FOREVER && timeToLive != -1) { if (timeToLive < ttl) { ttl = timeToLive; } } } private void formMaxVersions(HColumnDescriptor cf) { int maxVersion = cf.getMaxVersions(); if (maxVersions == -1) { maxVersions = maxVersion; } else if (maxVersion != HConstants.FOREVER && maxVersion != -1) { if (maxVersion < maxVersions) { maxVersions = maxVersion; } } } private void internalAdd(ColumnQualifier cq) { indexColumns.add(cq); lastColumn = cq; totalValueLength += cq.getMaxValueLength(); } /** * @return List of column specifiers */ public Set<ColumnQualifier> getIndexColumns() { return this.indexColumns; } /** * @param cf column family * @param qualifier * @throws IllegalArgumentException If column family name and/or qualifier is null or blanks * @throws IllegalArgumentException If column family name starts with '.',contains control * characters or colons */ private void isValidFamilyAndQualifier(HColumnDescriptor cf, String qualifier) { if (null == cf || null == qualifier) { throw new IllegalArgumentException("Column family/qualifier should not be null."); } if (StringUtils.isBlank(cf.getNameAsString()) || StringUtils.isBlank(qualifier)) { throw new IllegalArgumentException("Column family/qualifier should not be blank."); } } /** * @param ColumnQualifier to check duplicate entry */ private void isNotDuplicateEntry(ColumnQualifier c) { if (this.getIndexColumns().contains(c)) { throw new IllegalArgumentException("Duplicate column family and qualifier " + "combination should not be present."); } } /** * @param Data Input Stream * @throws IOException */ public void readFields(DataInput in) throws IOException { this.name = Bytes.readByteArray(in); try { HTableDescriptor.isLegalTableName(this.name); } catch (IllegalArgumentException e) { String msg = "Received unexpected data while parsing the column qualifiers :" + Bytes.toString(this.name) + "."; Log.warn(msg + " Could be an non-indexed table."); throw new EOFException(msg); } int indexColsSize = in.readInt(); indexColumns.clear(); for (int i = 0; i < indexColsSize; i++) { ColumnQualifier cq = new ColumnQualifier(); // Need to revisit this place. May be some other valid value though invalid // comes up. try { cq.readFields(in); } catch (IllegalArgumentException e) { throw new EOFException("Received unexpected data while parsing the column qualifiers."); } internalAdd(cq); } this.maxVersions = in.readInt(); this.ttl = in.readLong(); } /** * @param Data Output Stream * @throws IOException */ public void write(DataOutput out) throws IOException { Bytes.writeByteArray(out, this.name); out.writeInt(this.indexColumns.size()); for (ColumnQualifier cq : this.indexColumns) { cq.write(out); } out.writeInt(maxVersions); out.writeLong(ttl); } /** * @param IndexSpecification * @return int */ public int compareTo(IndexSpecification o) { return 0; } public String toString() { return "Index : " + getName() + ",Index Columns : " + indexColumns; } public boolean equals(Object obj) { if (obj instanceof IndexSpecification) { IndexSpecification other = (IndexSpecification) obj; return Bytes.equals(this.name, other.name); } return false; } public int hashCode() { return Bytes.hashCode(this.name); } public ColumnQualifier getLastColumn() { return this.lastColumn; } public boolean contains(byte[] family) { for (ColumnQualifier qual : indexColumns) { if (Bytes.equals(family, qual.getColumnFamily())) { return true; } } return false; } public boolean contains(byte[] family, byte[] qualifier) { if (qualifier == null || qualifier.length == 0) { return contains(family); } for (ColumnQualifier qual : indexColumns) { if (Bytes.equals(family, qual.getColumnFamily()) && Bytes.equals(qualifier, qual.getQualifier())) { return true; } } return false; } public int getTotalValueLength() { return totalValueLength; } /** * Return the minimum of the timeToLive specified for the column families in the specifed index * @return */ public long getTTL() { return this.ttl; } /** * Return the minimum of the maxVersion specified for the column families in the specified index * @return */ public int getMaxVersions() { return this.maxVersions; } }