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=>foo, table=>bar 125 * org:foo.bar, namespace=org, table=>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}