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