001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import java.nio.ByteBuffer;
021import java.nio.charset.StandardCharsets;
022import java.util.Arrays;
023import java.util.Set;
024import java.util.concurrent.CopyOnWriteArraySet;
025import org.apache.commons.lang3.ArrayUtils;
026import org.apache.hadoop.hbase.util.Bytes;
027import org.apache.yetus.audience.InterfaceAudience;
028
029import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
030
031/**
032 * Immutable POJO class for representing a table name. Which is of the form: <table
033 * namespace>:<table qualifier> Two special namespaces: 1. hbase - system namespace, used
034 * to contain hbase internal tables 2. default - tables with no explicit specified namespace will
035 * automatically fall into this namespace. ie a) foo:bar, means namespace=foo and qualifier=bar b)
036 * bar, means namespace=default and qualifier=bar c) default:bar, means namespace=default and
037 * qualifier=bar
038 * <p>
039 * Internally, in this class, we cache the instances to limit the number of objects and make the
040 * "equals" faster. We try to minimize the number of objects created of the number of array copy to
041 * check if we already have an instance of this TableName. The code is not optimize for a new
042 * instance creation but is optimized to check for existence.
043 * </p>
044 */
045@InterfaceAudience.Public
046public final class TableName implements Comparable<TableName> {
047
048  /** See {@link #createTableNameIfNecessary(ByteBuffer, ByteBuffer)} */
049  private static final Set<TableName> tableCache = new CopyOnWriteArraySet<>();
050
051  /** Namespace delimiter */
052  // this should always be only 1 byte long
053  public final static char NAMESPACE_DELIM = ':';
054
055  // A non-capture group so that this can be embedded.
056  // regex is a bit more complicated to support nuance of tables
057  // in default namespace
058  // Allows only letters, digits and '_'
059  public static final String VALID_NAMESPACE_REGEX = "(?:[_\\p{Digit}\\p{IsAlphabetic}]+)";
060  // Allows only letters, digits, '_', '-' and '.'
061  public static final String VALID_TABLE_QUALIFIER_REGEX =
062    "(?:[_\\p{Digit}\\p{IsAlphabetic}][-_.\\p{Digit}\\p{IsAlphabetic}]*)";
063  // Concatenation of NAMESPACE_REGEX and TABLE_QUALIFIER_REGEX,
064  // with NAMESPACE_DELIM as delimiter
065  public static final String VALID_USER_TABLE_REGEX = "(?:(?:(?:" + VALID_NAMESPACE_REGEX + "\\"
066    + NAMESPACE_DELIM + ")?)" + "(?:" + VALID_TABLE_QUALIFIER_REGEX + "))";
067
068  /** The hbase:meta table's name. */
069  public static final TableName META_TABLE_NAME =
070    valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "meta");
071
072  /**
073   * The Namespace table's name.
074   * @deprecated since 3.0.0 and will be removed in 4.0.0. We have folded the data in namespace
075   *             table into meta table, so do not use it any more.
076   * @see <a href="https://issues.apache.org/jira/browse/HBASE-21154">HBASE-21154</a>
077   */
078  @Deprecated
079  public static final TableName NAMESPACE_TABLE_NAME =
080    valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "namespace");
081
082  public static final String OLD_META_STR = ".META.";
083  public static final String OLD_ROOT_STR = "-ROOT-";
084
085  /** One globally disallowed name */
086  public static final String DISALLOWED_TABLE_NAME = "zookeeper";
087
088  /** Returns True if <code>tn</code> is the hbase:meta table name. */
089  public static boolean isMetaTableName(final TableName tn) {
090    return tn.equals(TableName.META_TABLE_NAME);
091  }
092
093  /**
094   * TableName for old -ROOT- table. It is used to read/process old WALs which have ROOT edits.
095   */
096  public static final TableName OLD_ROOT_TABLE_NAME = getADummyTableName(OLD_ROOT_STR);
097  /**
098   * TableName for old .META. table. Used in testing.
099   */
100  public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR);
101
102  private final byte[] name;
103  private final String nameAsString;
104  private final byte[] namespace;
105  private final String namespaceAsString;
106  private final byte[] qualifier;
107  private final String qualifierAsString;
108  private final boolean systemTable;
109  private final boolean backupsTable;
110  private final int hashCode;
111
112  /**
113   * Check passed byte array, "tableName", is legal user-space table name.
114   * @return Returns passed <code>tableName</code> param
115   * @throws IllegalArgumentException if passed a tableName is null or is made of other than 'word'
116   *                                  characters or underscores: i.e.
117   *                                  <code>[\p{IsAlphabetic}\p{Digit}.-:]</code>. The ':' is used
118   *                                  to delimit the namespace from the table name and can be used
119   *                                  for nothing else. Namespace names can only contain 'word'
120   *                                  characters <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_'
121   *                                  Qualifier names can only contain 'word' characters
122   *                                  <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_', '.' or '-'.
123   *                                  The name may not start with '.' or '-'. Valid fully qualified
124   *                                  table names: foo:bar, namespace=&gt;foo, table=&gt;bar
125   *                                  org:foo.bar, namespace=org, table=&gt;foo.bar
126   */
127  public static byte[] isLegalFullyQualifiedTableName(final byte[] tableName) {
128    if (tableName == null || tableName.length <= 0) {
129      throw new IllegalArgumentException("Name is null or empty");
130    }
131
132    int namespaceDelimIndex = ArrayUtils.lastIndexOf(tableName, (byte) NAMESPACE_DELIM);
133    if (namespaceDelimIndex < 0) {
134      isLegalTableQualifierName(tableName);
135    } else {
136      isLegalNamespaceName(tableName, 0, namespaceDelimIndex);
137      isLegalTableQualifierName(tableName, namespaceDelimIndex + 1, tableName.length);
138    }
139    return tableName;
140  }
141
142  public static byte[] isLegalTableQualifierName(final byte[] qualifierName) {
143    isLegalTableQualifierName(qualifierName, 0, qualifierName.length, false);
144    return qualifierName;
145  }
146
147  public static byte[] isLegalTableQualifierName(final byte[] qualifierName, boolean isSnapshot) {
148    isLegalTableQualifierName(qualifierName, 0, qualifierName.length, isSnapshot);
149    return qualifierName;
150  }
151
152  /**
153   * Qualifier names can only contain 'word' characters <code>[\p{IsAlphabetic}\p{Digit}]</code> or
154   * '_', '.' or '-'. The name may not start with '.' or '-'.
155   * @param qualifierName byte array containing the qualifier name
156   * @param start         start index
157   * @param end           end index (exclusive)
158   */
159  public static void isLegalTableQualifierName(final byte[] qualifierName, int start, int end) {
160    isLegalTableQualifierName(qualifierName, start, end, false);
161  }
162
163  public static void isLegalTableQualifierName(final byte[] qualifierName, int start, int end,
164    boolean isSnapshot) {
165    if (end - start < 1) {
166      throw new IllegalArgumentException(
167        isSnapshot ? "Snapshot" : "Table" + " qualifier must not be empty");
168    }
169    String qualifierString = Bytes.toString(qualifierName, start, end - start);
170    if (qualifierName[start] == '.' || qualifierName[start] == '-') {
171      throw new IllegalArgumentException("Illegal first character <" + qualifierName[start]
172        + "> at 0. " + (isSnapshot ? "Snapshot" : "User-space table")
173        + " qualifiers can only start with 'alphanumeric " + "characters' from any language: "
174        + qualifierString);
175    }
176    if (qualifierString.equals(DISALLOWED_TABLE_NAME)) {
177      // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
178      // A znode named "zookeeper" is disallowed by zookeeper.
179      throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'");
180    }
181    for (int i = 0; i < qualifierString.length(); i++) {
182      // Treat the string as a char-array as some characters may be multi-byte
183      char c = qualifierString.charAt(i);
184      // Check for letter, digit, underscore, hyphen, or period, and allowed by ZK.
185      // ZooKeeper also has limitations, but Character.isAlphabetic omits those all
186      // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
187      if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_' || c == '-' || c == '.') {
188        continue;
189      }
190      throw new IllegalArgumentException("Illegal character code:" + (int) c + ", <" + c + "> at "
191        + i + ". " + (isSnapshot ? "Snapshot" : "User-space table")
192        + " qualifiers may only contain 'alphanumeric characters' and digits: " + qualifierString);
193    }
194  }
195
196  public static void isLegalNamespaceName(byte[] namespaceName) {
197    isLegalNamespaceName(namespaceName, 0, namespaceName.length);
198  }
199
200  /**
201   * Valid namespace characters are alphabetic characters, numbers, and underscores.
202   */
203  public static void isLegalNamespaceName(final byte[] namespaceName, final int start,
204    final int end) {
205    if (end - start < 1) {
206      throw new IllegalArgumentException("Namespace name must not be empty");
207    }
208    String nsString = new String(namespaceName, start, (end - start), StandardCharsets.UTF_8);
209    if (nsString.equals(DISALLOWED_TABLE_NAME)) {
210      // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
211      // A znode named "zookeeper" is disallowed by zookeeper.
212      throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'");
213    }
214    for (int i = 0; i < nsString.length(); i++) {
215      // Treat the string as a char-array as some characters may be multi-byte
216      char c = nsString.charAt(i);
217      // ZooKeeper also has limitations, but Character.isAlphabetic omits those all
218      // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
219      if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_') {
220        continue;
221      }
222      throw new IllegalArgumentException(
223        "Illegal character <" + c + "> at " + i + ". Namespaces may only contain "
224          + "'alphanumeric characters' from any language and digits: " + nsString);
225    }
226  }
227
228  public byte[] getName() {
229    return name;
230  }
231
232  public String getNameAsString() {
233    return nameAsString;
234  }
235
236  public byte[] getNamespace() {
237    return namespace;
238  }
239
240  public String getNamespaceAsString() {
241    return namespaceAsString;
242  }
243
244  /**
245   * Ideally, getNameAsString should contain namespace within it, but if the namespace is default,
246   * it just returns the name. This method takes care of this corner case.
247   */
248  public String getNameWithNamespaceInclAsString() {
249    if (getNamespaceAsString().equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR)) {
250      return NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR + TableName.NAMESPACE_DELIM
251        + getNameAsString();
252    }
253    return getNameAsString();
254  }
255
256  public byte[] getQualifier() {
257    return qualifier;
258  }
259
260  public String getQualifierAsString() {
261    return qualifierAsString;
262  }
263
264  /** Returns A pointer to TableName as String bytes. */
265  public byte[] toBytes() {
266    return name;
267  }
268
269  public boolean isSystemTable() {
270    return systemTable;
271  }
272
273  public boolean isBackupsTable() {
274    return backupsTable;
275  }
276
277  @Override
278  public String toString() {
279    return nameAsString;
280  }
281
282  private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException {
283    this.qualifier = new byte[qualifier.remaining()];
284    qualifier.duplicate().get(this.qualifier);
285    this.qualifierAsString = Bytes.toString(this.qualifier);
286
287    if (qualifierAsString.equals(OLD_ROOT_STR)) {
288      throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated.");
289    }
290    if (qualifierAsString.equals(OLD_META_STR)) {
291      throw new IllegalArgumentException(
292        OLD_META_STR + " no longer exists. The table has been " + "renamed to " + META_TABLE_NAME);
293    }
294
295    if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) {
296      // Using the same objects: this will make the comparison faster later
297      this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
298      this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
299      this.systemTable = false;
300      this.backupsTable = false;
301
302      // The name does not include the namespace when it's the default one.
303      this.nameAsString = qualifierAsString;
304      this.name = this.qualifier;
305    } else {
306      if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) {
307        this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
308        this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
309        this.systemTable = true;
310        this.backupsTable = false;
311      } else if (Bytes.equals(NamespaceDescriptor.BACKUP_NAMESPACE_NAME, namespace)) {
312        this.namespace = NamespaceDescriptor.BACKUP_NAMESPACE_NAME;
313        this.namespaceAsString = NamespaceDescriptor.BACKUP_NAMESPACE_NAME_STR;
314        this.systemTable = true;
315        this.backupsTable = true;
316      } else {
317        this.namespace = new byte[namespace.remaining()];
318        namespace.duplicate().get(this.namespace);
319        this.namespaceAsString = Bytes.toString(this.namespace);
320        this.systemTable = false;
321        this.backupsTable = false;
322      }
323      this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
324      this.name = Bytes.toBytes(nameAsString);
325    }
326
327    this.hashCode = nameAsString.hashCode();
328
329    isLegalNamespaceName(this.namespace);
330    isLegalTableQualifierName(this.qualifier);
331  }
332
333  /** This is only for the old and meta tables. */
334  private TableName(String qualifier) {
335    this.qualifier = Bytes.toBytes(qualifier);
336    this.qualifierAsString = qualifier;
337
338    this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
339    this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
340    this.systemTable = true;
341    this.backupsTable = false;
342
343    // WARNING: nameAsString is different than name for old meta & root!
344    // This is by design.
345    this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
346    this.name = this.qualifier;
347
348    this.hashCode = nameAsString.hashCode();
349  }
350
351  /**
352   * Check that the object does not exist already. There are two reasons for creating the objects
353   * only once: 1) With 100K regions, the table names take ~20MB. 2) Equals becomes much faster as
354   * it's resolved with a reference and an int comparison.
355   */
356  private static TableName createTableNameIfNecessary(ByteBuffer bns, ByteBuffer qns) {
357    for (TableName tn : tableCache) {
358      if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
359        return tn;
360      }
361    }
362
363    TableName newTable = new TableName(bns, qns);
364    if (tableCache.add(newTable)) { // Adds the specified element if it is not already present
365      return newTable;
366    }
367
368    // Someone else added it. Let's find it.
369    for (TableName tn : tableCache) {
370      if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
371        return tn;
372      }
373    }
374    // this should never happen.
375    throw new IllegalStateException(newTable + " was supposed to be in the cache");
376  }
377
378  /**
379   * It is used to create table names for old META, and ROOT table. These tables are not really
380   * 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  public static TableName valueOf(String namespaceAsString, String qualifierAsString) {
388    if (namespaceAsString == null || namespaceAsString.length() < 1) {
389      namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
390    }
391
392    for (TableName tn : tableCache) {
393      if (
394        qualifierAsString.equals(tn.getQualifierAsString())
395          && namespaceAsString.equals(tn.getNamespaceAsString())
396      ) {
397        return tn;
398      }
399    }
400
401    return createTableNameIfNecessary(ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)),
402      ByteBuffer.wrap(Bytes.toBytes(qualifierAsString)));
403  }
404
405  /**
406   * Construct a TableName
407   * @param fullName will use the entire byte array
408   * @throws IllegalArgumentException if fullName equals old root or old meta. Some code depends on
409   *                                  this. The test is buried in the table creation to save on
410   *                                  array comparison when we're creating a standard table object
411   *                                  that will be in the cache.
412   */
413  public static TableName valueOf(byte[] fullName) throws IllegalArgumentException {
414    return valueOf(fullName, 0, fullName.length);
415  }
416
417  /**
418   * Construct a TableName
419   * @param fullName byte array to look into
420   * @param offset   within said array
421   * @param length   within said array
422   * @throws IllegalArgumentException if fullName equals old root or old meta.
423   */
424  public static TableName valueOf(byte[] fullName, int offset, int length)
425    throws IllegalArgumentException {
426    Preconditions.checkArgument(offset >= 0, "offset must be non-negative but was %s", offset);
427    Preconditions.checkArgument(offset < fullName.length, "offset (%s) must be < array length (%s)",
428      offset, fullName.length);
429    Preconditions.checkArgument(length <= fullName.length,
430      "length (%s) must be <= array length (%s)", length, fullName.length);
431    for (TableName tn : tableCache) {
432      final byte[] tnName = tn.getName();
433      if (Bytes.equals(tnName, 0, tnName.length, fullName, offset, length)) {
434        return tn;
435      }
436    }
437
438    int namespaceDelimIndex =
439      ArrayUtils.lastIndexOf(fullName, (byte) NAMESPACE_DELIM, offset + length - 1);
440
441    if (namespaceDelimIndex < offset) {
442      return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
443        ByteBuffer.wrap(fullName, offset, length));
444    } else {
445      return createTableNameIfNecessary(ByteBuffer.wrap(fullName, offset, namespaceDelimIndex),
446        ByteBuffer.wrap(fullName, namespaceDelimIndex + 1, length - (namespaceDelimIndex + 1)));
447    }
448  }
449
450  /**
451   * Construct a TableName
452   * @param fullname of a table, possibly with a leading namespace and ':' as delimiter.
453   * @throws IllegalArgumentException if fullName equals old root or old meta.
454   */
455  public static TableName valueOf(ByteBuffer fullname) {
456    fullname = fullname.duplicate();
457    fullname.mark();
458    boolean miss = true;
459    while (fullname.hasRemaining() && miss) {
460      miss = ((byte) NAMESPACE_DELIM) != fullname.get();
461    }
462    if (miss) {
463      fullname.reset();
464      return valueOf(null, fullname);
465    } else {
466      ByteBuffer qualifier = fullname.slice();
467      int delimiterIndex = fullname.position() - 1;
468      fullname.reset();
469      // changing variable name for clarity
470      ByteBuffer namespace = fullname.duplicate();
471      namespace.limit(delimiterIndex);
472      return valueOf(namespace, qualifier);
473    }
474  }
475
476  /**
477   * Construct a TableName
478   * @throws IllegalArgumentException if fullName equals old root or old meta. Some code depends on
479   *                                  this.
480   */
481  public static TableName valueOf(String name) {
482    for (TableName tn : tableCache) {
483      if (name.equals(tn.getNameAsString())) {
484        return tn;
485      }
486    }
487
488    final int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM);
489
490    if (namespaceDelimIndex < 0) {
491      return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
492        ByteBuffer.wrap(Bytes.toBytes(name)));
493    } else {
494      // indexOf is by character, not byte (consider multi-byte characters)
495      String ns = name.substring(0, namespaceDelimIndex);
496      String qualifier = name.substring(namespaceDelimIndex + 1);
497      return createTableNameIfNecessary(ByteBuffer.wrap(Bytes.toBytes(ns)),
498        ByteBuffer.wrap(Bytes.toBytes(qualifier)));
499    }
500  }
501
502  public static TableName valueOf(byte[] namespace, byte[] qualifier) {
503    if (namespace == null || namespace.length < 1) {
504      namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
505    }
506
507    for (TableName tn : tableCache) {
508      if (
509        Arrays.equals(tn.getQualifier(), qualifier) && Arrays.equals(tn.getNamespace(), namespace)
510      ) {
511        return tn;
512      }
513    }
514
515    return createTableNameIfNecessary(ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier));
516  }
517
518  public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) {
519    if (namespace == null || namespace.remaining() < 1) {
520      return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
521        qualifier);
522    }
523
524    return createTableNameIfNecessary(namespace, qualifier);
525  }
526
527  @Override
528  public boolean equals(Object o) {
529    if (this == o) return true;
530    if (o == null || getClass() != o.getClass()) return false;
531
532    TableName tableName = (TableName) o;
533
534    return o.hashCode() == hashCode && nameAsString.equals(tableName.nameAsString);
535  }
536
537  @Override
538  public int hashCode() {
539    return hashCode;
540  }
541
542  @Override
543  public int compareTo(TableName tableName) {
544    // For performance reasons, the ordering is not lexicographic.
545    if (this == tableName) {
546      return 0;
547    }
548    if (this.hashCode < tableName.hashCode()) {
549      return -1;
550    }
551    if (this.hashCode > tableName.hashCode()) {
552      return 1;
553    }
554    return this.nameAsString.compareTo(tableName.getNameAsString());
555  }
556
557}