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
;
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
;
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.
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
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.
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");
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>
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
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=>foo, table=>bar
145 * org:foo.bar, namespace=org, table=>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
);
156 isLegalNamespaceName(tableName
, 0, namespaceDelimIndex
);
157 isLegalTableQualifierName(tableName
, namespaceDelimIndex
+ 1, tableName
.length
);
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
,
185 isLegalTableQualifierName(qualifierName
, start
, end
, false);
188 public static void isLegalTableQualifierName(final byte[] qualifierName
,
191 boolean isSnapshot
) {
192 if(end
- start
< 1) {
193 throw new IllegalArgumentException(isSnapshot ?
"Snapshot" : "Table" + " qualifier must not be empty");
195 String qualifierString
= Bytes
.toString(qualifierName
, start
, end
- start
);
196 if (qualifierName
[start
] == '.' || qualifierName
[start
] == '-') {
197 throw new IllegalArgumentException("Illegal first character <" + qualifierName
[start
] +
198 "> at 0. " + (isSnapshot ?
"Snapshot" : "User-space table") +
199 " qualifiers can only start with 'alphanumeric " +
200 "characters' from any language: " +
203 if (qualifierString
.equals(DISALLOWED_TABLE_NAME
)) {
204 // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
205 // A znode named "zookeeper" is disallowed by zookeeper.
206 throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME
+ "'");
208 for (int i
= 0; i
< qualifierString
.length(); i
++) {
209 // Treat the string as a char-array as some characters may be multi-byte
210 char c
= qualifierString
.charAt(i
);
211 // Check for letter, digit, underscore, hyphen, or period, and allowed by ZK.
212 // ZooKeeper also has limitations, but Character.isAlphabetic omits those all
213 // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
214 if (Character
.isAlphabetic(c
) || Character
.isDigit(c
) || c
== '_' || c
== '-' || c
== '.') {
217 throw new IllegalArgumentException("Illegal character code:" + (int) c
+ ", <" + c
+ "> at " +
218 i
+ ". " + (isSnapshot ?
"Snapshot" : "User-space table") +
219 " qualifiers may only contain 'alphanumeric characters' and digits: " +
224 public static void isLegalNamespaceName(byte[] namespaceName
) {
225 isLegalNamespaceName(namespaceName
, 0, namespaceName
.length
);
229 * Valid namespace characters are alphabetic characters, numbers, and underscores.
231 public static void isLegalNamespaceName(final byte[] namespaceName
,
234 if(end
- start
< 1) {
235 throw new IllegalArgumentException("Namespace name must not be empty");
237 String nsString
= new String(namespaceName
, start
, (end
- start
), StandardCharsets
.UTF_8
);
238 if (nsString
.equals(DISALLOWED_TABLE_NAME
)) {
239 // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
240 // A znode named "zookeeper" is disallowed by zookeeper.
241 throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME
+ "'");
243 for (int i
= 0; i
< nsString
.length(); i
++) {
244 // Treat the string as a char-array as some characters may be multi-byte
245 char c
= nsString
.charAt(i
);
246 // ZooKeeper also has limitations, but Character.isAlphabetic omits those all
247 // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel
248 if (Character
.isAlphabetic(c
) || Character
.isDigit(c
)|| c
== '_') {
251 throw new IllegalArgumentException("Illegal character <" + c
+
252 "> at " + i
+ ". Namespaces may only contain " +
253 "'alphanumeric characters' from any language and digits: " + nsString
);
257 public byte[] getName() {
261 public String
getNameAsString() {
265 public byte[] getNamespace() {
269 public String
getNamespaceAsString() {
270 return namespaceAsString
;
274 * Ideally, getNameAsString should contain namespace within it,
275 * but if the namespace is default, it just returns the name. This method
276 * takes care of this corner case.
278 public String
getNameWithNamespaceInclAsString() {
279 if(getNamespaceAsString().equals(NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME_STR
)) {
280 return NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME_STR
+
281 TableName
.NAMESPACE_DELIM
+ getNameAsString();
283 return getNameAsString();
286 public byte[] getQualifier() {
290 public String
getQualifierAsString() {
291 return qualifierAsString
;
295 * @return A pointer to TableName as String bytes.
297 public byte[] toBytes() {
301 public boolean isSystemTable() {
306 public String
toString() {
312 * @throws IllegalArgumentException See {@link #valueOf(byte[])}
314 private TableName(ByteBuffer namespace
, ByteBuffer qualifier
) throws IllegalArgumentException
{
315 this.qualifier
= new byte[qualifier
.remaining()];
316 qualifier
.duplicate().get(this.qualifier
);
317 this.qualifierAsString
= Bytes
.toString(this.qualifier
);
319 if (qualifierAsString
.equals(OLD_ROOT_STR
)) {
320 throw new IllegalArgumentException(OLD_ROOT_STR
+ " has been deprecated.");
322 if (qualifierAsString
.equals(OLD_META_STR
)) {
323 throw new IllegalArgumentException(OLD_META_STR
+ " no longer exists. The table has been " +
324 "renamed to " + META_TABLE_NAME
);
327 if (Bytes
.equals(NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME
, namespace
)) {
328 // Using the same objects: this will make the comparison faster later
329 this.namespace
= NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME
;
330 this.namespaceAsString
= NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME_STR
;
331 this.systemTable
= false;
333 // The name does not include the namespace when it's the default one.
334 this.nameAsString
= qualifierAsString
;
335 this.name
= this.qualifier
;
337 if (Bytes
.equals(NamespaceDescriptor
.SYSTEM_NAMESPACE_NAME
, namespace
)) {
338 this.namespace
= NamespaceDescriptor
.SYSTEM_NAMESPACE_NAME
;
339 this.namespaceAsString
= NamespaceDescriptor
.SYSTEM_NAMESPACE_NAME_STR
;
340 this.systemTable
= true;
342 this.namespace
= new byte[namespace
.remaining()];
343 namespace
.duplicate().get(this.namespace
);
344 this.namespaceAsString
= Bytes
.toString(this.namespace
);
345 this.systemTable
= false;
347 this.nameAsString
= namespaceAsString
+ NAMESPACE_DELIM
+ qualifierAsString
;
348 this.name
= Bytes
.toBytes(nameAsString
);
351 this.hashCode
= nameAsString
.hashCode();
353 isLegalNamespaceName(this.namespace
);
354 isLegalTableQualifierName(this.qualifier
);
358 * This is only for the old and meta tables.
360 private TableName(String qualifier
) {
361 this.qualifier
= Bytes
.toBytes(qualifier
);
362 this.qualifierAsString
= qualifier
;
364 this.namespace
= NamespaceDescriptor
.SYSTEM_NAMESPACE_NAME
;
365 this.namespaceAsString
= NamespaceDescriptor
.SYSTEM_NAMESPACE_NAME_STR
;
366 this.systemTable
= true;
368 // WARNING: nameAsString is different than name for old meta & root!
369 // This is by design.
370 this.nameAsString
= namespaceAsString
+ NAMESPACE_DELIM
+ qualifierAsString
;
371 this.name
= this.qualifier
;
373 this.hashCode
= nameAsString
.hashCode();
378 * Check that the object does not exist already. There are two reasons for creating the objects
380 * 1) With 100K regions, the table names take ~20MB.
381 * 2) Equals becomes much faster as it's resolved with a reference and an int comparison.
383 private static TableName
createTableNameIfNecessary(ByteBuffer bns
, ByteBuffer qns
) {
384 for (TableName tn
: tableCache
) {
385 if (Bytes
.equals(tn
.getQualifier(), qns
) && Bytes
.equals(tn
.getNamespace(), bns
)) {
390 TableName newTable
= new TableName(bns
, qns
);
391 if (tableCache
.add(newTable
)) { // Adds the specified element if it is not already present
395 // Someone else added it. Let's find it.
396 for (TableName tn
: tableCache
) {
397 if (Bytes
.equals(tn
.getQualifier(), qns
) && Bytes
.equals(tn
.getNamespace(), bns
)) {
401 // this should never happen.
402 throw new IllegalStateException(newTable
+ " was supposed to be in the cache");
407 * It is used to create table names for old META, and ROOT table.
408 * These tables are not really legal tables. They are not added into the cache.
409 * @return a dummy TableName instance (with no validation) for the passed qualifier
411 private static TableName
getADummyTableName(String qualifier
) {
412 return new TableName(qualifier
);
416 public static TableName
valueOf(String namespaceAsString
, String qualifierAsString
) {
417 if (namespaceAsString
== null || namespaceAsString
.length() < 1) {
418 namespaceAsString
= NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME_STR
;
421 for (TableName tn
: tableCache
) {
422 if (qualifierAsString
.equals(tn
.getQualifierAsString()) &&
423 namespaceAsString
.equals(tn
.getNamespaceAsString())) {
428 return createTableNameIfNecessary(
429 ByteBuffer
.wrap(Bytes
.toBytes(namespaceAsString
)),
430 ByteBuffer
.wrap(Bytes
.toBytes(qualifierAsString
)));
435 * @param fullName will use the entire byte array
436 * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
437 * depends on this. The test is buried in the table creation to save on array comparison
438 * when we're creating a standard table object that will be in the cache.
440 public static TableName
valueOf(byte[] fullName
) throws IllegalArgumentException
{
441 return valueOf(fullName
, 0, fullName
.length
);
445 * @param fullName byte array to look into
446 * @param offset within said array
447 * @param length within said array
448 * @throws IllegalArgumentException if fullName equals old root or old meta.
450 public static TableName
valueOf(byte[] fullName
, int offset
, int length
)
451 throws IllegalArgumentException
{
452 Preconditions
.checkArgument(offset
>= 0, "offset must be non-negative but was %s", offset
);
453 Preconditions
.checkArgument(offset
< fullName
.length
, "offset (%s) must be < array length (%s)",
454 offset
, fullName
.length
);
455 Preconditions
.checkArgument(length
<= fullName
.length
,
456 "length (%s) must be <= array length (%s)", length
, fullName
.length
);
457 for (TableName tn
: tableCache
) {
458 final byte[] tnName
= tn
.getName();
459 if (Bytes
.equals(tnName
, 0, tnName
.length
, fullName
, offset
, length
)) {
464 int namespaceDelimIndex
= ArrayUtils
.lastIndexOf(fullName
, (byte) NAMESPACE_DELIM
,
465 offset
+ length
- 1);
467 if (namespaceDelimIndex
< offset
) {
468 return createTableNameIfNecessary(
469 ByteBuffer
.wrap(NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME
),
470 ByteBuffer
.wrap(fullName
, offset
, length
));
472 return createTableNameIfNecessary(
473 ByteBuffer
.wrap(fullName
, offset
, namespaceDelimIndex
),
474 ByteBuffer
.wrap(fullName
, namespaceDelimIndex
+ 1, length
- (namespaceDelimIndex
+ 1)));
479 * @param fullname of a table, possibly with a leading namespace and ':' as delimiter.
480 * @throws IllegalArgumentException if fullName equals old root or old meta.
482 public static TableName
valueOf(ByteBuffer fullname
) {
483 fullname
= fullname
.duplicate();
486 while (fullname
.hasRemaining() && miss
) {
487 miss
= ((byte) NAMESPACE_DELIM
) != fullname
.get();
491 return valueOf(null, fullname
);
493 ByteBuffer qualifier
= fullname
.slice();
494 int delimiterIndex
= fullname
.position() - 1;
496 // changing variable name for clarity
497 ByteBuffer namespace
= fullname
.duplicate();
498 namespace
.limit(delimiterIndex
);
499 return valueOf(namespace
, qualifier
);
504 * @throws IllegalArgumentException if fullName equals old root or old meta. Some code
507 public static TableName
valueOf(String name
) {
508 for (TableName tn
: tableCache
) {
509 if (name
.equals(tn
.getNameAsString())) {
514 final int namespaceDelimIndex
= name
.indexOf(NAMESPACE_DELIM
);
516 if (namespaceDelimIndex
< 0) {
517 return createTableNameIfNecessary(
518 ByteBuffer
.wrap(NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME
),
519 ByteBuffer
.wrap(Bytes
.toBytes(name
)));
521 // indexOf is by character, not byte (consider multi-byte characters)
522 String ns
= name
.substring(0, namespaceDelimIndex
);
523 String qualifier
= name
.substring(namespaceDelimIndex
+ 1);
524 return createTableNameIfNecessary(
525 ByteBuffer
.wrap(Bytes
.toBytes(ns
)),
526 ByteBuffer
.wrap(Bytes
.toBytes(qualifier
)));
531 public static TableName
valueOf(byte[] namespace
, byte[] qualifier
) {
532 if (namespace
== null || namespace
.length
< 1) {
533 namespace
= NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME
;
536 for (TableName tn
: tableCache
) {
537 if (Arrays
.equals(tn
.getQualifier(), qualifier
) &&
538 Arrays
.equals(tn
.getNamespace(), namespace
)) {
543 return createTableNameIfNecessary(
544 ByteBuffer
.wrap(namespace
), ByteBuffer
.wrap(qualifier
));
547 public static TableName
valueOf(ByteBuffer namespace
, ByteBuffer qualifier
) {
548 if (namespace
== null || namespace
.remaining() < 1) {
549 return createTableNameIfNecessary(
550 ByteBuffer
.wrap(NamespaceDescriptor
.DEFAULT_NAMESPACE_NAME
), qualifier
);
553 return createTableNameIfNecessary(namespace
, qualifier
);
557 public boolean equals(Object o
) {
558 if (this == o
) return true;
559 if (o
== null || getClass() != o
.getClass()) return false;
561 TableName tableName
= (TableName
) o
;
563 return o
.hashCode() == hashCode
&& nameAsString
.equals(tableName
.nameAsString
);
567 public int hashCode() {
572 * For performance reasons, the ordering is not lexicographic.
575 public int compareTo(TableName tableName
) {
576 if (this == tableName
) return 0;
577 if (this.hashCode
< tableName
.hashCode()) {
580 if (this.hashCode
> tableName
.hashCode()) {
583 return this.nameAsString
.compareTo(tableName
.getNameAsString());