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 org.apache.hadoop.hbase.classification.InterfaceAudience;
22  import org.apache.hadoop.hbase.classification.InterfaceStability;
23  import org.apache.hadoop.hbase.KeyValue.KVComparator;
24  import org.apache.hadoop.hbase.util.Bytes;
25  
26  import java.nio.ByteBuffer;
27  import java.util.Arrays;
28  import java.util.Set;
29  import java.util.concurrent.CopyOnWriteArraySet;
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    /** The backup table's name. */
90    public static final TableName BACKUP_TABLE_NAME =
91        valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "backup");
92  
93    public static final String OLD_META_STR = ".META.";
94    public static final String OLD_ROOT_STR = "-ROOT-";
95  
96  
97  
98    /**
99     * TableName for old -ROOT- table. It is used to read/process old WALs which have
100    * ROOT edits.
101    */
102   public static final TableName OLD_ROOT_TABLE_NAME = getADummyTableName(OLD_ROOT_STR);
103   /**
104    * TableName for old .META. table. Used in testing.
105    */
106   public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR);
107 
108   private final byte[] name;
109   private final String nameAsString;
110   private final byte[] namespace;
111   private final String namespaceAsString;
112   private final byte[] qualifier;
113   private final String qualifierAsString;
114   private final boolean systemTable;
115   private final int hashCode;
116 
117   /**
118    * Check passed byte array, "tableName", is legal user-space table name.
119    * @return Returns passed <code>tableName</code> param
120    * @throws IllegalArgumentException if passed a tableName is null or
121    * is made of other than 'word' characters or underscores: i.e.
122    * <code>[a-zA-Z_0-9.-:]</code>. The ':' is used to delimit the namespace
123    * from the table name and can be used for nothing else.
124    *
125    * Namespace names can only contain 'word' characters
126    * <code>[a-zA-Z_0-9]</code> or '_'
127    *
128    * Qualifier names can only contain 'word' characters
129    * <code>[a-zA-Z_0-9]</code> or '_', '.' or '-'.
130    * The name may not start with '.' or '-'.
131    *
132    * Valid fully qualified table names:
133    * foo:bar, namespace=>foo, table=>bar
134    * org:foo.bar, namespace=org, table=>foo.bar
135    */
136   public static byte [] isLegalFullyQualifiedTableName(final byte[] tableName) {
137     if (tableName == null || tableName.length <= 0) {
138       throw new IllegalArgumentException("Name is null or empty");
139     }
140 
141     int namespaceDelimIndex = com.google.common.primitives.Bytes.lastIndexOf(tableName,
142         (byte) NAMESPACE_DELIM);
143     if (namespaceDelimIndex < 0){
144       isLegalTableQualifierName(tableName);
145     } else {
146       isLegalNamespaceName(tableName, 0, namespaceDelimIndex);
147       isLegalTableQualifierName(tableName, namespaceDelimIndex + 1, tableName.length);
148     }
149     return tableName;
150   }
151 
152   public static byte [] isLegalTableQualifierName(final byte[] qualifierName) {
153     isLegalTableQualifierName(qualifierName, 0, qualifierName.length, false);
154     return qualifierName;
155   }
156 
157   public static byte [] isLegalTableQualifierName(final byte[] qualifierName, boolean isSnapshot) {
158     isLegalTableQualifierName(qualifierName, 0, qualifierName.length, isSnapshot);
159     return qualifierName;
160   }
161 
162 
163   /**
164    * Qualifier names can only contain 'word' characters
165    * <code>[a-zA-Z_0-9]</code> or '_', '.' or '-'.
166    * The name may not start with '.' or '-'.
167    *
168    * @param qualifierName byte array containing the qualifier name
169    * @param start start index
170    * @param end end index (exclusive)
171    */
172   public static void isLegalTableQualifierName(final byte[] qualifierName,
173                                                 int start,
174                                                 int end) {
175       isLegalTableQualifierName(qualifierName, start, end, false);
176   }
177 
178   public static void isLegalTableQualifierName(final byte[] qualifierName,
179                                                 int start,
180                                                 int end,
181                                                 boolean isSnapshot) {
182     if(end - start < 1) {
183       throw new IllegalArgumentException(isSnapshot ? "Snapshot" : "Table" + " qualifier must not be empty");
184     }
185 
186     if (qualifierName[start] == '.' || qualifierName[start] == '-') {
187       throw new IllegalArgumentException("Illegal first character <" + qualifierName[start] +
188                                          "> at 0. " + (isSnapshot ? "Snapshot" : "User-space table") +
189                                          " qualifiers can only start with 'alphanumeric " +
190                                          "characters': i.e. [a-zA-Z_0-9]: " +
191                                          Bytes.toString(qualifierName, start, end));
192     }
193     for (int i = start; i < end; i++) {
194       if (Character.isLetterOrDigit(qualifierName[i]) ||
195           qualifierName[i] == '_' ||
196           qualifierName[i] == '-' ||
197           qualifierName[i] == '.') {
198         continue;
199       }
200       throw new IllegalArgumentException("Illegal character code:" + qualifierName[i] +
201                                          ", <" + (char) qualifierName[i] + "> at " + i +
202                                          ". " + (isSnapshot ? "Snapshot" : "User-space table") +
203                                          " qualifiers can only contain " +
204                                          "'alphanumeric characters': i.e. [a-zA-Z_0-9-.]: " +
205                                          Bytes.toString(qualifierName, start, end));
206     }
207   }
208   public static void isLegalNamespaceName(byte[] namespaceName) {
209     isLegalNamespaceName(namespaceName, 0, namespaceName.length);
210   }
211 
212   /**
213    * Valid namespace characters are [a-zA-Z_0-9]
214    */
215   public static void isLegalNamespaceName(final byte[] namespaceName,
216                                            final int start,
217                                            final int end) {
218     if(end - start < 1) {
219       throw new IllegalArgumentException("Namespace name must not be empty");
220     }
221     for (int i = start; i < end; i++) {
222       if (Character.isLetterOrDigit(namespaceName[i])|| namespaceName[i] == '_') {
223         continue;
224       }
225       throw new IllegalArgumentException("Illegal character <" + namespaceName[i] +
226         "> at " + i + ". Namespaces can only contain " +
227         "'alphanumeric characters': i.e. [a-zA-Z_0-9]: " + Bytes.toString(namespaceName,
228           start, end));
229     }
230   }
231 
232   public byte[] getName() {
233     return name;
234   }
235 
236   public String getNameAsString() {
237     return nameAsString;
238   }
239 
240   public byte[] getNamespace() {
241     return namespace;
242   }
243 
244   public String getNamespaceAsString() {
245     return namespaceAsString;
246   }
247 
248   /**
249    * Ideally, getNameAsString should contain namespace within it,
250    * but if the namespace is default, it just returns the name. This method
251    * takes care of this corner case.
252    */
253   public String getNameWithNamespaceInclAsString() {
254     if(getNamespaceAsString().equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR)) {
255       return NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR +
256           TableName.NAMESPACE_DELIM + getNameAsString();
257     }
258     return getNameAsString();
259   }
260 
261   public byte[] getQualifier() {
262     return qualifier;
263   }
264 
265   public String getQualifierAsString() {
266     return qualifierAsString;
267   }
268 
269   public byte[] toBytes() {
270     return name;
271   }
272 
273   public boolean isSystemTable() {
274     return systemTable;
275   }
276 
277   @Override
278   public String toString() {
279     return nameAsString;
280   }
281 
282   /**
283    *
284    * @throws IllegalArgumentException See {@link #valueOf(byte[])}
285    */
286   private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException {
287     this.qualifier = new byte[qualifier.remaining()];
288     qualifier.duplicate().get(this.qualifier);
289     this.qualifierAsString = Bytes.toString(this.qualifier);
290 
291     if (qualifierAsString.equals(OLD_ROOT_STR)) {
292       throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated.");
293     }
294     if (qualifierAsString.equals(OLD_META_STR)) {
295       throw new IllegalArgumentException(OLD_META_STR + " no longer exists. The table has been " +
296           "renamed to " + META_TABLE_NAME);
297     }
298 
299     if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) {
300       // Using the same objects: this will make the comparison faster later
301       this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
302       this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
303       this.systemTable = false;
304 
305       // The name does not include the namespace when it's the default one.
306       this.nameAsString = qualifierAsString;
307       this.name = this.qualifier;
308     } else {
309       if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) {
310         this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
311         this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
312         this.systemTable = true;
313       } else {
314         this.namespace = new byte[namespace.remaining()];
315         namespace.duplicate().get(this.namespace);
316         this.namespaceAsString = Bytes.toString(this.namespace);
317         this.systemTable = false;
318       }
319       this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
320       this.name = Bytes.toBytes(nameAsString);
321     }
322 
323     this.hashCode = nameAsString.hashCode();
324 
325     isLegalNamespaceName(this.namespace);
326     isLegalTableQualifierName(this.qualifier);
327   }
328 
329   /**
330    * This is only for the old and meta tables.
331    */
332   private TableName(String qualifier) {
333     this.qualifier = Bytes.toBytes(qualifier);
334     this.qualifierAsString = qualifier;
335 
336     this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
337     this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
338     this.systemTable = true;
339 
340     // WARNING: nameAsString is different than name for old meta & root!
341     // This is by design.
342     this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
343     this.name = this.qualifier;
344 
345     this.hashCode = nameAsString.hashCode();
346   }
347 
348 
349   /**
350    * Check that the object does not exist already. There are two reasons for creating the objects
351    * only once:
352    * 1) With 100K regions, the table names take ~20MB.
353    * 2) Equals becomes much faster as it's resolved with a reference and an int comparison.
354    */
355   private static TableName createTableNameIfNecessary(ByteBuffer bns, ByteBuffer qns) {
356     for (TableName tn : tableCache) {
357       if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
358         return tn;
359       }
360     }
361 
362     TableName newTable = new TableName(bns, qns);
363     if (tableCache.add(newTable)) {  // Adds the specified element if it is not already present
364       return newTable;
365     }
366 
367     // Someone else added it. Let's find it.
368     for (TableName tn : tableCache) {
369       if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
370         return tn;
371       }
372     }
373     // this should never happen.
374     throw new IllegalStateException(newTable + " was supposed to be in the cache");
375   }
376 
377 
378   /**
379    * It is used to create table names for old META, and ROOT table.
380    * These tables are not really legal tables. They are not added into the cache.
381    * @return a dummy TableName instance (with no validation) for the passed qualifier
382    */
383   private static TableName getADummyTableName(String qualifier) {
384     return new TableName(qualifier);
385   }
386 
387 
388   public static TableName valueOf(String namespaceAsString, String qualifierAsString) {
389     if (namespaceAsString == null || namespaceAsString.length() < 1) {
390       namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
391     }
392 
393     for (TableName tn : tableCache) {
394       if (qualifierAsString.equals(tn.getQualifierAsString()) &&
395           namespaceAsString.equals(tn.getNameAsString())) {
396         return tn;
397       }
398     }
399 
400     return createTableNameIfNecessary(
401         ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)),
402         ByteBuffer.wrap(Bytes.toBytes(qualifierAsString)));
403   }
404 
405 
406   /**
407    * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
408    *  depends on this. The test is buried in the table creation to save on array comparison
409    *  when we're creating a standard table object that will be in the cache.
410    */
411   public static TableName valueOf(byte[] fullName) throws IllegalArgumentException{
412     for (TableName tn : tableCache) {
413       if (Arrays.equals(tn.getName(), fullName)) {
414         return tn;
415       }
416     }
417 
418     int namespaceDelimIndex = com.google.common.primitives.Bytes.lastIndexOf(fullName,
419         (byte) NAMESPACE_DELIM);
420 
421     if (namespaceDelimIndex < 0) {
422       return createTableNameIfNecessary(
423           ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
424           ByteBuffer.wrap(fullName));
425     } else {
426       return createTableNameIfNecessary(
427           ByteBuffer.wrap(fullName, 0, namespaceDelimIndex),
428           ByteBuffer.wrap(fullName, namespaceDelimIndex + 1,
429               fullName.length - (namespaceDelimIndex + 1)));
430     }
431   }
432 
433 
434   /**
435    * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
436    *  depends on this.
437    */
438   public static TableName valueOf(String name) {
439     for (TableName tn : tableCache) {
440       if (name.equals(tn.getNameAsString())) {
441         return tn;
442       }
443     }
444 
445     int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM);
446     byte[] nameB = Bytes.toBytes(name);
447 
448     if (namespaceDelimIndex < 0) {
449       return createTableNameIfNecessary(
450           ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
451           ByteBuffer.wrap(nameB));
452     } else {
453       return createTableNameIfNecessary(
454           ByteBuffer.wrap(nameB, 0, namespaceDelimIndex),
455           ByteBuffer.wrap(nameB, namespaceDelimIndex + 1,
456               nameB.length - (namespaceDelimIndex + 1)));
457     }
458   }
459 
460 
461   public static TableName valueOf(byte[] namespace, byte[] qualifier) {
462     if (namespace == null || namespace.length < 1) {
463       namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
464     }
465 
466     for (TableName tn : tableCache) {
467       if (Arrays.equals(tn.getQualifier(), qualifier) &&
468           Arrays.equals(tn.getNamespace(), namespace)) {
469         return tn;
470       }
471     }
472 
473     return createTableNameIfNecessary(
474         ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier));
475   }
476 
477   public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) {
478     if (namespace == null || namespace.remaining() < 1) {
479       return createTableNameIfNecessary(
480           ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), qualifier);
481     }
482 
483     return createTableNameIfNecessary(namespace, qualifier);
484   }
485 
486   @Override
487   public boolean equals(Object o) {
488     if (this == o) return true;
489     if (o == null || getClass() != o.getClass()) return false;
490 
491     TableName tableName = (TableName) o;
492 
493     return o.hashCode() == hashCode && nameAsString.equals(tableName.nameAsString);
494   }
495 
496   @Override
497   public int hashCode() {
498     return hashCode;
499   }
500 
501   /**
502    * For performance reasons, the ordering is not lexicographic.
503    */
504   @Override
505   public int compareTo(TableName tableName) {
506     if (this == tableName) return 0;
507     if (this.hashCode < tableName.hashCode()) {
508       return -1;
509     }
510     if (this.hashCode > tableName.hashCode()) {
511       return 1;
512     }
513     return this.nameAsString.compareTo(tableName.getNameAsString());
514   }
515 
516   /**
517    * Get the appropriate row comparator for this table.
518    *
519    * @return The comparator.
520    * @deprecated The comparator is an internal property of the table. Should
521    * not have been exposed here
522    */
523   @InterfaceAudience.Private
524   @Deprecated
525   public KVComparator getRowComparator() {
526      if(TableName.META_TABLE_NAME.equals(this)) {
527       return KeyValue.META_COMPARATOR;
528     }
529     return KeyValue.COMPARATOR;
530   }
531 }