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