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