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     setMaxVersions(desc.getMaxVersions());
189   }
190 
191   /**
192    * Constructor
193    * @param familyName Column family name. Must be 'printable' -- digit or
194    * letter -- and may not contain a <code>:<code>
195    * @param maxVersions Maximum number of versions to keep
196    * @param compression Compression type
197    * @param inMemory If true, column data should be kept in an HRegionServer's
198    * cache
199    * @param blockCacheEnabled If true, MapFile blocks should be cached
200    * @param timeToLive Time-to-live of cell contents, in seconds
201    * (use HConstants.FOREVER for unlimited TTL)
202    * @param bloomFilter Bloom filter type for this column
203    *
204    * @throws IllegalArgumentException if passed a family name that is made of
205    * other than 'word' characters: i.e. <code>[a-zA-Z_0-9]</code> or contains
206    * a <code>:</code>
207    * @throws IllegalArgumentException if the number of versions is &lt;= 0
208    */
209   public HColumnDescriptor(final byte [] familyName, final int maxVersions,
210       final String compression, final boolean inMemory,
211       final boolean blockCacheEnabled,
212       final int timeToLive, final String bloomFilter) {
213     this(familyName, maxVersions, compression, inMemory, blockCacheEnabled,
214       DEFAULT_BLOCKSIZE, timeToLive, bloomFilter, DEFAULT_REPLICATION_SCOPE);
215   }
216 
217   /**
218    * Constructor
219    * @param familyName Column family name. Must be 'printable' -- digit or
220    * letter -- and may not contain a <code>:<code>
221    * @param maxVersions Maximum number of versions to keep
222    * @param compression Compression type
223    * @param inMemory If true, column data should be kept in an HRegionServer's
224    * cache
225    * @param blockCacheEnabled If true, MapFile blocks should be cached
226    * @param blocksize
227    * @param timeToLive Time-to-live of cell contents, in seconds
228    * (use HConstants.FOREVER for unlimited TTL)
229    * @param bloomFilter Bloom filter type for this column
230    * @param scope The scope tag for this column
231    *
232    * @throws IllegalArgumentException if passed a family name that is made of
233    * other than 'word' characters: i.e. <code>[a-zA-Z_0-9]</code> or contains
234    * a <code>:</code>
235    * @throws IllegalArgumentException if the number of versions is &lt;= 0
236    */
237   public HColumnDescriptor(final byte [] familyName, final int maxVersions,
238       final String compression, final boolean inMemory,
239       final boolean blockCacheEnabled, final int blocksize,
240       final int timeToLive, final String bloomFilter, final int scope) {
241     isLegalFamilyName(familyName);
242     this.name = familyName;
243 
244     if (maxVersions <= 0) {
245       // TODO: Allow maxVersion of 0 to be the way you say "Keep all versions".
246       // Until there is support, consider 0 or < 0 -- a configuration error.
247       throw new IllegalArgumentException("Maximum versions must be positive");
248     }
249     setMaxVersions(maxVersions);
250     setInMemory(inMemory);
251     setBlockCacheEnabled(blockCacheEnabled);
252     setTimeToLive(timeToLive);
253     setCompressionType(Compression.Algorithm.
254       valueOf(compression.toUpperCase()));
255     setBloomFilterType(StoreFile.BloomType.
256       valueOf(bloomFilter.toUpperCase()));
257     setBlocksize(blocksize);
258     setScope(scope);
259   }
260 
261   /**
262    * @param b Family name.
263    * @return <code>b</code>
264    * @throws IllegalArgumentException If not null and not a legitimate family
265    * name: i.e. 'printable' and ends in a ':' (Null passes are allowed because
266    * <code>b</code> can be null when deserializing).  Cannot start with a '.'
267    * either.
268    */
269   public static byte [] isLegalFamilyName(final byte [] b) {
270     if (b == null) {
271       return b;
272     }
273     if (b[0] == '.') {
274       throw new IllegalArgumentException("Family names cannot start with a " +
275         "period: " + Bytes.toString(b));
276     }
277     for (int i = 0; i < b.length; i++) {
278       if (Character.isISOControl(b[i]) || b[i] == ':') {
279         throw new IllegalArgumentException("Illegal character <" + b[i] +
280           ">. Family names cannot contain control characters or colons: " +
281           Bytes.toString(b));
282       }
283     }
284     return b;
285   }
286 
287   /**
288    * @return Name of this column family
289    */
290   public byte [] getName() {
291     return name;
292   }
293 
294   /**
295    * @return Name of this column family
296    */
297   public String getNameAsString() {
298     return Bytes.toString(this.name);
299   }
300 
301   /**
302    * @param key The key.
303    * @return The value.
304    */
305   public byte[] getValue(byte[] key) {
306     ImmutableBytesWritable ibw = values.get(new ImmutableBytesWritable(key));
307     if (ibw == null)
308       return null;
309     return ibw.get();
310   }
311 
312   /**
313    * @param key The key.
314    * @return The value as a string.
315    */
316   public String getValue(String key) {
317     byte[] value = getValue(Bytes.toBytes(key));
318     if (value == null)
319       return null;
320     return Bytes.toString(value);
321   }
322 
323   /**
324    * @return All values.
325    */
326   public Map<ImmutableBytesWritable,ImmutableBytesWritable> getValues() {
327     return Collections.unmodifiableMap(values);
328   }
329 
330   /**
331    * @param key The key.
332    * @param value The value.
333    */
334   public void setValue(byte[] key, byte[] value) {
335     values.put(new ImmutableBytesWritable(key),
336       new ImmutableBytesWritable(value));
337   }
338 
339   /**
340    * @param key Key whose key and value we're to remove from HCD parameters.
341    */
342   public void remove(final byte [] key) {
343     values.remove(new ImmutableBytesWritable(key));
344   }
345 
346   /**
347    * @param key The key.
348    * @param value The value.
349    */
350   public void setValue(String key, String value) {
351     setValue(Bytes.toBytes(key), Bytes.toBytes(value));
352   }
353 
354   /** @return compression type being used for the column family */
355   public Compression.Algorithm getCompression() {
356     String n = getValue(COMPRESSION);
357     return Compression.Algorithm.valueOf(n.toUpperCase());
358   }
359 
360   /** @return maximum number of versions */
361   public int getMaxVersions() {
362     return this.cachedMaxVersions;
363   }
364 
365   /**
366    * @param maxVersions maximum number of versions
367    */
368   public void setMaxVersions(int maxVersions) {
369     setValue(HConstants.VERSIONS, Integer.toString(maxVersions));
370     cachedMaxVersions = maxVersions;
371   }
372 
373   /**
374    * @return Blocksize.
375    */
376   public synchronized int getBlocksize() {
377     if (this.blocksize == null) {
378       String value = getValue(BLOCKSIZE);
379       this.blocksize = (value != null)?
380         Integer.decode(value): Integer.valueOf(DEFAULT_BLOCKSIZE);
381     }
382     return this.blocksize.intValue();
383   }
384 
385   /**
386    * @param s
387    */
388   public void setBlocksize(int s) {
389     setValue(BLOCKSIZE, Integer.toString(s));
390     this.blocksize = null;
391   }
392 
393   /**
394    * @return Compression type setting.
395    */
396   public Compression.Algorithm getCompressionType() {
397     return getCompression();
398   }
399 
400   /**
401    * Compression types supported in hbase.
402    * LZO is not bundled as part of the hbase distribution.
403    * See <a href="http://wiki.apache.org/hadoop/UsingLzoCompression">LZO Compression</a>
404    * for how to enable it.
405    * @param type Compression type setting.
406    */
407   public void setCompressionType(Compression.Algorithm type) {
408     String compressionType;
409     switch (type) {
410       case LZO: compressionType = "LZO"; break;
411       case GZ: compressionType = "GZ"; break;
412       default: compressionType = "NONE"; break;
413     }
414     setValue(COMPRESSION, compressionType);
415   }
416 
417   /**
418    * @return True if we are to keep all in use HRegionServer cache.
419    */
420   public boolean isInMemory() {
421     String value = getValue(HConstants.IN_MEMORY);
422     if (value != null)
423       return Boolean.valueOf(value).booleanValue();
424     return DEFAULT_IN_MEMORY;
425   }
426 
427   /**
428    * @param inMemory True if we are to keep all values in the HRegionServer
429    * cache
430    */
431   public void setInMemory(boolean inMemory) {
432     setValue(HConstants.IN_MEMORY, Boolean.toString(inMemory));
433   }
434 
435   /**
436    * @return Time-to-live of cell contents, in seconds.
437    */
438   public int getTimeToLive() {
439     String value = getValue(TTL);
440     return (value != null)? Integer.valueOf(value).intValue(): DEFAULT_TTL;
441   }
442 
443   /**
444    * @param timeToLive Time-to-live of cell contents, in seconds.
445    */
446   public void setTimeToLive(int timeToLive) {
447     setValue(TTL, Integer.toString(timeToLive));
448   }
449 
450   /**
451    * @return True if MapFile blocks should be cached.
452    */
453   public boolean isBlockCacheEnabled() {
454     String value = getValue(BLOCKCACHE);
455     if (value != null)
456       return Boolean.valueOf(value).booleanValue();
457     return DEFAULT_BLOCKCACHE;
458   }
459 
460   /**
461    * @param blockCacheEnabled True if MapFile blocks should be cached.
462    */
463   public void setBlockCacheEnabled(boolean blockCacheEnabled) {
464     setValue(BLOCKCACHE, Boolean.toString(blockCacheEnabled));
465   }
466 
467   /**
468    * @return bloom filter type used for new StoreFiles in ColumnFamily
469    */
470   public StoreFile.BloomType getBloomFilterType() {
471     String n = getValue(BLOOMFILTER);
472     if (n == null) {
473       n = DEFAULT_BLOOMFILTER;
474     }
475     return StoreFile.BloomType.valueOf(n.toUpperCase());
476   }
477 
478   /**
479    * @param toggle bloom filter type
480    */
481   public void setBloomFilterType(final StoreFile.BloomType bt) {
482     setValue(BLOOMFILTER, bt.toString());
483   }
484 
485    /**
486     * @return the scope tag
487     */
488   public int getScope() {
489     String value = getValue(REPLICATION_SCOPE);
490     if (value != null) {
491       return Integer.valueOf(value).intValue();
492     }
493     return DEFAULT_REPLICATION_SCOPE;
494   }
495 
496  /**
497   * @param scope the scope tag
498   */
499   public void setScope(int scope) {
500     setValue(REPLICATION_SCOPE, Integer.toString(scope));
501   }
502 
503   /**
504    * @see java.lang.Object#toString()
505    */
506   @Override
507   public String toString() {
508     StringBuilder s = new StringBuilder();
509     s.append('{');
510     s.append(HConstants.NAME);
511     s.append(" => '");
512     s.append(Bytes.toString(name));
513     s.append("'");
514     for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
515         values.entrySet()) {
516       String key = Bytes.toString(e.getKey().get());
517       String value = Bytes.toString(e.getValue().get());
518       s.append(", ");
519       s.append(key);
520       s.append(" => '");
521       s.append(value);
522       s.append("'");
523     }
524     s.append('}');
525     return s.toString();
526   }
527 
528   /**
529    * @see java.lang.Object#equals(java.lang.Object)
530    */
531   @Override
532   public boolean equals(Object obj) {
533     if (this == obj) {
534       return true;
535     }
536     if (obj == null) {
537       return false;
538     }
539     if (!(obj instanceof HColumnDescriptor)) {
540       return false;
541     }
542     return compareTo((HColumnDescriptor)obj) == 0;
543   }
544 
545   /**
546    * @see java.lang.Object#hashCode()
547    */
548   @Override
549   public int hashCode() {
550     int result = Bytes.hashCode(this.name);
551     result ^= Byte.valueOf(COLUMN_DESCRIPTOR_VERSION).hashCode();
552     result ^= values.hashCode();
553     return result;
554   }
555 
556   // Writable
557 
558   public void readFields(DataInput in) throws IOException {
559     int version = in.readByte();
560     if (version < 6) {
561       if (version <= 2) {
562         Text t = new Text();
563         t.readFields(in);
564         this.name = t.getBytes();
565 //        if(KeyValue.getFamilyDelimiterIndex(this.name, 0, this.name.length)
566 //            > 0) {
567 //          this.name = stripColon(this.name);
568 //        }
569       } else {
570         this.name = Bytes.readByteArray(in);
571       }
572       this.values.clear();
573       setMaxVersions(in.readInt());
574       int ordinal = in.readInt();
575       setCompressionType(Compression.Algorithm.values()[ordinal]);
576       setInMemory(in.readBoolean());
577       setBloomFilterType(in.readBoolean() ? BloomType.ROW : BloomType.NONE);
578       if (getBloomFilterType() != BloomType.NONE && version < 5) {
579         // If a bloomFilter is enabled and the column descriptor is less than
580         // version 5, we need to skip over it to read the rest of the column
581         // descriptor. There are no BloomFilterDescriptors written to disk for
582         // column descriptors with a version number >= 5
583         throw new UnsupportedClassVersionError(this.getClass().getName() +
584             " does not support backward compatibility with versions older " +
585             "than version 5");
586       }
587       if (version > 1) {
588         setBlockCacheEnabled(in.readBoolean());
589       }
590       if (version > 2) {
591        setTimeToLive(in.readInt());
592       }
593     } else {
594       // version 6+
595       this.name = Bytes.readByteArray(in);
596       this.values.clear();
597       int numValues = in.readInt();
598       for (int i = 0; i < numValues; i++) {
599         ImmutableBytesWritable key = new ImmutableBytesWritable();
600         ImmutableBytesWritable value = new ImmutableBytesWritable();
601         key.readFields(in);
602         value.readFields(in);
603 
604         // in version 8, the BloomFilter setting changed from bool to enum
605         if (version < 8 && Bytes.toString(key.get()).equals(BLOOMFILTER)) {
606           value.set(Bytes.toBytes(
607               Boolean.getBoolean(Bytes.toString(value.get()))
608                 ? BloomType.ROW.toString()
609                 : BloomType.NONE.toString()));
610         }
611 
612         values.put(key, value);
613       }
614       if (version == 6) {
615         // Convert old values.
616         setValue(COMPRESSION, Compression.Algorithm.NONE.getName());
617       }
618       String value = getValue(HConstants.VERSIONS);
619       this.cachedMaxVersions = (value != null)?
620           Integer.valueOf(value).intValue(): DEFAULT_VERSIONS;
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 }