View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase;
20  
21  import java.nio.ByteBuffer;
22  import java.util.Arrays;
23  import java.util.Set;
24  import java.util.concurrent.CopyOnWriteArraySet;
25  
26  import org.apache.hadoop.hbase.KeyValue.KVComparator;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.hbase.classification.InterfaceStability;
29  import org.apache.hadoop.hbase.util.Bytes;
30  
31  /**
32   * Immutable POJO class for representing a table name.
33   * Which is of the form:
34   * <table namespace>:<table qualifier>
35   *
36   * Two special namespaces:
37   *
38   * 1. hbase - system namespace, used to contain hbase internal tables
39   * 2. default - tables with no explicit specified namespace will
40   * automatically fall into this namespace.
41   *
42   * ie
43   *
44   * a) foo:bar, means namespace=foo and qualifier=bar
45   * b) bar, means namespace=default and qualifier=bar
46   * c) default:bar, means namespace=default and qualifier=bar
47   *
48   *  <p>
49   * Internally, in this class, we cache the instances to limit the number of objects and
50   *  make the "equals" faster. We try to minimize the number of objects created of
51   *  the number of array copy to check if we already have an instance of this TableName. The code
52   *  is not optimize for a new instance creation but is optimized to check for existence.
53   * </p>
54   */
55  @InterfaceAudience.Public
56  @InterfaceStability.Evolving
57  public final class TableName implements Comparable<TableName> {
58  
59    /** See {@link #createTableNameIfNecessary(ByteBuffer, ByteBuffer)} */
60    private static final Set<TableName> tableCache = new CopyOnWriteArraySet<TableName>();
61  
62    /** Namespace delimiter */
63    //this should always be only 1 byte long
64    public final static char NAMESPACE_DELIM = ':';
65  
66    // A non-capture group so that this can be embedded.
67    // regex is a bit more complicated to support nuance of tables
68    // in default namespace
69    //Allows only letters, digits and '_'
70    public static final String VALID_NAMESPACE_REGEX =
71        "(?:[a-zA-Z_0-9]+)";
72    //Allows only letters, digits, '_', '-' and '.'
73    public static final String VALID_TABLE_QUALIFIER_REGEX =
74        "(?:[a-zA-Z_0-9][a-zA-Z_0-9-.]*)";
75    //Concatenation of NAMESPACE_REGEX and TABLE_QUALIFIER_REGEX,
76    //with NAMESPACE_DELIM as delimiter
77    public static final String VALID_USER_TABLE_REGEX =
78        "(?:(?:(?:"+VALID_NAMESPACE_REGEX+"\\"+NAMESPACE_DELIM+")?)" +
79           "(?:"+VALID_TABLE_QUALIFIER_REGEX+"))";
80  
81    /** The hbase:meta table's name. */
82    public static final TableName META_TABLE_NAME =
83        valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "meta");
84  
85    /** The Namespace table's name. */
86    public static final TableName NAMESPACE_TABLE_NAME =
87        valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "namespace");
88  
89    public static final String OLD_META_STR = ".META.";
90    public static final String OLD_ROOT_STR = "-ROOT-";
91  
92  
93  
94    /**
95     * TableName for old -ROOT- table. It is used to read/process old WALs which have
96     * ROOT edits.
97     */
98    public static final TableName OLD_ROOT_TABLE_NAME = getADummyTableName(OLD_ROOT_STR);
99    /**
100    * TableName for old .META. table. Used in testing.
101    */
102   public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR);
103 
104   private final byte[] name;
105   private final String nameAsString;
106   private final byte[] namespace;
107   private final String namespaceAsString;
108   private final byte[] qualifier;
109   private final String qualifierAsString;
110   private final boolean systemTable;
111   private final int hashCode;
112 
113   /**
114    * Check passed byte array, "tableName", is legal user-space table name.
115    * @return Returns passed <code>tableName</code> param
116    * @throws IllegalArgumentException if passed a tableName is null or
117    * is made of other than 'word' characters or underscores: i.e.
118    * <code>[a-zA-Z_0-9.-:]</code>. The ':' is used to delimit the namespace
119    * from the table name and can be used for nothing else.
120    *
121    * Namespace names can only contain 'word' characters
122    * <code>[a-zA-Z_0-9]</code> or '_'
123    *
124    * Qualifier names can only contain 'word' characters
125    * <code>[a-zA-Z_0-9]</code> or '_', '.' or '-'.
126    * The name may not start with '.' or '-'.
127    *
128    * Valid fully qualified table names:
129    * foo:bar, namespace=>foo, table=>bar
130    * org:foo.bar, namespace=org, table=>foo.bar
131    */
132   public static byte [] isLegalFullyQualifiedTableName(final byte[] tableName) {
133     if (tableName == null || tableName.length <= 0) {
134       throw new IllegalArgumentException("Name is null or empty");
135     }
136 
137     int namespaceDelimIndex = com.google.common.primitives.Bytes.lastIndexOf(tableName,
138         (byte) NAMESPACE_DELIM);
139     if (namespaceDelimIndex < 0){
140       isLegalTableQualifierName(tableName);
141     } else {
142       isLegalNamespaceName(tableName, 0, namespaceDelimIndex);
143       isLegalTableQualifierName(tableName, namespaceDelimIndex + 1, tableName.length);
144     }
145     return tableName;
146   }
147 
148   public static byte [] isLegalTableQualifierName(final byte[] qualifierName) {
149     isLegalTableQualifierName(qualifierName, 0, qualifierName.length, false);
150     return qualifierName;
151   }
152 
153   public static byte [] isLegalTableQualifierName(final byte[] qualifierName, boolean isSnapshot) {
154     isLegalTableQualifierName(qualifierName, 0, qualifierName.length, isSnapshot);
155     return qualifierName;
156   }
157 
158 
159   /**
160    * Qualifier names can only contain 'word' characters
161    * <code>[a-zA-Z_0-9]</code> or '_', '.' or '-'.
162    * The name may not start with '.' or '-'.
163    *
164    * @param qualifierName byte array containing the qualifier name
165    * @param start start index
166    * @param end end index (exclusive)
167    */
168   public static void isLegalTableQualifierName(final byte[] qualifierName,
169                                                 int start,
170                                                 int end) {
171       isLegalTableQualifierName(qualifierName, start, end, false);
172   }
173 
174   public static void isLegalTableQualifierName(final byte[] qualifierName,
175                                                 int start,
176                                                 int end,
177                                                 boolean isSnapshot) {
178     if(end - start < 1) {
179       throw new IllegalArgumentException(isSnapshot ? "Snapshot" : "Table" + " qualifier must not be empty");
180     }
181 
182     if (qualifierName[start] == '.' || qualifierName[start] == '-') {
183       throw new IllegalArgumentException("Illegal first character <" + qualifierName[0] +
184                                          "> at 0. Namespaces can only start with alphanumeric " +
185                                          "characters': i.e. [a-zA-Z_0-9]: " +
186                                          Bytes.toString(qualifierName));
187     }
188     for (int i = start; i < end; i++) {
189       if (Character.isLetterOrDigit(qualifierName[i]) ||
190           qualifierName[i] == '_' ||
191           qualifierName[i] == '-' ||
192           qualifierName[i] == '.') {
193         continue;
194       }
195       throw new IllegalArgumentException("Illegal character code:" + qualifierName[i] +
196                                          ", <" + (char) qualifierName[i] + "> at " + i +
197                                          ". " + (isSnapshot ? "snapshot" : "User-space table") +
198                                          " qualifiers can only contain " +
199                                          "'alphanumeric characters': i.e. [a-zA-Z_0-9-.]: " +
200                                          Bytes.toString(qualifierName, start, end));
201     }
202   }
203   public static void isLegalNamespaceName(byte[] namespaceName) {
204     isLegalNamespaceName(namespaceName, 0, namespaceName.length);
205   }
206 
207   /**
208    * Valid namespace characters are [a-zA-Z_0-9]
209    */
210   public static void isLegalNamespaceName(byte[] namespaceName, int offset, int length) {
211     for (int i = offset; i < length; i++) {
212       if (Character.isLetterOrDigit(namespaceName[i])|| namespaceName[i] == '_') {
213         continue;
214       }
215       throw new IllegalArgumentException("Illegal character <" + namespaceName[i] +
216         "> at " + i + ". Namespaces can only contain " +
217         "'alphanumeric characters': i.e. [a-zA-Z_0-9]: " + Bytes.toString(namespaceName,
218           offset, length));
219     }
220     if(offset == length)
221       throw new IllegalArgumentException("Illegal character <" + namespaceName[offset] +
222           "> at " + offset + ". Namespaces can only contain " +
223           "'alphanumeric characters': i.e. [a-zA-Z_0-9]: " + Bytes.toString(namespaceName,
224             offset, length));
225   }
226 
227   public byte[] getName() {
228     return name;
229   }
230 
231   public String getNameAsString() {
232     return nameAsString;
233   }
234 
235   public byte[] getNamespace() {
236     return namespace;
237   }
238 
239   public String getNamespaceAsString() {
240     return namespaceAsString;
241   }
242 
243   public byte[] getQualifier() {
244     return qualifier;
245   }
246 
247   public String getQualifierAsString() {
248     return qualifierAsString;
249   }
250 
251   public byte[] toBytes() {
252     return name;
253   }
254 
255   public boolean isSystemTable() {
256     return systemTable;
257   }
258 
259   @Override
260   public String toString() {
261     return nameAsString;
262   }
263 
264   /**
265    *
266    * @throws IllegalArgumentException See {@link #valueOf(byte[])}
267    */
268   private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException {
269     this.qualifier = new byte[qualifier.remaining()];
270     qualifier.duplicate().get(this.qualifier);
271     this.qualifierAsString = Bytes.toString(this.qualifier);
272 
273     if (qualifierAsString.equals(OLD_ROOT_STR)) {
274       throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated.");
275     }
276     if (qualifierAsString.equals(OLD_META_STR)) {
277       throw new IllegalArgumentException(OLD_META_STR + " no longer exists. The table has been " +
278           "renamed to " + META_TABLE_NAME);
279     }
280 
281     if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) {
282       // Using the same objects: this will make the comparison faster later
283       this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
284       this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
285       this.systemTable = false;
286 
287       // The name does not include the namespace when it's the default one.
288       this.nameAsString = qualifierAsString;
289       this.name = this.qualifier;
290     } else {
291       if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) {
292         this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
293         this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
294         this.systemTable = true;
295       } else {
296         this.namespace = new byte[namespace.remaining()];
297         namespace.duplicate().get(this.namespace);
298         this.namespaceAsString = Bytes.toString(this.namespace);
299         this.systemTable = false;
300       }
301       this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
302       this.name = Bytes.toBytes(nameAsString);
303     }
304 
305     this.hashCode = nameAsString.hashCode();
306 
307     isLegalNamespaceName(this.namespace);
308     isLegalTableQualifierName(this.qualifier);
309   }
310 
311   /**
312    * This is only for the old and meta tables.
313    */
314   private TableName(String qualifier) {
315     this.qualifier = Bytes.toBytes(qualifier);
316     this.qualifierAsString = qualifier;
317 
318     this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
319     this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
320     this.systemTable = true;
321 
322     // WARNING: nameAsString is different than name for old meta & root!
323     // This is by design.
324     this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
325     this.name = this.qualifier;
326 
327     this.hashCode = nameAsString.hashCode();
328   }
329 
330 
331   /**
332    * Check that the object does not exist already. There are two reasons for creating the objects
333    * only once:
334    * 1) With 100K regions, the table names take ~20MB.
335    * 2) Equals becomes much faster as it's resolved with a reference and an int comparison.
336    */
337   private static TableName createTableNameIfNecessary(ByteBuffer bns, ByteBuffer qns) {
338     for (TableName tn : tableCache) {
339       if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
340         return tn;
341       }
342     }
343 
344     TableName newTable = new TableName(bns, qns);
345     if (tableCache.add(newTable)) {  // Adds the specified element if it is not already present
346       return newTable;
347     }
348 
349     // Someone else added it. Let's find it.
350     for (TableName tn : tableCache) {
351       if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
352         return tn;
353       }
354     }
355     // this should never happen.
356     throw new IllegalStateException(newTable + " was supposed to be in the cache");
357   }
358 
359 
360   /**
361    * It is used to create table names for old META, and ROOT table.
362    * These tables are not really legal tables. They are not added into the cache.
363    * @return a dummy TableName instance (with no validation) for the passed qualifier
364    */
365   private static TableName getADummyTableName(String qualifier) {
366     return new TableName(qualifier);
367   }
368 
369 
370   public static TableName valueOf(String namespaceAsString, String qualifierAsString) {
371     if (namespaceAsString == null || namespaceAsString.length() < 1) {
372       namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
373     }
374 
375     for (TableName tn : tableCache) {
376       if (qualifierAsString.equals(tn.getQualifierAsString()) &&
377           namespaceAsString.equals(tn.getNameAsString())) {
378         return tn;
379       }
380     }
381 
382     return createTableNameIfNecessary(
383         ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)),
384         ByteBuffer.wrap(Bytes.toBytes(qualifierAsString)));
385   }
386 
387 
388   /**
389    * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
390    *  depends on this. The test is buried in the table creation to save on array comparison
391    *  when we're creating a standard table object that will be in the cache.
392    */
393   public static TableName valueOf(byte[] fullName) throws IllegalArgumentException{
394     for (TableName tn : tableCache) {
395       if (Arrays.equals(tn.getName(), fullName)) {
396         return tn;
397       }
398     }
399 
400     int namespaceDelimIndex = com.google.common.primitives.Bytes.lastIndexOf(fullName,
401         (byte) NAMESPACE_DELIM);
402 
403     if (namespaceDelimIndex < 0) {
404       return createTableNameIfNecessary(
405           ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
406           ByteBuffer.wrap(fullName));
407     } else {
408       return createTableNameIfNecessary(
409           ByteBuffer.wrap(fullName, 0, namespaceDelimIndex),
410           ByteBuffer.wrap(fullName, namespaceDelimIndex + 1,
411               fullName.length - (namespaceDelimIndex + 1)));
412     }
413   }
414 
415 
416   /**
417    * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
418    *  depends on this.
419    */
420   public static TableName valueOf(String name) {
421     for (TableName tn : tableCache) {
422       if (name.equals(tn.getNameAsString())) {
423         return tn;
424       }
425     }
426 
427     int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM);
428     byte[] nameB = Bytes.toBytes(name);
429 
430     if (namespaceDelimIndex < 0) {
431       return createTableNameIfNecessary(
432           ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
433           ByteBuffer.wrap(nameB));
434     } else {
435       return createTableNameIfNecessary(
436           ByteBuffer.wrap(nameB, 0, namespaceDelimIndex),
437           ByteBuffer.wrap(nameB, namespaceDelimIndex + 1,
438               nameB.length - (namespaceDelimIndex + 1)));
439     }
440   }
441 
442 
443   public static TableName valueOf(byte[] namespace, byte[] qualifier) {
444     if (namespace == null || namespace.length < 1) {
445       namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
446     }
447 
448     for (TableName tn : tableCache) {
449       if (Arrays.equals(tn.getQualifier(), qualifier) &&
450           Arrays.equals(tn.getNamespace(), namespace)) {
451         return tn;
452       }
453     }
454 
455     return createTableNameIfNecessary(
456         ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier));
457   }
458 
459   public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) {
460     if (namespace == null || namespace.remaining() < 1) {
461       return createTableNameIfNecessary(
462           ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), qualifier);
463     }
464 
465     return createTableNameIfNecessary(namespace, qualifier);
466   }
467 
468   @Override
469   public boolean equals(Object o) {
470     if (this == o) return true;
471     if (o == null || getClass() != o.getClass()) return false;
472 
473     TableName tableName = (TableName) o;
474 
475     return o.hashCode() == hashCode && nameAsString.equals(tableName.nameAsString);
476   }
477 
478   @Override
479   public int hashCode() {
480     return hashCode;
481   }
482 
483   /**
484    * For performance reasons, the ordering is not lexicographic.
485    */
486   @Override
487   public int compareTo(TableName tableName) {
488     if (this == tableName) return 0;
489     if (this.hashCode < tableName.hashCode()) {
490       return -1;
491     }
492     if (this.hashCode > tableName.hashCode()) {
493       return 1;
494     }
495     return this.nameAsString.compareTo(tableName.getNameAsString());
496   }
497 
498   /**
499    * Get the appropriate row comparator for this table.
500    *
501    * @return The comparator.
502    */
503   public KVComparator getRowComparator() {
504      if(TableName.META_TABLE_NAME.equals(this)) {
505       return KeyValue.META_COMPARATOR;
506     }
507     return KeyValue.COMPARATOR;
508   }
509 }