HBASE-24163 MOB compactor implementations should use format specifiers when calling...
[hbase.git] / hbase-common / src / main / java / org / apache / hadoop / hbase / TableName.java
blobb659d141f931e33749378d86c8e510aa82c1b4be
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 package org.apache.hadoop.hbase;
21 import java.nio.ByteBuffer;
22 import java.nio.charset.StandardCharsets;
23 import java.util.Arrays;
24 import java.util.Set;
25 import java.util.concurrent.CopyOnWriteArraySet;
27 import org.apache.commons.lang3.ArrayUtils;
28 import org.apache.hadoop.hbase.util.Bytes;
29 import org.apache.yetus.audience.InterfaceAudience;
31 import org.apache.hbase.thirdparty.com.google.common.base.Preconditions;
33 /**
34 * Immutable POJO class for representing a table name.
35 * Which is of the form:
36 * <table namespace>:<table qualifier>
38 * Two special namespaces:
40 * 1. hbase - system namespace, used to contain hbase internal tables
41 * 2. default - tables with no explicit specified namespace will
42 * automatically fall into this namespace.
44 * ie
46 * a) foo:bar, means namespace=foo and qualifier=bar
47 * b) bar, means namespace=default and qualifier=bar
48 * c) default:bar, means namespace=default and qualifier=bar
50 * <p>
51 * Internally, in this class, we cache the instances to limit the number of objects and
52 * make the "equals" faster. We try to minimize the number of objects created of
53 * the number of array copy to check if we already have an instance of this TableName. The code
54 * is not optimize for a new instance creation but is optimized to check for existence.
55 * </p>
57 @InterfaceAudience.Public
58 public final class TableName implements Comparable<TableName> {
60 /** See {@link #createTableNameIfNecessary(ByteBuffer, ByteBuffer)} */
61 private static final Set<TableName> tableCache = new CopyOnWriteArraySet<>();
63 /** Namespace delimiter */
64 //this should always be only 1 byte long
65 public final static char NAMESPACE_DELIM = ':';
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+"))";
82 /** The hbase:meta table's name. */
83 public static final TableName META_TABLE_NAME =
84 valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "meta");
86 /**
87 * The Namespace table's name.
88 * @deprecated since 3.0.0 and will be removed in 4.0.0. We have folded the data in namespace
89 * table into meta table, so do not use it any more.
90 * @see <a href="https://issues.apache.org/jira/browse/HBASE-21154">HBASE-21154</a>
92 @Deprecated
93 public static final TableName NAMESPACE_TABLE_NAME =
94 valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "namespace");
96 public static final String OLD_META_STR = ".META.";
97 public static final String OLD_ROOT_STR = "-ROOT-";
99 /** One globally disallowed name */
100 public static final String DISALLOWED_TABLE_NAME = "zookeeper";
103 * @return True if <code>tn</code> is the hbase:meta table name.
105 public static boolean isMetaTableName(final TableName tn) {
106 return tn.equals(TableName.META_TABLE_NAME);
110 * TableName for old -ROOT- table. It is used to read/process old WALs which have
111 * ROOT edits.
113 public static final TableName OLD_ROOT_TABLE_NAME = getADummyTableName(OLD_ROOT_STR);
115 * TableName for old .META. table. Used in testing.
117 public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR);
119 private final byte[] name;
120 private final String nameAsString;
121 private final byte[] namespace;
122 private final String namespaceAsString;
123 private final byte[] qualifier;
124 private final String qualifierAsString;
125 private final boolean systemTable;
126 private final int hashCode;
129 * Check passed byte array, "tableName", is legal user-space table name.
130 * @return Returns passed <code>tableName</code> param
131 * @throws IllegalArgumentException if passed a tableName is null or
132 * is made of other than 'word' characters or underscores: i.e.
133 * <code>[\p{IsAlphabetic}\p{Digit}.-:]</code>. The ':' is used to delimit the namespace
134 * from the table name and can be used for nothing else.
136 * Namespace names can only contain 'word' characters
137 * <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_'
139 * Qualifier names can only contain 'word' characters
140 * <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_', '.' or '-'.
141 * The name may not start with '.' or '-'.
143 * Valid fully qualified table names:
144 * foo:bar, namespace=&gt;foo, table=&gt;bar
145 * org:foo.bar, namespace=org, table=&gt;foo.bar
147 public static byte [] isLegalFullyQualifiedTableName(final byte[] tableName) {
148 if (tableName == null || tableName.length <= 0) {
149 throw new IllegalArgumentException("Name is null or empty");
152 int namespaceDelimIndex = ArrayUtils.lastIndexOf(tableName, (byte) NAMESPACE_DELIM);
153 if (namespaceDelimIndex < 0){
154 isLegalTableQualifierName(tableName);
155 } else {
156 isLegalNamespaceName(tableName, 0, namespaceDelimIndex);
157 isLegalTableQualifierName(tableName, namespaceDelimIndex + 1, tableName.length);
159 return tableName;
162 public static byte [] isLegalTableQualifierName(final byte[] qualifierName) {
163 isLegalTableQualifierName(qualifierName, 0, qualifierName.length, false);
164 return qualifierName;
167 public static byte [] isLegalTableQualifierName(final byte[] qualifierName, boolean isSnapshot) {
168 isLegalTableQualifierName(qualifierName, 0, qualifierName.length, isSnapshot);
169 return qualifierName;
174 * Qualifier names can only contain 'word' characters
175 * <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_', '.' or '-'.
176 * The name may not start with '.' or '-'.
178 * @param qualifierName byte array containing the qualifier name
179 * @param start start index
180 * @param end end index (exclusive)
182 public static void isLegalTableQualifierName(final byte[] qualifierName,
183 int start,
184 int end) {
185 isLegalTableQualifierName(qualifierName, start, end, false);
188 public static void isLegalTableQualifierName(final byte[] qualifierName,
189 int start,
190 int end,
191 boolean isSnapshot) {
192 if(end - start < 1) {
193 throw new IllegalArgumentException(isSnapshot ? "Snapshot" : "Table" + " qualifier must not be empty");
195 if (qualifierName[start] == '.' || qualifierName[start] == '-') {
196 throw new IllegalArgumentException("Illegal first character <" + qualifierName[start] +
197 "> at 0. " + (isSnapshot ? "Snapshot" : "User-space table") +
198 " qualifiers can only start with 'alphanumeric " +
199 "characters' from any language: " +
200 Bytes.toString(qualifierName, start, end));
202 // Treat the bytes as UTF-8
203 String qualifierString = new String(
204 qualifierName, start, (end - start), StandardCharsets.UTF_8);
205 if (qualifierString.equals(DISALLOWED_TABLE_NAME)) {
206 // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
207 // A znode named "zookeeper" is disallowed by zookeeper.
208 throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'");
210 for (int i = 0; i < qualifierString.length(); i++) {
211 // Treat the string as a char-array as some characters may be multi-byte
212 char c = qualifierString.charAt(i);
213 // Check for letter, digit, underscore, hyphen, or period, and allowed by ZK.
214 // ZooKeeper also has limitations, but Character.isAlphabetic omits those all
215 // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
216 if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_' || c == '-' || c == '.') {
217 continue;
219 throw new IllegalArgumentException("Illegal character code:" + (int) c + ", <" + c + "> at " +
220 i + ". " + (isSnapshot ? "Snapshot" : "User-space table") +
221 " qualifiers may only contain 'alphanumeric characters' and digits: " +
222 qualifierString);
226 public static void isLegalNamespaceName(byte[] namespaceName) {
227 isLegalNamespaceName(namespaceName, 0, namespaceName.length);
231 * Valid namespace characters are alphabetic characters, numbers, and underscores.
233 public static void isLegalNamespaceName(final byte[] namespaceName,
234 final int start,
235 final int end) {
236 if(end - start < 1) {
237 throw new IllegalArgumentException("Namespace name must not be empty");
239 String nsString = new String(namespaceName, start, (end - start), StandardCharsets.UTF_8);
240 if (nsString.equals(DISALLOWED_TABLE_NAME)) {
241 // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
242 // A znode named "zookeeper" is disallowed by zookeeper.
243 throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'");
245 for (int i = 0; i < nsString.length(); i++) {
246 // Treat the string as a char-array as some characters may be multi-byte
247 char c = nsString.charAt(i);
248 // ZooKeeper also has limitations, but Character.isAlphabetic omits those all
249 // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
250 if (Character.isAlphabetic(c) || Character.isDigit(c)|| c == '_') {
251 continue;
253 throw new IllegalArgumentException("Illegal character <" + c +
254 "> at " + i + ". Namespaces may only contain " +
255 "'alphanumeric characters' from any language and digits: " + nsString);
259 public byte[] getName() {
260 return name;
263 public String getNameAsString() {
264 return nameAsString;
267 public byte[] getNamespace() {
268 return namespace;
271 public String getNamespaceAsString() {
272 return namespaceAsString;
276 * Ideally, getNameAsString should contain namespace within it,
277 * but if the namespace is default, it just returns the name. This method
278 * takes care of this corner case.
280 public String getNameWithNamespaceInclAsString() {
281 if(getNamespaceAsString().equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR)) {
282 return NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR +
283 TableName.NAMESPACE_DELIM + getNameAsString();
285 return getNameAsString();
288 public byte[] getQualifier() {
289 return qualifier;
292 public String getQualifierAsString() {
293 return qualifierAsString;
297 * @return A pointer to TableName as String bytes.
299 public byte[] toBytes() {
300 return name;
303 public boolean isSystemTable() {
304 return systemTable;
307 @Override
308 public String toString() {
309 return nameAsString;
314 * @throws IllegalArgumentException See {@link #valueOf(byte[])}
316 private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException {
317 this.qualifier = new byte[qualifier.remaining()];
318 qualifier.duplicate().get(this.qualifier);
319 this.qualifierAsString = Bytes.toString(this.qualifier);
321 if (qualifierAsString.equals(OLD_ROOT_STR)) {
322 throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated.");
324 if (qualifierAsString.equals(OLD_META_STR)) {
325 throw new IllegalArgumentException(OLD_META_STR + " no longer exists. The table has been " +
326 "renamed to " + META_TABLE_NAME);
329 if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) {
330 // Using the same objects: this will make the comparison faster later
331 this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
332 this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
333 this.systemTable = false;
335 // The name does not include the namespace when it's the default one.
336 this.nameAsString = qualifierAsString;
337 this.name = this.qualifier;
338 } else {
339 if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) {
340 this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
341 this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
342 this.systemTable = true;
343 } else {
344 this.namespace = new byte[namespace.remaining()];
345 namespace.duplicate().get(this.namespace);
346 this.namespaceAsString = Bytes.toString(this.namespace);
347 this.systemTable = false;
349 this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
350 this.name = Bytes.toBytes(nameAsString);
353 this.hashCode = nameAsString.hashCode();
355 isLegalNamespaceName(this.namespace);
356 isLegalTableQualifierName(this.qualifier);
360 * This is only for the old and meta tables.
362 private TableName(String qualifier) {
363 this.qualifier = Bytes.toBytes(qualifier);
364 this.qualifierAsString = qualifier;
366 this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME;
367 this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR;
368 this.systemTable = true;
370 // WARNING: nameAsString is different than name for old meta & root!
371 // This is by design.
372 this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString;
373 this.name = this.qualifier;
375 this.hashCode = nameAsString.hashCode();
380 * Check that the object does not exist already. There are two reasons for creating the objects
381 * only once:
382 * 1) With 100K regions, the table names take ~20MB.
383 * 2) Equals becomes much faster as it's resolved with a reference and an int comparison.
385 private static TableName createTableNameIfNecessary(ByteBuffer bns, ByteBuffer qns) {
386 for (TableName tn : tableCache) {
387 if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
388 return tn;
392 TableName newTable = new TableName(bns, qns);
393 if (tableCache.add(newTable)) { // Adds the specified element if it is not already present
394 return newTable;
397 // Someone else added it. Let's find it.
398 for (TableName tn : tableCache) {
399 if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) {
400 return tn;
403 // this should never happen.
404 throw new IllegalStateException(newTable + " was supposed to be in the cache");
409 * It is used to create table names for old META, and ROOT table.
410 * These tables are not really legal tables. They are not added into the cache.
411 * @return a dummy TableName instance (with no validation) for the passed qualifier
413 private static TableName getADummyTableName(String qualifier) {
414 return new TableName(qualifier);
418 public static TableName valueOf(String namespaceAsString, String qualifierAsString) {
419 if (namespaceAsString == null || namespaceAsString.length() < 1) {
420 namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
423 for (TableName tn : tableCache) {
424 if (qualifierAsString.equals(tn.getQualifierAsString()) &&
425 namespaceAsString.equals(tn.getNamespaceAsString())) {
426 return tn;
430 return createTableNameIfNecessary(
431 ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)),
432 ByteBuffer.wrap(Bytes.toBytes(qualifierAsString)));
437 * @param fullName will use the entire byte array
438 * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
439 * depends on this. The test is buried in the table creation to save on array comparison
440 * when we're creating a standard table object that will be in the cache.
442 public static TableName valueOf(byte[] fullName) throws IllegalArgumentException{
443 return valueOf(fullName, 0, fullName.length);
447 * @param fullName byte array to look into
448 * @param offset within said array
449 * @param length within said array
450 * @throws IllegalArgumentException if fullName equals old root or old meta.
452 public static TableName valueOf(byte[] fullName, int offset, int length)
453 throws IllegalArgumentException {
454 Preconditions.checkArgument(offset >= 0, "offset must be non-negative but was %s", offset);
455 Preconditions.checkArgument(offset < fullName.length, "offset (%s) must be < array length (%s)",
456 offset, fullName.length);
457 Preconditions.checkArgument(length <= fullName.length,
458 "length (%s) must be <= array length (%s)", length, fullName.length);
459 for (TableName tn : tableCache) {
460 final byte[] tnName = tn.getName();
461 if (Bytes.equals(tnName, 0, tnName.length, fullName, offset, length)) {
462 return tn;
466 int namespaceDelimIndex = ArrayUtils.lastIndexOf(fullName, (byte) NAMESPACE_DELIM,
467 offset + length - 1);
469 if (namespaceDelimIndex < offset) {
470 return createTableNameIfNecessary(
471 ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
472 ByteBuffer.wrap(fullName, offset, length));
473 } else {
474 return createTableNameIfNecessary(
475 ByteBuffer.wrap(fullName, offset, namespaceDelimIndex),
476 ByteBuffer.wrap(fullName, namespaceDelimIndex + 1, length - (namespaceDelimIndex + 1)));
481 * @param fullname of a table, possibly with a leading namespace and ':' as delimiter.
482 * @throws IllegalArgumentException if fullName equals old root or old meta.
484 public static TableName valueOf(ByteBuffer fullname) {
485 fullname = fullname.duplicate();
486 fullname.mark();
487 boolean miss = true;
488 while (fullname.hasRemaining() && miss) {
489 miss = ((byte) NAMESPACE_DELIM) != fullname.get();
491 if (miss) {
492 fullname.reset();
493 return valueOf(null, fullname);
494 } else {
495 ByteBuffer qualifier = fullname.slice();
496 int delimiterIndex = fullname.position() - 1;
497 fullname.reset();
498 // changing variable name for clarity
499 ByteBuffer namespace = fullname.duplicate();
500 namespace.limit(delimiterIndex);
501 return valueOf(namespace, qualifier);
506 * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
507 * depends on this.
509 public static TableName valueOf(String name) {
510 for (TableName tn : tableCache) {
511 if (name.equals(tn.getNameAsString())) {
512 return tn;
516 final int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM);
518 if (namespaceDelimIndex < 0) {
519 return createTableNameIfNecessary(
520 ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME),
521 ByteBuffer.wrap(Bytes.toBytes(name)));
522 } else {
523 // indexOf is by character, not byte (consider multi-byte characters)
524 String ns = name.substring(0, namespaceDelimIndex);
525 String qualifier = name.substring(namespaceDelimIndex + 1);
526 return createTableNameIfNecessary(
527 ByteBuffer.wrap(Bytes.toBytes(ns)),
528 ByteBuffer.wrap(Bytes.toBytes(qualifier)));
533 public static TableName valueOf(byte[] namespace, byte[] qualifier) {
534 if (namespace == null || namespace.length < 1) {
535 namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME;
538 for (TableName tn : tableCache) {
539 if (Arrays.equals(tn.getQualifier(), qualifier) &&
540 Arrays.equals(tn.getNamespace(), namespace)) {
541 return tn;
545 return createTableNameIfNecessary(
546 ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier));
549 public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) {
550 if (namespace == null || namespace.remaining() < 1) {
551 return createTableNameIfNecessary(
552 ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), qualifier);
555 return createTableNameIfNecessary(namespace, qualifier);
558 @Override
559 public boolean equals(Object o) {
560 if (this == o) return true;
561 if (o == null || getClass() != o.getClass()) return false;
563 TableName tableName = (TableName) o;
565 return o.hashCode() == hashCode && nameAsString.equals(tableName.nameAsString);
568 @Override
569 public int hashCode() {
570 return hashCode;
574 * For performance reasons, the ordering is not lexicographic.
576 @Override
577 public int compareTo(TableName tableName) {
578 if (this == tableName) return 0;
579 if (this.hashCode < tableName.hashCode()) {
580 return -1;
582 if (this.hashCode > tableName.hashCode()) {
583 return 1;
585 return this.nameAsString.compareTo(tableName.getNameAsString());