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.hadoop.hbase.util.Bytes; 026import org.apache.yetus.audience.InterfaceAudience; 027 028/** 029 * Immutable POJO class for representing a table name. Which is of the form: <table 030 * namespace>:<table qualifier> Two special namespaces: 1. hbase - system namespace, used 031 * to contain hbase internal tables 2. default - tables with no explicit specified namespace will 032 * automatically fall into this namespace. ie a) foo:bar, means namespace=foo and qualifier=bar b) 033 * bar, means namespace=default and qualifier=bar c) default:bar, means namespace=default and 034 * qualifier=bar 035 * <p> 036 * Internally, in this class, we cache the instances to limit the number of objects and make the 037 * "equals" faster. We try to minimize the number of objects created of the number of array copy to 038 * check if we already have an instance of this TableName. The code is not optimize for a new 039 * instance creation but is optimized to check for existence. 040 * </p> 041 */ 042@InterfaceAudience.Public 043public final class TableName implements Comparable<TableName> { 044 045 /** See {@link #createTableNameIfNecessary(ByteBuffer, ByteBuffer)} */ 046 private static final Set<TableName> tableCache = new CopyOnWriteArraySet<>(); 047 048 /** Namespace delimiter */ 049 // this should always be only 1 byte long 050 public final static char NAMESPACE_DELIM = ':'; 051 052 // A non-capture group so that this can be embedded. 053 // regex is a bit more complicated to support nuance of tables 054 // in default namespace 055 // Allows only letters, digits and '_' 056 public static final String VALID_NAMESPACE_REGEX = "(?:[_\\p{Digit}\\p{IsAlphabetic}]+)"; 057 // Allows only letters, digits, '_', '-' and '.' 058 public static final String VALID_TABLE_QUALIFIER_REGEX = 059 "(?:[_\\p{Digit}\\p{IsAlphabetic}][-_.\\p{Digit}\\p{IsAlphabetic}]*)"; 060 // Concatenation of NAMESPACE_REGEX and TABLE_QUALIFIER_REGEX, 061 // with NAMESPACE_DELIM as delimiter 062 public static final String VALID_USER_TABLE_REGEX = "(?:(?:(?:" + VALID_NAMESPACE_REGEX + "\\" 063 + NAMESPACE_DELIM + ")?)" + "(?:" + VALID_TABLE_QUALIFIER_REGEX + "))"; 064 065 /** The hbase:meta table's name. */ 066 public static final TableName META_TABLE_NAME = 067 valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "meta"); 068 069 /** The Namespace table's name. */ 070 public static final TableName NAMESPACE_TABLE_NAME = 071 valueOf(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR, "namespace"); 072 073 public static final String OLD_META_STR = ".META."; 074 public static final String OLD_ROOT_STR = "-ROOT-"; 075 076 /** One globally disallowed name */ 077 public static final String DISALLOWED_TABLE_NAME = "zookeeper"; 078 079 /** Returns True if <code>tn</code> is the hbase:meta table name. */ 080 public static boolean isMetaTableName(final TableName tn) { 081 return tn.equals(TableName.META_TABLE_NAME); 082 } 083 084 /** 085 * TableName for old -ROOT- table. It is used to read/process old WALs which have ROOT edits. 086 */ 087 public static final TableName OLD_ROOT_TABLE_NAME = getADummyTableName(OLD_ROOT_STR); 088 /** 089 * TableName for old .META. table. Used in testing. 090 */ 091 public static final TableName OLD_META_TABLE_NAME = getADummyTableName(OLD_META_STR); 092 093 private final byte[] name; 094 private final String nameAsString; 095 private final byte[] namespace; 096 private final String namespaceAsString; 097 private final byte[] qualifier; 098 private final String qualifierAsString; 099 private final boolean systemTable; 100 private final int hashCode; 101 102 /** 103 * Check passed byte array, "tableName", is legal user-space table name. 104 * @return Returns passed <code>tableName</code> param 105 * @throws IllegalArgumentException if passed a tableName is null or is made of other than 'word' 106 * characters or underscores: i.e. 107 * <code>[\p{IsAlphabetic}\p{Digit}.-:]</code>. The ':' is used 108 * to delimit the namespace from the table name and can be used 109 * for nothing else. Namespace names can only contain 'word' 110 * characters <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_' 111 * Qualifier names can only contain 'word' characters 112 * <code>[\p{IsAlphabetic}\p{Digit}]</code> or '_', '.' or '-'. 113 * The name may not start with '.' or '-'. Valid fully qualified 114 * table names: foo:bar, namespace=>foo, table=>bar 115 * org:foo.bar, namespace=org, table=>foo.bar 116 */ 117 public static byte[] isLegalFullyQualifiedTableName(final byte[] tableName) { 118 if (tableName == null || tableName.length <= 0) { 119 throw new IllegalArgumentException("Name is null or empty"); 120 } 121 122 int namespaceDelimIndex = org.apache.hbase.thirdparty.com.google.common.primitives.Bytes 123 .lastIndexOf(tableName, (byte) NAMESPACE_DELIM); 124 if (namespaceDelimIndex < 0) { 125 isLegalTableQualifierName(tableName); 126 } else { 127 isLegalNamespaceName(tableName, 0, namespaceDelimIndex); 128 isLegalTableQualifierName(tableName, namespaceDelimIndex + 1, tableName.length); 129 } 130 return tableName; 131 } 132 133 public static byte[] isLegalTableQualifierName(final byte[] qualifierName) { 134 isLegalTableQualifierName(qualifierName, 0, qualifierName.length, false); 135 return qualifierName; 136 } 137 138 public static byte[] isLegalTableQualifierName(final byte[] qualifierName, boolean isSnapshot) { 139 isLegalTableQualifierName(qualifierName, 0, qualifierName.length, isSnapshot); 140 return qualifierName; 141 } 142 143 /** 144 * Qualifier names can only contain 'word' characters <code>[\p{IsAlphabetic}\p{Digit}]</code> or 145 * '_', '.' or '-'. The name may not start with '.' or '-'. 146 * @param qualifierName byte array containing the qualifier name 147 * @param start start index 148 * @param end end index (exclusive) 149 */ 150 public static void isLegalTableQualifierName(final byte[] qualifierName, int start, int end) { 151 isLegalTableQualifierName(qualifierName, start, end, false); 152 } 153 154 public static void isLegalTableQualifierName(final byte[] qualifierName, int start, int end, 155 boolean isSnapshot) { 156 if (end - start < 1) { 157 throw new IllegalArgumentException( 158 isSnapshot ? "Snapshot" : "Table" + " qualifier must not be empty"); 159 } 160 if (qualifierName[start] == '.' || qualifierName[start] == '-') { 161 throw new IllegalArgumentException("Illegal first character <" + qualifierName[start] 162 + "> at 0. " + (isSnapshot ? "Snapshot" : "User-space table") 163 + " qualifiers can only start with 'alphanumeric " + "characters' from any language: " 164 + Bytes.toString(qualifierName, start, end)); 165 } 166 // Treat the bytes as UTF-8 167 String qualifierString = 168 new String(qualifierName, start, (end - start), StandardCharsets.UTF_8); 169 if (qualifierString.equals(DISALLOWED_TABLE_NAME)) { 170 // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel 171 // A znode named "zookeeper" is disallowed by zookeeper. 172 throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'"); 173 } 174 for (int i = 0; i < qualifierString.length(); i++) { 175 // Treat the string as a char-array as some characters may be multi-byte 176 char c = qualifierString.charAt(i); 177 // Check for letter, digit, underscore, hyphen, or period, and allowed by ZK. 178 // ZooKeeper also has limitations, but Character.isAlphabetic omits those all 179 // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel 180 if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_' || c == '-' || c == '.') { 181 continue; 182 } 183 throw new IllegalArgumentException("Illegal character code:" + (int) c + ", <" + c + "> at " 184 + i + ". " + (isSnapshot ? "Snapshot" : "User-space table") 185 + " qualifiers may only contain 'alphanumeric characters' and digits: " + qualifierString); 186 } 187 } 188 189 public static void isLegalNamespaceName(byte[] namespaceName) { 190 isLegalNamespaceName(namespaceName, 0, namespaceName.length); 191 } 192 193 /** 194 * Valid namespace characters are alphabetic characters, numbers, and underscores. 195 */ 196 public static void isLegalNamespaceName(final byte[] namespaceName, final int start, 197 final int end) { 198 if (end - start < 1) { 199 throw new IllegalArgumentException("Namespace name must not be empty"); 200 } 201 String nsString = new String(namespaceName, start, (end - start), StandardCharsets.UTF_8); 202 if (nsString.equals(DISALLOWED_TABLE_NAME)) { 203 // Per https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel 204 // A znode named "zookeeper" is disallowed by zookeeper. 205 throw new IllegalArgumentException("Tables may not be named '" + DISALLOWED_TABLE_NAME + "'"); 206 } 207 for (int i = 0; i < nsString.length(); i++) { 208 // Treat the string as a char-array as some characters may be multi-byte 209 char c = nsString.charAt(i); 210 // ZooKeeper also has limitations, but Character.isAlphabetic omits those all 211 // See https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#ch_zkDataModel 212 if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '_') { 213 continue; 214 } 215 throw new IllegalArgumentException( 216 "Illegal character <" + c + "> at " + i + ". Namespaces may only contain " 217 + "'alphanumeric characters' from any language and digits: " + nsString); 218 } 219 } 220 221 public byte[] getName() { 222 return name; 223 } 224 225 public String getNameAsString() { 226 return nameAsString; 227 } 228 229 public byte[] getNamespace() { 230 return namespace; 231 } 232 233 public String getNamespaceAsString() { 234 return namespaceAsString; 235 } 236 237 /** 238 * Ideally, getNameAsString should contain namespace within it, but if the namespace is default, 239 * it just returns the name. This method takes care of this corner case. 240 */ 241 public String getNameWithNamespaceInclAsString() { 242 if (getNamespaceAsString().equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR)) { 243 return NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR + TableName.NAMESPACE_DELIM 244 + getNameAsString(); 245 } 246 return getNameAsString(); 247 } 248 249 public byte[] getQualifier() { 250 return qualifier; 251 } 252 253 public String getQualifierAsString() { 254 return qualifierAsString; 255 } 256 257 /** Returns A pointer to TableName as String bytes. */ 258 public byte[] toBytes() { 259 return name; 260 } 261 262 public boolean isSystemTable() { 263 return systemTable; 264 } 265 266 @Override 267 public String toString() { 268 return nameAsString; 269 } 270 271 /** 272 * @throws IllegalArgumentException See {@link #valueOf(byte[])} 273 */ 274 private TableName(ByteBuffer namespace, ByteBuffer qualifier) throws IllegalArgumentException { 275 this.qualifier = new byte[qualifier.remaining()]; 276 qualifier.duplicate().get(this.qualifier); 277 this.qualifierAsString = Bytes.toString(this.qualifier); 278 279 if (qualifierAsString.equals(OLD_ROOT_STR)) { 280 throw new IllegalArgumentException(OLD_ROOT_STR + " has been deprecated."); 281 } 282 if (qualifierAsString.equals(OLD_META_STR)) { 283 throw new IllegalArgumentException( 284 OLD_META_STR + " no longer exists. The table has been " + "renamed to " + META_TABLE_NAME); 285 } 286 287 if (Bytes.equals(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME, namespace)) { 288 // Using the same objects: this will make the comparison faster later 289 this.namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME; 290 this.namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR; 291 this.systemTable = false; 292 293 // The name does not include the namespace when it's the default one. 294 this.nameAsString = qualifierAsString; 295 this.name = this.qualifier; 296 } else { 297 if (Bytes.equals(NamespaceDescriptor.SYSTEM_NAMESPACE_NAME, namespace)) { 298 this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME; 299 this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR; 300 this.systemTable = true; 301 } else { 302 this.namespace = new byte[namespace.remaining()]; 303 namespace.duplicate().get(this.namespace); 304 this.namespaceAsString = Bytes.toString(this.namespace); 305 this.systemTable = false; 306 } 307 this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString; 308 this.name = Bytes.toBytes(nameAsString); 309 } 310 311 this.hashCode = nameAsString.hashCode(); 312 313 isLegalNamespaceName(this.namespace); 314 isLegalTableQualifierName(this.qualifier); 315 } 316 317 /** 318 * This is only for the old and meta tables. 319 */ 320 private TableName(String qualifier) { 321 this.qualifier = Bytes.toBytes(qualifier); 322 this.qualifierAsString = qualifier; 323 324 this.namespace = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME; 325 this.namespaceAsString = NamespaceDescriptor.SYSTEM_NAMESPACE_NAME_STR; 326 this.systemTable = true; 327 328 // WARNING: nameAsString is different than name for old meta & root! 329 // This is by design. 330 this.nameAsString = namespaceAsString + NAMESPACE_DELIM + qualifierAsString; 331 this.name = this.qualifier; 332 333 this.hashCode = nameAsString.hashCode(); 334 } 335 336 /** 337 * Check that the object does not exist already. There are two reasons for creating the objects 338 * only once: 1) With 100K regions, the table names take ~20MB. 2) Equals becomes much faster as 339 * it's resolved with a reference and an int comparison. 340 */ 341 private static TableName createTableNameIfNecessary(ByteBuffer bns, ByteBuffer qns) { 342 for (TableName tn : tableCache) { 343 if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) { 344 return tn; 345 } 346 } 347 348 TableName newTable = new TableName(bns, qns); 349 if (tableCache.add(newTable)) { // Adds the specified element if it is not already present 350 return newTable; 351 } 352 353 // Someone else added it. Let's find it. 354 for (TableName tn : tableCache) { 355 if (Bytes.equals(tn.getQualifier(), qns) && Bytes.equals(tn.getNamespace(), bns)) { 356 return tn; 357 } 358 } 359 // this should never happen. 360 throw new IllegalStateException(newTable + " was supposed to be in the cache"); 361 } 362 363 /** 364 * It is used to create table names for old META, and ROOT table. These tables are not really 365 * legal tables. They are not added into the cache. 366 * @return a dummy TableName instance (with no validation) for the passed qualifier 367 */ 368 private static TableName getADummyTableName(String qualifier) { 369 return new TableName(qualifier); 370 } 371 372 public static TableName valueOf(String namespaceAsString, String qualifierAsString) { 373 if (namespaceAsString == null || namespaceAsString.length() < 1) { 374 namespaceAsString = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR; 375 } 376 377 for (TableName tn : tableCache) { 378 if ( 379 qualifierAsString.equals(tn.getQualifierAsString()) 380 && namespaceAsString.equals(tn.getNamespaceAsString()) 381 ) { 382 return tn; 383 } 384 } 385 386 return createTableNameIfNecessary(ByteBuffer.wrap(Bytes.toBytes(namespaceAsString)), 387 ByteBuffer.wrap(Bytes.toBytes(qualifierAsString))); 388 } 389 390 /** 391 * @throws IllegalArgumentException if fullName equals old root or old meta. Some code depends on 392 * this. The test is buried in the table creation to save on 393 * array comparison when we're creating a standard table object 394 * that will be in the cache. 395 */ 396 public static TableName valueOf(byte[] fullName) throws IllegalArgumentException { 397 for (TableName tn : tableCache) { 398 if (Arrays.equals(tn.getName(), fullName)) { 399 return tn; 400 } 401 } 402 403 int namespaceDelimIndex = org.apache.hbase.thirdparty.com.google.common.primitives.Bytes 404 .lastIndexOf(fullName, (byte) NAMESPACE_DELIM); 405 406 if (namespaceDelimIndex < 0) { 407 return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), 408 ByteBuffer.wrap(fullName)); 409 } else { 410 return createTableNameIfNecessary(ByteBuffer.wrap(fullName, 0, namespaceDelimIndex), 411 ByteBuffer.wrap(fullName, namespaceDelimIndex + 1, 412 fullName.length - (namespaceDelimIndex + 1))); 413 } 414 } 415 416 /** 417 * @throws IllegalArgumentException if fullName equals old root or old meta. Some code depends on 418 * this. 419 */ 420 public static TableName valueOf(String name) { 421 for (TableName tn : tableCache) { 422 if (name.equals(tn.getNameAsString())) { 423 return tn; 424 } 425 } 426 427 final int namespaceDelimIndex = name.indexOf(NAMESPACE_DELIM); 428 429 if (namespaceDelimIndex < 0) { 430 return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), 431 ByteBuffer.wrap(Bytes.toBytes(name))); 432 } else { 433 // indexOf is by character, not byte (consider multi-byte characters) 434 String ns = name.substring(0, namespaceDelimIndex); 435 String qualifier = name.substring(namespaceDelimIndex + 1); 436 return createTableNameIfNecessary(ByteBuffer.wrap(Bytes.toBytes(ns)), 437 ByteBuffer.wrap(Bytes.toBytes(qualifier))); 438 } 439 } 440 441 public static TableName valueOf(byte[] namespace, byte[] qualifier) { 442 if (namespace == null || namespace.length < 1) { 443 namespace = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME; 444 } 445 446 for (TableName tn : tableCache) { 447 if ( 448 Arrays.equals(tn.getQualifier(), qualifier) && Arrays.equals(tn.getNamespace(), namespace) 449 ) { 450 return tn; 451 } 452 } 453 454 return createTableNameIfNecessary(ByteBuffer.wrap(namespace), ByteBuffer.wrap(qualifier)); 455 } 456 457 public static TableName valueOf(ByteBuffer namespace, ByteBuffer qualifier) { 458 if (namespace == null || namespace.remaining() < 1) { 459 return createTableNameIfNecessary(ByteBuffer.wrap(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME), 460 qualifier); 461 } 462 463 return createTableNameIfNecessary(namespace, qualifier); 464 } 465 466 @Override 467 public boolean equals(Object o) { 468 if (this == o) return true; 469 if (o == null || getClass() != o.getClass()) return false; 470 471 TableName tableName = (TableName) o; 472 473 return o.hashCode() == hashCode && nameAsString.equals(tableName.nameAsString); 474 } 475 476 @Override 477 public int hashCode() { 478 return hashCode; 479 } 480 481 /** 482 * For performance reasons, the ordering is not lexicographic. 483 */ 484 @Override 485 public int compareTo(TableName tableName) { 486 if (this == tableName) return 0; 487 if (this.hashCode < tableName.hashCode()) { 488 return -1; 489 } 490 if (this.hashCode > tableName.hashCode()) { 491 return 1; 492 } 493 return this.nameAsString.compareTo(tableName.getNameAsString()); 494 } 495 496}