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 /** 089 * @return True if <code>tn</code> is the hbase:meta table name. 090 */ 091 public static boolean isMetaTableName(final TableName tn) { 092 return tn.equals(TableName.META_TABLE_NAME); 093 } 094 095 /** 096 * TableName for old -ROOT- table. It is used to read/process old WALs which have ROOT edits. 097 */ 098 public static final TableName OLD_ROOT_TABLE_NAME = getADummyTableName(OLD_ROOT_STR); 099 /** 100 * TableName for old .META. table. Used in testing. 101 */ 102 public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR); 103 104 private final byte[] name; 105 private final String nameAsString; 106 private final byte[] namespace; 107 private final String namespaceAsString; 108 private final byte[] qualifier; 109 private final String qualifierAsString; 110 private final boolean systemTable; 111 private final int hashCode; 112 113 /** 114 * Check passed byte array, "tableName", is legal user-space table name. 115 * @return Returns passed <code>tableName</code> param 116 * @throws IllegalArgumentException if passed a tableName is null or is made of other than 'word' 117 * characters or underscores: i.e. 118 * <code>[\p{IsAlphabetic}\p{Digit}.-:]</code>. The ':' is used 119 * to delimit the namespace from the table name and can be used 120 * for nothing else. Namespace names can only contain 'word' 121 * characters <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_' 122 * Qualifier names can only contain 'word' characters 123 * <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_', '.' or '-'. 124 * The name may not start with '.' or '-'. Valid fully qualified 125 * table names: foo:bar, namespace=>foo, table=>bar 126 * org:foo.bar, namespace=org, table=>foo.bar 127 */ 128 public static byte[] isLegalFullyQualifiedTableName(final byte[] tableName) { 129 if (tableName == null || tableName.length <= 0) { 130 throw new IllegalArgumentException("Name is null or empty"); 131 } 132 133 int namespaceDelimIndex = ArrayUtils.lastIndexOf(tableName, (byte) NAMESPACE_DELIM); 134 if (namespaceDelimIndex < 0) { 135 isLegalTableQualifierName(tableName); 136 } else { 137 isLegalNamespaceName(tableName, 0, namespaceDelimIndex); 138 isLegalTableQualifierName(tableName, namespaceDelimIndex + 1, tableName.length); 139 } 140 return tableName; 141 } 142 143 public static byte[] isLegalTableQualifierName(final byte[] qualifierName) { 144 isLegalTableQualifierName(qualifierName, 0, qualifierName.length, false); 145 return qualifierName; 146 } 147 148 public static byte[] isLegalTableQualifierName(final byte[] qualifierName, boolean isSnapshot) { 149 isLegalTableQualifierName(qualifierName, 0, qualifierName.length, isSnapshot); 150 return qualifierName; 151 } 152 153 /** 154 * Qualifier names can only contain 'word' characters <code>[\p{IsAlphabetic}\p{Digit}]</code> or 155 * '_', '.' or '-'. The name may not start with '.' or '-'. 156 * @param qualifierName byte array containing the qualifier name 157 * @param start start index 158 * @param end end index (exclusive) 159 */ 160 public static void isLegalTableQualifierName(final byte[] qualifierName, int start, int end) { 161 isLegalTableQualifierName(qualifierName, start, end, false); 162 } 163 164 public static void isLegalTableQualifierName(final byte[] qualifierName, int start, int end, 165 boolean isSnapshot) { 166 if (end - start < 1) { 167 throw new IllegalArgumentException( 168 isSnapshot ? "Snapshot" : "Table" + " qualifier must not be empty"); 169 } 170 String qualifierString = Bytes.toString(qualifierName, start, end - start); 171 if (qualifierName[start] == '.' || qualifierName[start] == '-') { 172 throw new IllegalArgumentException("Illegal first character <" + qualifierName[start] 173 + "> at 0. " + (isSnapshot ? "Snapshot" : "User-space table") 174 + " qualifiers can only start with 'alphanumeric " + "characters' from any language: " 175 + qualifierString); 176 } 177 if (qualifierString.equals(DISALLOWED_TABLE_NAME)) { 178 // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel 179 // A znode named "zookeeper" is disallowed by zookeeper. 180 throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'"); 181 } 182 for (int i = 0; i < qualifierString.length(); i++) { 183 // Treat the string as a char-array as some characters may be multi-byte 184 char c = qualifierString.charAt(i); 185 // Check for letter, digit, underscore, hyphen, or period, and allowed by ZK. 186 // ZooKeeper also has limitations, but Character.isAlphabetic omits those all 187 // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel 188 if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_' || c == '-' || c == '.') { 189 continue; 190 } 191 throw new IllegalArgumentException("Illegal character code:" + (int) c + ", <" + c + "> at " 192 + i + ". " + (isSnapshot ? "Snapshot" : "User-space table") 193 + " qualifiers may only contain 'alphanumeric characters' and digits: " + qualifierString); 194 } 195 } 196 197 public static void isLegalNamespaceName(byte[] namespaceName) { 198 isLegalNamespaceName(namespaceName, 0, namespaceName.length); 199 } 200 201 /** 202 * Valid namespace characters are alphabetic characters, numbers, and underscores. 203 */ 204 public static void isLegalNamespaceName(final byte[] namespaceName, final int start, 205 final int end) { 206 if (end - start < 1) { 207 throw new IllegalArgumentException("Namespace name must not be empty"); 208 } 209 String nsString = new String(namespaceName, start, (end - start), StandardCharsets.UTF_8); 210 if (nsString.equals(DISALLOWED_TABLE_NAME)) { 211 // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel 212 // A znode named "zookeeper" is disallowed by zookeeper. 213 throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'"); 214 } 215 for (int i = 0; i < nsString.length(); i++) { 216 // Treat the string as a char-array as some characters may be multi-byte 217 char c = nsString.charAt(i); 218 // ZooKeeper also has limitations, but Character.isAlphabetic omits those all 219 // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel 220 if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_') { 221 continue; 222 } 223 throw new IllegalArgumentException( 224 "Illegal character <" + c + "> at " + i + ". Namespaces may only contain " 225 + "'alphanumeric characters' from any language and digits: " + nsString); 226 } 227 } 228 229 public byte[] getName() { 230 return name; 231 } 232 233 public String getNameAsString() { 234 return nameAsString; 235 } 236 237 public byte[] getNamespace() { 238 return namespace; 239 } 240 241 public String getNamespaceAsString() { 242 return namespaceAsString; 243 } 244 245 /** 246 * Ideally, getNameAsString should contain namespace within it, but if the namespace is default, 247 * it just returns the name. This method takes care of this corner case. 248 */ 249 public String getNameWithNamespaceInclAsString() { 250 if (getNamespaceAsString().equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR)) { 251 return NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR + TableName.NAMESPACE_DELIM 252 + getNameAsString(); 253 } 254 return getNameAsString(); 255 } 256 257 public byte[] getQualifier() { 258 return qualifier; 259 } 260 261 public String getQualifierAsString() { 262 return qualifierAsString; 263 } 264 265 /** 266 * @return A pointer to TableName as String bytes. 267 */ 268 public byte[] toBytes() { 269 return name; 270 } 271 272 public boolean isSystemTable() { 273 return systemTable; 274 } 275 276 @Override 277 public String toString() { 278 return nameAsString; 279 } 280 281 /** 282 * @throws IllegalArgumentException See {@link #valueOf(byte[])} 283 */ 284 private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException { 285 this.qualifier = new byte[qualifier.remaining()]; 286 qualifier.duplicate().get(this.qualifier); 287 this.qualifierAsString = Bytes.toString(this.qualifier); 288 289 if (qualifierAsString.equals(OLD_ROOT_STR)) { 290 throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated."); 291 } 292 if (qualifierAsString.equals(OLD_META_STR)) { 293 throw new IllegalArgumentException( 294 OLD_META_STR + " no longer exists. The table has been " + "renamed to " + META_TABLE_NAME); 295 } 296 297 if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) { 298 // Using the same objects: this will make the comparison faster later 299 this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME; 300 this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR; 301 this.systemTable = false; 302 303 // The name does not include the namespace when it's the default one. 304 this.nameAsString = qualifierAsString; 305 this.name = this.qualifier; 306 } else { 307 if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) { 308 this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME; 309 this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR; 310 this.systemTable = true; 311 } else { 312 this.namespace = new byte[namespace.remaining()]; 313 namespace.duplicate().get(this.namespace); 314 this.namespaceAsString = Bytes.toString(this.namespace); 315 this.systemTable = false; 316 } 317 this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString; 318 this.name = Bytes.toBytes(nameAsString); 319 } 320 321 this.hashCode = nameAsString.hashCode(); 322 323 isLegalNamespaceName(this.namespace); 324 isLegalTableQualifierName(this.qualifier); 325 } 326 327 /** 328 * This is only for the old and meta tables. 329 */ 330 private TableName(String qualifier) { 331 this.qualifier = Bytes.toBytes(qualifier); 332 this.qualifierAsString = qualifier; 333 334 this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME; 335 this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR; 336 this.systemTable = true; 337 338 // WARNING: nameAsString is different than name for old meta & root! 339 // This is by design. 340 this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString; 341 this.name = this.qualifier; 342 343 this.hashCode = nameAsString.hashCode(); 344 } 345 346 /** 347 * Check that the object does not exist already. There are two reasons for creating the objects 348 * only once: 1) With 100K regions, the table names take ~20MB. 2) Equals becomes much faster as 349 * it's resolved with a reference and an int comparison. 350 */ 351 private static TableName createTableNameIfNecessary(ByteBuffer bns, ByteBuffer qns) { 352 for (TableName tn : tableCache) { 353 if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) { 354 return tn; 355 } 356 } 357 358 TableName newTable = new TableName(bns, qns); 359 if (tableCache.add(newTable)) { // Adds the specified element if it is not already present 360 return newTable; 361 } 362 363 // Someone else added it. Let's find it. 364 for (TableName tn : tableCache) { 365 if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) { 366 return tn; 367 } 368 } 369 // this should never happen. 370 throw new IllegalStateException(newTable + " was supposed to be in the cache"); 371 } 372 373 /** 374 * It is used to create table names for old META, and ROOT table. These tables are not really 375 * legal tables. They are not added into the cache. 376 * @return a dummy TableName instance (with no validation) for the passed qualifier 377 */ 378 private static TableName getADummyTableName(String qualifier) { 379 return new TableName(qualifier); 380 } 381 382 public static TableName valueOf(String namespaceAsString, String qualifierAsString) { 383 if (namespaceAsString == null || namespaceAsString.length() < 1) { 384 namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR; 385 } 386 387 for (TableName tn : tableCache) { 388 if ( 389 qualifierAsString.equals(tn.getQualifierAsString()) 390 && namespaceAsString.equals(tn.getNamespaceAsString()) 391 ) { 392 return tn; 393 } 394 } 395 396 return createTableNameIfNecessary(ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)), 397 ByteBuffer.wrap(Bytes.toBytes(qualifierAsString))); 398 } 399 400 /** 401 * @param fullName will use the entire byte array 402 * @throws IllegalArgumentException if fullName equals old root or old meta. Some code depends on 403 * this. The test is buried in the table creation to save on 404 * array comparison when we're creating a standard table object 405 * that will be in the cache. 406 */ 407 public static TableName valueOf(byte[] fullName) throws IllegalArgumentException { 408 return valueOf(fullName, 0, fullName.length); 409 } 410 411 /** 412 * @param fullName byte array to look into 413 * @param offset within said array 414 * @param length within said array 415 * @throws IllegalArgumentException if fullName equals old root or old meta. 416 */ 417 public static TableName valueOf(byte[] fullName, int offset, int length) 418 throws IllegalArgumentException { 419 Preconditions.checkArgument(offset >= 0, "offset must be non-negative but was %s", offset); 420 Preconditions.checkArgument(offset < fullName.length, "offset (%s) must be < array length (%s)", 421 offset, fullName.length); 422 Preconditions.checkArgument(length <= fullName.length, 423 "length (%s) must be <= array length (%s)", length, fullName.length); 424 for (TableName tn : tableCache) { 425 final byte[] tnName = tn.getName(); 426 if (Bytes.equals(tnName, 0, tnName.length, fullName, offset, length)) { 427 return tn; 428 } 429 } 430 431 int namespaceDelimIndex = 432 ArrayUtils.lastIndexOf(fullName, (byte) NAMESPACE_DELIM, offset + length - 1); 433 434 if (namespaceDelimIndex < offset) { 435 return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), 436 ByteBuffer.wrap(fullName, offset, length)); 437 } else { 438 return createTableNameIfNecessary(ByteBuffer.wrap(fullName, offset, namespaceDelimIndex), 439 ByteBuffer.wrap(fullName, namespaceDelimIndex + 1, length - (namespaceDelimIndex + 1))); 440 } 441 } 442 443 /** 444 * @param fullname of a table, possibly with a leading namespace and ':' as delimiter. 445 * @throws IllegalArgumentException if fullName equals old root or old meta. 446 */ 447 public static TableName valueOf(ByteBuffer fullname) { 448 fullname = fullname.duplicate(); 449 fullname.mark(); 450 boolean miss = true; 451 while (fullname.hasRemaining() && miss) { 452 miss = ((byte) NAMESPACE_DELIM) != fullname.get(); 453 } 454 if (miss) { 455 fullname.reset(); 456 return valueOf(null, fullname); 457 } else { 458 ByteBuffer qualifier = fullname.slice(); 459 int delimiterIndex = fullname.position() - 1; 460 fullname.reset(); 461 // changing variable name for clarity 462 ByteBuffer namespace = fullname.duplicate(); 463 namespace.limit(delimiterIndex); 464 return valueOf(namespace, qualifier); 465 } 466 } 467 468 /** 469 * @throws IllegalArgumentException if fullName equals old root or old meta. Some code depends on 470 * this. 471 */ 472 public static TableName valueOf(String name) { 473 for (TableName tn : tableCache) { 474 if (name.equals(tn.getNameAsString())) { 475 return tn; 476 } 477 } 478 479 final int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM); 480 481 if (namespaceDelimIndex < 0) { 482 return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), 483 ByteBuffer.wrap(Bytes.toBytes(name))); 484 } else { 485 // indexOf is by character, not byte (consider multi-byte characters) 486 String ns = name.substring(0, namespaceDelimIndex); 487 String qualifier = name.substring(namespaceDelimIndex + 1); 488 return createTableNameIfNecessary(ByteBuffer.wrap(Bytes.toBytes(ns)), 489 ByteBuffer.wrap(Bytes.toBytes(qualifier))); 490 } 491 } 492 493 public static TableName valueOf(byte[] namespace, byte[] qualifier) { 494 if (namespace == null || namespace.length < 1) { 495 namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME; 496 } 497 498 for (TableName tn : tableCache) { 499 if ( 500 Arrays.equals(tn.getQualifier(), qualifier) && Arrays.equals(tn.getNamespace(), namespace) 501 ) { 502 return tn; 503 } 504 } 505 506 return createTableNameIfNecessary(ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier)); 507 } 508 509 public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) { 510 if (namespace == null || namespace.remaining() < 1) { 511 return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), 512 qualifier); 513 } 514 515 return createTableNameIfNecessary(namespace, qualifier); 516 } 517 518 @Override 519 public boolean equals(Object o) { 520 if (this == o) return true; 521 if (o == null || getClass() != o.getClass()) return false; 522 523 TableName tableName = (TableName) o; 524 525 return o.hashCode() == hashCode && nameAsString.equals(tableName.nameAsString); 526 } 527 528 @Override 529 public int hashCode() { 530 return hashCode; 531 } 532 533 /** 534 * For performance reasons, the ordering is not lexicographic. 535 */ 536 @Override 537 public int compareTo(TableName tableName) { 538 if (this == tableName) return 0; 539 if (this.hashCode < tableName.hashCode()) { 540 return -1; 541 } 542 if (this.hashCode > tableName.hashCode()) { 543 return 1; 544 } 545 return this.nameAsString.compareTo(tableName.getNameAsString()); 546 } 547 548}