View Javadoc

1   /**
2    * Copyright 2007 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase;
21  
22  import java.io.DataInput;
23  import java.io.DataOutput;
24  import java.io.IOException;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.Map;
28  
29  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
30  import org.apache.hadoop.hbase.io.hfile.Compression;
31  import org.apache.hadoop.hbase.io.hfile.HFile;
32  import org.apache.hadoop.hbase.regionserver.StoreFile;
33  import org.apache.hadoop.hbase.regionserver.StoreFile.BloomType;
34  import org.apache.hadoop.hbase.util.Bytes;
35  import org.apache.hadoop.io.Text;
36  import org.apache.hadoop.io.WritableComparable;
37  
38  /**
39   * An HColumnDescriptor contains information about a column family such as the
40   * number of versions, compression settings, etc.
41   *
42   * It is used as input when creating a table or adding a column. Once set, the
43   * parameters that specify a column cannot be changed without deleting the
44   * column and recreating it. If there is data stored in the column, it will be
45   * deleted when the column is deleted.
46   */
47  public class HColumnDescriptor implements WritableComparable<HColumnDescriptor> {
48    // For future backward compatibility
49  
50    // Version 3 was when column names become byte arrays and when we picked up
51    // Time-to-live feature.  Version 4 was when we moved to byte arrays, HBASE-82.
52    // Version 5 was when bloom filter descriptors were removed.
53    // Version 6 adds metadata as a map where keys and values are byte[].
54    // Version 7 -- add new compression and hfile blocksize to HColumnDescriptor (HBASE-1217)
55    // Version 8 -- reintroduction of bloom filters, changed from boolean to enum
56    private static final byte COLUMN_DESCRIPTOR_VERSION = (byte)8;
57  
58    /**
59     * The type of compression.
60     * @see org.apache.hadoop.io.SequenceFile.Writer
61     * @deprecated Compression now means which compression library
62     * rather than 'what' to compress.
63     */
64    @Deprecated
65    public static enum CompressionType {
66      /** Do not compress records. */
67      NONE,
68      /** Compress values only, each separately. */
69      RECORD,
70      /** Compress sequences of records together in blocks. */
71      BLOCK
72    }
73  
74    public static final String COMPRESSION = "COMPRESSION";
75    public static final String BLOCKCACHE = "BLOCKCACHE";
76    public static final String BLOCKSIZE = "BLOCKSIZE";
77    public static final String LENGTH = "LENGTH";
78    public static final String TTL = "TTL";
79    public static final String BLOOMFILTER = "BLOOMFILTER";
80    public static final String FOREVER = "FOREVER";
81    public static final String REPLICATION_SCOPE = "REPLICATION_SCOPE";
82  
83    /**
84     * Default compression type.
85     */
86    public static final String DEFAULT_COMPRESSION =
87      Compression.Algorithm.NONE.getName();
88  
89    /**
90     * Default number of versions of a record to keep.
91     */
92    public static final int DEFAULT_VERSIONS = 3;
93  
94    /*
95     * Cache here the HCD value.
96     * Question: its OK to cache since when we're reenable, we create a new HCD?
97     */
98    private volatile Integer blocksize = null;
99  
100   /**
101    * Default setting for whether to serve from memory or not.
102    */
103   public static final boolean DEFAULT_IN_MEMORY = false;
104 
105   /**
106    * Default setting for whether to use a block cache or not.
107    */
108   public static final boolean DEFAULT_BLOCKCACHE = true;
109 
110   /**
111    * Default size of blocks in files store to the filesytem.  Use smaller for
112    * faster random-access at expense of larger indices (more memory consumption).
113    */
114   public static final int DEFAULT_BLOCKSIZE = HFile.DEFAULT_BLOCKSIZE;
115 
116   /**
117    * Default setting for whether or not to use bloomfilters.
118    */
119   public static final String DEFAULT_BLOOMFILTER = StoreFile.BloomType.NONE.toString();
120 
121   /**
122    * Default time to live of cell contents.
123    */
124   public static final int DEFAULT_TTL = HConstants.FOREVER;
125 
126   /**
127    * Default scope.
128    */
129   public static final int DEFAULT_REPLICATION_SCOPE = HConstants.REPLICATION_SCOPE_LOCAL;
130 
131   // Column family name
132   private byte [] name;
133 
134   // Column metadata
135   protected Map<ImmutableBytesWritable,ImmutableBytesWritable> values =
136     new HashMap<ImmutableBytesWritable,ImmutableBytesWritable>();
137 
138   /*
139    * Cache the max versions rather than calculate it every time.
140    */
141   private int cachedMaxVersions = -1;
142 
143   /**
144    * Default constructor. Must be present for Writable.
145    */
146   public HColumnDescriptor() {
147     this.name = null;
148   }
149 
150   /**
151    * Construct a column descriptor specifying only the family name
152    * The other attributes are defaulted.
153    *
154    * @param familyName Column family name. Must be 'printable' -- digit or
155    * letter -- and may not contain a <code>:<code>
156    */
157   public HColumnDescriptor(final String familyName) {
158     this(Bytes.toBytes(familyName));
159   }
160 
161   /**
162    * Construct a column descriptor specifying only the family name
163    * The other attributes are defaulted.
164    *
165    * @param familyName Column family name. Must be 'printable' -- digit or
166    * letter -- and may not contain a <code>:<code>
167    */
168   public HColumnDescriptor(final byte [] familyName) {
169     this (familyName == null || familyName.length <= 0?
170       HConstants.EMPTY_BYTE_ARRAY: familyName, DEFAULT_VERSIONS,
171       DEFAULT_COMPRESSION, DEFAULT_IN_MEMORY, DEFAULT_BLOCKCACHE,
172       DEFAULT_TTL, DEFAULT_BLOOMFILTER);
173   }
174 
175   /**
176    * Constructor.
177    * Makes a deep copy of the supplied descriptor.
178    * Can make a modifiable descriptor from an UnmodifyableHColumnDescriptor.
179    * @param desc The descriptor.
180    */
181   public HColumnDescriptor(HColumnDescriptor desc) {
182     super();
183     this.name = desc.name.clone();
184     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
185         desc.values.entrySet()) {
186       this.values.put(e.getKey(), e.getValue());
187     }
188   }
189 
190   /**
191    * Constructor
192    * @param familyName Column family name. Must be 'printable' -- digit or
193    * letter -- and may not contain a <code>:<code>
194    * @param maxVersions Maximum number of versions to keep
195    * @param compression Compression type
196    * @param inMemory If true, column data should be kept in an HRegionServer's
197    * cache
198    * @param blockCacheEnabled If true, MapFile blocks should be cached
199    * @param timeToLive Time-to-live of cell contents, in seconds
200    * (use HConstants.FOREVER for unlimited TTL)
201    * @param bloomFilter Bloom filter type for this column
202    *
203    * @throws IllegalArgumentException if passed a family name that is made of
204    * other than 'word' characters: i.e. <code>[a-zA-Z_0-9]</code> or contains
205    * a <code>:</code>
206    * @throws IllegalArgumentException if the number of versions is &lt;= 0
207    */
208   public HColumnDescriptor(final byte [] familyName, final int maxVersions,
209       final String compression, final boolean inMemory,
210       final boolean blockCacheEnabled,
211       final int timeToLive, final String bloomFilter) {
212     this(familyName, maxVersions, compression, inMemory, blockCacheEnabled,
213       DEFAULT_BLOCKSIZE, timeToLive, bloomFilter, DEFAULT_REPLICATION_SCOPE);
214   }
215 
216   /**
217    * Constructor
218    * @param familyName Column family name. Must be 'printable' -- digit or
219    * letter -- and may not contain a <code>:<code>
220    * @param maxVersions Maximum number of versions to keep
221    * @param compression Compression type
222    * @param inMemory If true, column data should be kept in an HRegionServer's
223    * cache
224    * @param blockCacheEnabled If true, MapFile blocks should be cached
225    * @param blocksize
226    * @param timeToLive Time-to-live of cell contents, in seconds
227    * (use HConstants.FOREVER for unlimited TTL)
228    * @param bloomFilter Bloom filter type for this column
229    * @param scope The scope tag for this column
230    *
231    * @throws IllegalArgumentException if passed a family name that is made of
232    * other than 'word' characters: i.e. <code>[a-zA-Z_0-9]</code> or contains
233    * a <code>:</code>
234    * @throws IllegalArgumentException if the number of versions is &lt;= 0
235    */
236   public HColumnDescriptor(final byte [] familyName, final int maxVersions,
237       final String compression, final boolean inMemory,
238       final boolean blockCacheEnabled, final int blocksize,
239       final int timeToLive, final String bloomFilter, final int scope) {
240     isLegalFamilyName(familyName);
241     this.name = familyName;
242 
243     if (maxVersions <= 0) {
244       // TODO: Allow maxVersion of 0 to be the way you say "Keep all versions".
245       // Until there is support, consider 0 or < 0 -- a configuration error.
246       throw new IllegalArgumentException("Maximum versions must be positive");
247     }
248     setMaxVersions(maxVersions);
249     setInMemory(inMemory);
250     setBlockCacheEnabled(blockCacheEnabled);
251     setTimeToLive(timeToLive);
252     setCompressionType(Compression.Algorithm.
253       valueOf(compression.toUpperCase()));
254     setBloomFilterType(StoreFile.BloomType.
255       valueOf(bloomFilter.toUpperCase()));
256     setBlocksize(blocksize);
257     setScope(scope);
258   }
259 
260   /**
261    * @param b Family name.
262    * @return <code>b</code>
263    * @throws IllegalArgumentException If not null and not a legitimate family
264    * name: i.e. 'printable' and ends in a ':' (Null passes are allowed because
265    * <code>b</code> can be null when deserializing).  Cannot start with a '.'
266    * either.
267    */
268   public static byte [] isLegalFamilyName(final byte [] b) {
269     if (b == null) {
270       return b;
271     }
272     if (b[0] == '.') {
273       throw new IllegalArgumentException("Family names cannot start with a " +
274         "period: " + Bytes.toString(b));
275     }
276     for (int i = 0; i < b.length; i++) {
277       if (Character.isISOControl(b[i]) || b[i] == ':') {
278         throw new IllegalArgumentException("Illegal character <" + b[i] +
279           ">. Family names cannot contain control characters or colons: " +
280           Bytes.toString(b));
281       }
282     }
283     return b;
284   }
285 
286   /**
287    * @return Name of this column family
288    */
289   public byte [] getName() {
290     return name;
291   }
292 
293   /**
294    * @return Name of this column family
295    */
296   public String getNameAsString() {
297     return Bytes.toString(this.name);
298   }
299 
300   /**
301    * @param key The key.
302    * @return The value.
303    */
304   public byte[] getValue(byte[] key) {
305     ImmutableBytesWritable ibw = values.get(new ImmutableBytesWritable(key));
306     if (ibw == null)
307       return null;
308     return ibw.get();
309   }
310 
311   /**
312    * @param key The key.
313    * @return The value as a string.
314    */
315   public String getValue(String key) {
316     byte[] value = getValue(Bytes.toBytes(key));
317     if (value == null)
318       return null;
319     return Bytes.toString(value);
320   }
321 
322   /**
323    * @return All values.
324    */
325   public Map<ImmutableBytesWritable,ImmutableBytesWritable> getValues() {
326     return Collections.unmodifiableMap(values);
327   }
328 
329   /**
330    * @param key The key.
331    * @param value The value.
332    */
333   public void setValue(byte[] key, byte[] value) {
334     values.put(new ImmutableBytesWritable(key),
335       new ImmutableBytesWritable(value));
336   }
337 
338   /**
339    * @param key Key whose key and value we're to remove from HCD parameters.
340    */
341   public void remove(final byte [] key) {
342     values.remove(new ImmutableBytesWritable(key));
343   }
344 
345   /**
346    * @param key The key.
347    * @param value The value.
348    */
349   public void setValue(String key, String value) {
350     setValue(Bytes.toBytes(key), Bytes.toBytes(value));
351   }
352 
353   /** @return compression type being used for the column family */
354   public Compression.Algorithm getCompression() {
355     String n = getValue(COMPRESSION);
356     return Compression.Algorithm.valueOf(n.toUpperCase());
357   }
358 
359   /** @return maximum number of versions */
360   public synchronized int getMaxVersions() {
361     if (this.cachedMaxVersions == -1) {
362       String value = getValue(HConstants.VERSIONS);
363       this.cachedMaxVersions = (value != null)?
364         Integer.valueOf(value).intValue(): DEFAULT_VERSIONS;
365     }
366     return this.cachedMaxVersions;
367   }
368 
369   /**
370    * @param maxVersions maximum number of versions
371    */
372   public void setMaxVersions(int maxVersions) {
373     setValue(HConstants.VERSIONS, Integer.toString(maxVersions));
374   }
375 
376   /**
377    * @return Blocksize.
378    */
379   public synchronized int getBlocksize() {
380     if (this.blocksize == null) {
381       String value = getValue(BLOCKSIZE);
382       this.blocksize = (value != null)?
383         Integer.decode(value): Integer.valueOf(DEFAULT_BLOCKSIZE);
384     }
385     return this.blocksize.intValue();
386   }
387 
388   /**
389    * @param s
390    */
391   public void setBlocksize(int s) {
392     setValue(BLOCKSIZE, Integer.toString(s));
393     this.blocksize = null;
394   }
395 
396   /**
397    * @return Compression type setting.
398    */
399   public Compression.Algorithm getCompressionType() {
400     return getCompression();
401   }
402 
403   /**
404    * Compression types supported in hbase.
405    * LZO is not bundled as part of the hbase distribution.
406    * See <a href="http://wiki.apache.org/hadoop/UsingLzoCompression">LZO Compression</a>
407    * for how to enable it.
408    * @param type Compression type setting.
409    */
410   public void setCompressionType(Compression.Algorithm type) {
411     String compressionType;
412     switch (type) {
413       case LZO: compressionType = "LZO"; break;
414       case GZ: compressionType = "GZ"; break;
415       default: compressionType = "NONE"; break;
416     }
417     setValue(COMPRESSION, compressionType);
418   }
419 
420   /**
421    * @return True if we are to keep all in use HRegionServer cache.
422    */
423   public boolean isInMemory() {
424     String value = getValue(HConstants.IN_MEMORY);
425     if (value != null)
426       return Boolean.valueOf(value).booleanValue();
427     return DEFAULT_IN_MEMORY;
428   }
429 
430   /**
431    * @param inMemory True if we are to keep all values in the HRegionServer
432    * cache
433    */
434   public void setInMemory(boolean inMemory) {
435     setValue(HConstants.IN_MEMORY, Boolean.toString(inMemory));
436   }
437 
438   /**
439    * @return Time-to-live of cell contents, in seconds.
440    */
441   public int getTimeToLive() {
442     String value = getValue(TTL);
443     return (value != null)? Integer.valueOf(value).intValue(): DEFAULT_TTL;
444   }
445 
446   /**
447    * @param timeToLive Time-to-live of cell contents, in seconds.
448    */
449   public void setTimeToLive(int timeToLive) {
450     setValue(TTL, Integer.toString(timeToLive));
451   }
452 
453   /**
454    * @return True if MapFile blocks should be cached.
455    */
456   public boolean isBlockCacheEnabled() {
457     String value = getValue(BLOCKCACHE);
458     if (value != null)
459       return Boolean.valueOf(value).booleanValue();
460     return DEFAULT_BLOCKCACHE;
461   }
462 
463   /**
464    * @param blockCacheEnabled True if MapFile blocks should be cached.
465    */
466   public void setBlockCacheEnabled(boolean blockCacheEnabled) {
467     setValue(BLOCKCACHE, Boolean.toString(blockCacheEnabled));
468   }
469 
470   /**
471    * @return bloom filter type used for new StoreFiles in ColumnFamily
472    */
473   public StoreFile.BloomType getBloomFilterType() {
474     String n = getValue(BLOOMFILTER);
475     if (n == null) {
476       n = DEFAULT_BLOOMFILTER;
477     }
478     return StoreFile.BloomType.valueOf(n.toUpperCase());
479   }
480 
481   /**
482    * @param toggle bloom filter type
483    */
484   public void setBloomFilterType(final StoreFile.BloomType bt) {
485     setValue(BLOOMFILTER, bt.toString());
486   }
487 
488    /**
489     * @return the scope tag
490     */
491   public int getScope() {
492     String value = getValue(REPLICATION_SCOPE);
493     if (value != null) {
494       return Integer.valueOf(value).intValue();
495     }
496     return DEFAULT_REPLICATION_SCOPE;
497   }
498 
499  /**
500   * @param scope the scope tag
501   */
502   public void setScope(int scope) {
503     setValue(REPLICATION_SCOPE, Integer.toString(scope));
504   }
505 
506   /**
507    * @see java.lang.Object#toString()
508    */
509   @Override
510   public String toString() {
511     StringBuilder s = new StringBuilder();
512     s.append('{');
513     s.append(HConstants.NAME);
514     s.append(" => '");
515     s.append(Bytes.toString(name));
516     s.append("'");
517     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
518         values.entrySet()) {
519       String key = Bytes.toString(e.getKey().get());
520       String value = Bytes.toString(e.getValue().get());
521       s.append(", ");
522       s.append(key);
523       s.append(" => '");
524       s.append(value);
525       s.append("'");
526     }
527     s.append('}');
528     return s.toString();
529   }
530 
531   /**
532    * @see java.lang.Object#equals(java.lang.Object)
533    */
534   @Override
535   public boolean equals(Object obj) {
536     if (this == obj) {
537       return true;
538     }
539     if (obj == null) {
540       return false;
541     }
542     if (!(obj instanceof HColumnDescriptor)) {
543       return false;
544     }
545     return compareTo((HColumnDescriptor)obj) == 0;
546   }
547 
548   /**
549    * @see java.lang.Object#hashCode()
550    */
551   @Override
552   public int hashCode() {
553     int result = Bytes.hashCode(this.name);
554     result ^= Byte.valueOf(COLUMN_DESCRIPTOR_VERSION).hashCode();
555     result ^= values.hashCode();
556     return result;
557   }
558 
559   // Writable
560 
561   public void readFields(DataInput in) throws IOException {
562     int version = in.readByte();
563     if (version < 6) {
564       if (version <= 2) {
565         Text t = new Text();
566         t.readFields(in);
567         this.name = t.getBytes();
568 //        if(KeyValue.getFamilyDelimiterIndex(this.name, 0, this.name.length)
569 //            > 0) {
570 //          this.name = stripColon(this.name);
571 //        }
572       } else {
573         this.name = Bytes.readByteArray(in);
574       }
575       this.values.clear();
576       setMaxVersions(in.readInt());
577       int ordinal = in.readInt();
578       setCompressionType(Compression.Algorithm.values()[ordinal]);
579       setInMemory(in.readBoolean());
580       setBloomFilterType(in.readBoolean() ? BloomType.ROW : BloomType.NONE);
581       if (getBloomFilterType() != BloomType.NONE && version < 5) {
582         // If a bloomFilter is enabled and the column descriptor is less than
583         // version 5, we need to skip over it to read the rest of the column
584         // descriptor. There are no BloomFilterDescriptors written to disk for
585         // column descriptors with a version number >= 5
586         throw new UnsupportedClassVersionError(this.getClass().getName() +
587             " does not support backward compatibility with versions older " +
588             "than version 5");
589       }
590       if (version > 1) {
591         setBlockCacheEnabled(in.readBoolean());
592       }
593       if (version > 2) {
594        setTimeToLive(in.readInt());
595       }
596     } else {
597       // version 6+
598       this.name = Bytes.readByteArray(in);
599       this.values.clear();
600       int numValues = in.readInt();
601       for (int i = 0; i < numValues; i++) {
602         ImmutableBytesWritable key = new ImmutableBytesWritable();
603         ImmutableBytesWritable value = new ImmutableBytesWritable();
604         key.readFields(in);
605         value.readFields(in);
606         
607         // in version 8, the BloomFilter setting changed from bool to enum
608         if (version < 8 && Bytes.toString(key.get()).equals(BLOOMFILTER)) {
609           value.set(Bytes.toBytes(
610               Boolean.getBoolean(Bytes.toString(value.get()))
611                 ? BloomType.ROW.toString() 
612                 : BloomType.NONE.toString()));
613         }
614 
615         values.put(key, value);
616       }
617       if (version == 6) {
618         // Convert old values.
619         setValue(COMPRESSION, Compression.Algorithm.NONE.getName());
620       }
621     }
622   }
623 
624   public void write(DataOutput out) throws IOException {
625     out.writeByte(COLUMN_DESCRIPTOR_VERSION);
626     Bytes.writeByteArray(out, this.name);
627     out.writeInt(values.size());
628     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
629         values.entrySet()) {
630       e.getKey().write(out);
631       e.getValue().write(out);
632     }
633   }
634 
635   // Comparable
636 
637   public int compareTo(HColumnDescriptor o) {
638     int result = Bytes.compareTo(this.name, o.getName());
639     if (result == 0) {
640       // punt on comparison for ordering, just calculate difference
641       result = this.values.hashCode() - o.values.hashCode();
642       if (result < 0)
643         result = -1;
644       else if (result > 0)
645         result = 1;
646     }
647     return result;
648   }
649 }