HBASE-649 API polluted with default and protected access data members and methods
HBASE-650 Add String versions of get, scanner, put in HTable HBASE-656 Do not retry exceptions such as unknown scanner or illegal argument A src/java/org/apache/hadoop/hbase/ColumnNameParseException.java A src/java/org/apache/hadoop/hbase/LeaseException.java Added. Thrown instead of IllegalArgumentExceptions M src/java/org/apache/hadoop/hbase/Leases.java Use new LeaseException in place of IllegalArgument M src/java/org/apache/hadoop/hbase/HStoreKey.java Use new ColumnNameParse in place of IllegalArgument M src/java/org/apache/hadoop/hbase/master/ServerManager.java Log at debug if LeaseException (Not important if it happens). A src/java/org/apache/hadoop/hbase/DoNotRetryIOException.java An IOE that shouldn't be retried. M src/java/org/apache/hadoop/hbase/InvalidColumnNameException.java M src/java/org/apache/hadoop/hbase/UnknownScannerException.java Inherit from DoNotRetryIOException else we keep trying. M src/java/org/apache/hadoop/hbase/util/Bytes.java (toByteArrays): Added one to handle [] String. M src/java/org/apache/hadoop/hbase/client/HTable.java Make String overrides of all methods. Made data members private (turns out a bunch arent' even used). Stopped it inheriting from HConstants so we don't have big dump of all HConstants as first thing in javadoc. M src/java/org/apache/hadoop/hbase/client/HConnectionManager.java If instance of DoNotRetryIOException, let the exception out. M src/java/org/apache/hadoop/hbase/client/HBaseAdmin.java Make String overrides of all methods. Stopped it inheriting from HConstants so we don't have big dump of all HConstants as first thing in javadoc. git-svn-id: https://svn.apache.org/repos/asf/hadoop/hbase/trunk@661541 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
f8c4649209
commit
f328681e00
|
@ -29,8 +29,11 @@ Hbase Change Log
|
|||
HBASE-646 EOFException opening HStoreFile info file (spin on HBASE-645and 550)
|
||||
HBASE-648 If mapfile index is empty, run repair
|
||||
HBASE-640 TestMigrate failing on hudson
|
||||
HASE-651 Table.commit should throw NoSuchColumnFamilyException if column
|
||||
HBASE-651 Table.commit should throw NoSuchColumnFamilyException if column
|
||||
family doesn't exist
|
||||
HBASE-649 API polluted with default and protected access data members and methods
|
||||
HBASE-650 Add String versions of get, scanner, put in HTable
|
||||
HBASE-656 Do not retry exceptions such as unknown scanner or illegal argument
|
||||
|
||||
IMPROVEMENTS
|
||||
HBASE-559 MR example job to count table rows
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright 2008 The Apache Software Foundation
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.hadoop.hbase;
|
||||
|
||||
/**
|
||||
* Thrown if issue with passed column name.
|
||||
*/
|
||||
public class ColumnNameParseException extends DoNotRetryIOException {
|
||||
public ColumnNameParseException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ColumnNameParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright 2008 The Apache Software Foundation
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.hadoop.hbase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Subclass if exception is not meant to be retried: e.g.
|
||||
* {@link UnknownScannerException}
|
||||
*/
|
||||
public class DoNotRetryIOException extends IOException {
|
||||
public DoNotRetryIOException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public DoNotRetryIOException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -300,12 +300,14 @@ public class HStoreKey implements WritableComparable {
|
|||
/**
|
||||
* @param column
|
||||
* @return New byte array that holds <code>column</code> family prefix.
|
||||
* @throws ColumnNameParseException
|
||||
* @see #parseColumn(byte[])
|
||||
*/
|
||||
public static byte [] getFamily(final byte [] column) {
|
||||
public static byte [] getFamily(final byte [] column)
|
||||
throws ColumnNameParseException {
|
||||
int index = getFamilyDelimiterIndex(column);
|
||||
if (index <= 0) {
|
||||
throw new IllegalArgumentException("No ':' delimiter between " +
|
||||
throw new ColumnNameParseException("No ':' delimiter between " +
|
||||
"column family and qualifier in the passed column name <" +
|
||||
Bytes.toString(column) + ">");
|
||||
}
|
||||
|
@ -370,12 +372,14 @@ public class HStoreKey implements WritableComparable {
|
|||
* @return Return array of size two whose first element has the family
|
||||
* prefix of passed column <code>c</code> and whose second element is the
|
||||
* column qualifier.
|
||||
* @throws ColumnNameParseException
|
||||
*/
|
||||
public static byte [][] parseColumn(final byte [] c) {
|
||||
public static byte [][] parseColumn(final byte [] c)
|
||||
throws ColumnNameParseException {
|
||||
byte [][] result = new byte [2][];
|
||||
int index = getFamilyDelimiterIndex(c);
|
||||
if (index == -1) {
|
||||
throw new IllegalArgumentException("Impossible column name: " + c);
|
||||
throw new ColumnNameParseException("Impossible column name: " + c);
|
||||
}
|
||||
result[0] = new byte [index];
|
||||
System.arraycopy(c, 0, result[0], 0, index);
|
||||
|
|
|
@ -19,12 +19,11 @@
|
|||
*/
|
||||
package org.apache.hadoop.hbase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Thrown when an invalid column name is encountered
|
||||
*/
|
||||
public class InvalidColumnNameException extends IOException {
|
||||
public class InvalidColumnNameException extends DoNotRetryIOException {
|
||||
private static final long serialVersionUID = 1L << 29 - 1L;
|
||||
/** default constructor */
|
||||
public InvalidColumnNameException() {
|
||||
|
@ -38,4 +37,4 @@ public class InvalidColumnNameException extends IOException {
|
|||
public InvalidColumnNameException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Copyright 2008 The Apache Software Foundation
|
||||
*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.hadoop.hbase;
|
||||
|
||||
public class LeaseException extends DoNotRetryIOException {
|
||||
public LeaseException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public LeaseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -165,12 +165,13 @@ public class Leases extends Thread {
|
|||
* Renew a lease
|
||||
*
|
||||
* @param leaseName name of lease
|
||||
* @throws LeaseException
|
||||
*/
|
||||
public void renewLease(final String leaseName) {
|
||||
public void renewLease(final String leaseName) throws LeaseException {
|
||||
synchronized (leaseQueue) {
|
||||
Lease lease = leases.get(leaseName);
|
||||
if (lease == null) {
|
||||
throw new IllegalArgumentException("lease '" + leaseName +
|
||||
throw new LeaseException("lease '" + leaseName +
|
||||
"' does not exist");
|
||||
}
|
||||
leaseQueue.remove(lease);
|
||||
|
@ -183,13 +184,13 @@ public class Leases extends Thread {
|
|||
* Client explicitly cancels a lease.
|
||||
*
|
||||
* @param leaseName name of lease
|
||||
* @throws LeaseException
|
||||
*/
|
||||
public void cancelLease(final String leaseName) {
|
||||
public void cancelLease(final String leaseName) throws LeaseException {
|
||||
synchronized (leaseQueue) {
|
||||
Lease lease = leases.remove(leaseName);
|
||||
if (lease == null) {
|
||||
throw new IllegalArgumentException("lease '" + leaseName +
|
||||
"' does not exist");
|
||||
throw new LeaseException("lease '" + leaseName + "' does not exist");
|
||||
}
|
||||
leaseQueue.remove(lease);
|
||||
}
|
||||
|
|
|
@ -19,12 +19,11 @@
|
|||
*/
|
||||
package org.apache.hadoop.hbase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Thrown if a region server is passed an unknown scanner id
|
||||
*/
|
||||
public class UnknownScannerException extends IOException {
|
||||
public class UnknownScannerException extends DoNotRetryIOException {
|
||||
private static final long serialVersionUID = 993179627856392526L;
|
||||
|
||||
/** constructor */
|
||||
|
|
|
@ -47,9 +47,8 @@ import org.apache.hadoop.ipc.RemoteException;
|
|||
/**
|
||||
* Provides administrative functions for HBase
|
||||
*/
|
||||
public class HBaseAdmin implements HConstants {
|
||||
protected final Log LOG = LogFactory.getLog(this.getClass().getName());
|
||||
|
||||
public class HBaseAdmin {
|
||||
private final Log LOG = LogFactory.getLog(this.getClass().getName());
|
||||
private final HConnection connection;
|
||||
private final long pause;
|
||||
private final int numRetries;
|
||||
|
@ -86,9 +85,9 @@ public class HBaseAdmin implements HConstants {
|
|||
* @return True if table exists already.
|
||||
* @throws MasterNotRunningException
|
||||
*/
|
||||
public boolean tableExists(final String tableName)
|
||||
public boolean tableExists(final Text tableName)
|
||||
throws MasterNotRunningException {
|
||||
return tableExists(Bytes.toBytes(tableName));
|
||||
return tableExists(tableName.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -96,11 +95,11 @@ public class HBaseAdmin implements HConstants {
|
|||
* @return True if table exists already.
|
||||
* @throws MasterNotRunningException
|
||||
*/
|
||||
public boolean tableExists(final Text tableName)
|
||||
public boolean tableExists(final String tableName)
|
||||
throws MasterNotRunningException {
|
||||
return tableExists(tableName.getBytes());
|
||||
return tableExists(Bytes.toBytes(tableName));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param tableName Table to check.
|
||||
* @return True if table exists already.
|
||||
|
@ -147,7 +146,7 @@ public class HBaseAdmin implements HConstants {
|
|||
for (int tries = 0; tries < numRetries; tries++) {
|
||||
try {
|
||||
// Wait for new table to come on-line
|
||||
connection.locateRegion(desc.getName(), EMPTY_START_ROW);
|
||||
connection.locateRegion(desc.getName(), HConstants.EMPTY_START_ROW);
|
||||
break;
|
||||
|
||||
} catch (TableNotFoundException e) {
|
||||
|
@ -199,6 +198,16 @@ public class HBaseAdmin implements HConstants {
|
|||
deleteTable(tableName.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a table
|
||||
*
|
||||
* @param tableName name of table to delete
|
||||
* @throws IOException
|
||||
*/
|
||||
public void deleteTable(final String tableName) throws IOException {
|
||||
deleteTable(Bytes.toBytes(tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a table
|
||||
*
|
||||
|
@ -226,14 +235,15 @@ public class HBaseAdmin implements HConstants {
|
|||
try {
|
||||
scannerId =
|
||||
server.openScanner(firstMetaServer.getRegionInfo().getRegionName(),
|
||||
COL_REGIONINFO_ARRAY, tableName, HConstants.LATEST_TIMESTAMP, null);
|
||||
HConstants.COL_REGIONINFO_ARRAY, tableName,
|
||||
HConstants.LATEST_TIMESTAMP, null);
|
||||
RowResult values = server.next(scannerId);
|
||||
if (values == null || values.size() == 0) {
|
||||
break;
|
||||
}
|
||||
boolean found = false;
|
||||
for (Map.Entry<byte [], Cell> e: values.entrySet()) {
|
||||
if (Bytes.equals(e.getKey(), COL_REGIONINFO)) {
|
||||
if (Bytes.equals(e.getKey(), HConstants.COL_REGIONINFO)) {
|
||||
info = (HRegionInfo) Writables.getWritable(
|
||||
e.getValue().getValue(), info);
|
||||
|
||||
|
@ -282,7 +292,17 @@ public class HBaseAdmin implements HConstants {
|
|||
public void enableTable(final Text tableName) throws IOException {
|
||||
enableTable(tableName.getBytes());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Brings a table on-line (enables it)
|
||||
*
|
||||
* @param tableName name of the table
|
||||
* @throws IOException
|
||||
*/
|
||||
public void enableTable(final String tableName) throws IOException {
|
||||
enableTable(Bytes.toBytes(tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Brings a table on-line (enables it)
|
||||
*
|
||||
|
@ -315,7 +335,8 @@ public class HBaseAdmin implements HConstants {
|
|||
try {
|
||||
scannerId =
|
||||
server.openScanner(firstMetaServer.getRegionInfo().getRegionName(),
|
||||
COL_REGIONINFO_ARRAY, tableName, HConstants.LATEST_TIMESTAMP, null);
|
||||
HConstants.COL_REGIONINFO_ARRAY, tableName,
|
||||
HConstants.LATEST_TIMESTAMP, null);
|
||||
boolean isenabled = false;
|
||||
|
||||
while (true) {
|
||||
|
@ -329,7 +350,7 @@ public class HBaseAdmin implements HConstants {
|
|||
}
|
||||
valuesfound += 1;
|
||||
for (Map.Entry<byte [], Cell> e: values.entrySet()) {
|
||||
if (Bytes.equals(e.getKey(), COL_REGIONINFO)) {
|
||||
if (Bytes.equals(e.getKey(), HConstants.COL_REGIONINFO)) {
|
||||
info = (HRegionInfo) Writables.getWritable(
|
||||
e.getValue().getValue(), info);
|
||||
|
||||
|
@ -391,7 +412,18 @@ public class HBaseAdmin implements HConstants {
|
|||
public void disableTable(final Text tableName) throws IOException {
|
||||
disableTable(tableName.getBytes());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disables a table (takes it off-line) If it is being served, the master
|
||||
* will tell the servers to stop serving it.
|
||||
*
|
||||
* @param tableName name of table
|
||||
* @throws IOException
|
||||
*/
|
||||
public void disableTable(final String tableName) throws IOException {
|
||||
disableTable(Bytes.toBytes(tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables a table (takes it off-line) If it is being served, the master
|
||||
* will tell the servers to stop serving it.
|
||||
|
@ -423,7 +455,8 @@ public class HBaseAdmin implements HConstants {
|
|||
try {
|
||||
scannerId =
|
||||
server.openScanner(firstMetaServer.getRegionInfo().getRegionName(),
|
||||
COL_REGIONINFO_ARRAY, tableName, HConstants.LATEST_TIMESTAMP, null);
|
||||
HConstants.COL_REGIONINFO_ARRAY, tableName,
|
||||
HConstants.LATEST_TIMESTAMP, null);
|
||||
boolean disabled = false;
|
||||
while (true) {
|
||||
RowResult values = server.next(scannerId);
|
||||
|
@ -435,7 +468,7 @@ public class HBaseAdmin implements HConstants {
|
|||
}
|
||||
valuesfound += 1;
|
||||
for (Map.Entry<byte [], Cell> e: values.entrySet()) {
|
||||
if (Bytes.equals(e.getKey(), COL_REGIONINFO)) {
|
||||
if (Bytes.equals(e.getKey(), HConstants.COL_REGIONINFO)) {
|
||||
info = (HRegionInfo) Writables.getWritable(
|
||||
e.getValue().getValue(), info);
|
||||
|
||||
|
@ -497,7 +530,19 @@ public class HBaseAdmin implements HConstants {
|
|||
throws IOException {
|
||||
addColumn(tableName.getBytes(), column);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a column to an existing table
|
||||
*
|
||||
* @param tableName name of the table to add column to
|
||||
* @param column column descriptor of column to be added
|
||||
* @throws IOException
|
||||
*/
|
||||
public void addColumn(final String tableName, HColumnDescriptor column)
|
||||
throws IOException {
|
||||
addColumn(Bytes.toBytes(tableName), column);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a column to an existing table
|
||||
*
|
||||
|
@ -529,7 +574,19 @@ public class HBaseAdmin implements HConstants {
|
|||
throws IOException {
|
||||
deleteColumn(tableName.getBytes(), columnName.getBytes());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Delete a column from a table
|
||||
*
|
||||
* @param tableName name of table
|
||||
* @param columnName name of column to be deleted
|
||||
* @throws IOException
|
||||
*/
|
||||
public void deleteColumn(final String tableName, final String columnName)
|
||||
throws IOException {
|
||||
deleteColumn(Bytes.toBytes(tableName), Bytes.toBytes(columnName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a column from a table
|
||||
*
|
||||
|
@ -563,7 +620,22 @@ public class HBaseAdmin implements HConstants {
|
|||
throws IOException {
|
||||
modifyColumn(tableName.getBytes(), columnName.getBytes(), descriptor);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modify an existing column family on a table
|
||||
*
|
||||
* @param tableName name of table
|
||||
* @param columnName name of column to be modified
|
||||
* @param descriptor new column descriptor to use
|
||||
* @throws IOException
|
||||
*/
|
||||
public void modifyColumn(final String tableName, final String columnName,
|
||||
HColumnDescriptor descriptor)
|
||||
throws IOException {
|
||||
modifyColumn(Bytes.toBytes(tableName), Bytes.toBytes(columnName),
|
||||
descriptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify an existing column family on a table
|
||||
*
|
||||
|
@ -606,8 +678,8 @@ public class HBaseAdmin implements HConstants {
|
|||
|
||||
private HRegionLocation getFirstMetaServerForTable(final byte [] tableName)
|
||||
throws IOException {
|
||||
return connection.locateRegion(META_TABLE_NAME,
|
||||
HRegionInfo.createRegionName(tableName, null, NINES));
|
||||
return connection.locateRegion(HConstants.META_TABLE_NAME,
|
||||
HRegionInfo.createRegionName(tableName, null, HConstants.NINES));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -622,4 +694,4 @@ public class HBaseAdmin implements HConstants {
|
|||
copyOfConf.setInt("hbase.client.retries.number", 1);
|
||||
new HBaseAdmin(copyOfConf);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.hadoop.hbase.DoNotRetryIOException;
|
||||
import org.apache.hadoop.hbase.HBaseConfiguration;
|
||||
import org.apache.hadoop.hbase.HConstants;
|
||||
import org.apache.hadoop.hbase.HRegionInfo;
|
||||
|
@ -732,6 +733,9 @@ public class HConnectionManager implements HConstants {
|
|||
if (t instanceof RemoteException) {
|
||||
t = RemoteExceptionHandler.decodeRemoteException((RemoteException) t);
|
||||
}
|
||||
if (t instanceof DoNotRetryIOException) {
|
||||
throw (DoNotRetryIOException)t;
|
||||
}
|
||||
exceptions.add(t);
|
||||
if (tries == numRetries - 1) {
|
||||
throw new RetriesExhaustedException(callable.getServerName(),
|
||||
|
|
|
@ -25,7 +25,6 @@ import java.util.HashMap;
|
|||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
@ -48,20 +47,45 @@ import org.apache.hadoop.io.Text;
|
|||
/**
|
||||
* Used to communicate with a single HBase table
|
||||
*/
|
||||
public class HTable implements HConstants {
|
||||
protected final Log LOG = LogFactory.getLog(this.getClass());
|
||||
public class HTable {
|
||||
private final Log LOG = LogFactory.getLog(this.getClass());
|
||||
private final HConnection connection;
|
||||
private final byte [] tableName;
|
||||
private HBaseConfiguration configuration;
|
||||
|
||||
protected final HConnection connection;
|
||||
protected final byte [] tableName;
|
||||
protected final long pause;
|
||||
protected final int numRetries;
|
||||
protected Random rand;
|
||||
protected HBaseConfiguration configuration;
|
||||
/**
|
||||
* Creates an object to access a HBase table
|
||||
*
|
||||
* @param tableName name of the table
|
||||
* @throws IOException
|
||||
*/
|
||||
public HTable(final Text tableName)
|
||||
throws IOException {
|
||||
this(new HBaseConfiguration(), tableName.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object to access a HBase table
|
||||
*
|
||||
* @param tableName name of the table
|
||||
* @throws IOException
|
||||
*/
|
||||
public HTable(final String tableName)
|
||||
throws IOException {
|
||||
this(new HBaseConfiguration(), Bytes.toBytes(tableName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an object to access a HBase table
|
||||
*
|
||||
* @param tableName name of the table
|
||||
* @throws IOException
|
||||
*/
|
||||
public HTable(final byte [] tableName)
|
||||
throws IOException {
|
||||
this(new HBaseConfiguration(), tableName);
|
||||
}
|
||||
|
||||
protected volatile boolean tableDoesNotExist;
|
||||
|
||||
// For row mutation operations
|
||||
|
||||
/**
|
||||
* Creates an object to access a HBase table
|
||||
*
|
||||
|
@ -73,7 +97,7 @@ public class HTable implements HConstants {
|
|||
throws IOException {
|
||||
this(conf, tableName.getBytes());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates an object to access a HBase table
|
||||
*
|
||||
|
@ -98,10 +122,29 @@ public class HTable implements HConstants {
|
|||
this.connection = HConnectionManager.getConnection(conf);
|
||||
this.configuration = conf;
|
||||
this.tableName = tableName;
|
||||
this.pause = conf.getLong("hbase.client.pause", 10 * 1000);
|
||||
this.numRetries = conf.getInt("hbase.client.retries.number", 5);
|
||||
this.rand = new Random();
|
||||
this.connection.locateRegion(tableName, EMPTY_START_ROW);
|
||||
this.connection.locateRegion(tableName, HConstants.EMPTY_START_ROW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find region location hosting passed row using cached info
|
||||
* @param row Row to find.
|
||||
* @return Location of row.
|
||||
* @throws IOException
|
||||
*/
|
||||
public HRegionLocation getRegionLocation(final Text row)
|
||||
throws IOException {
|
||||
return connection.getRegionLocation(tableName, row.getBytes(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find region location hosting passed row using cached info
|
||||
* @param row Row to find.
|
||||
* @return Location of row.
|
||||
* @throws IOException
|
||||
*/
|
||||
public HRegionLocation getRegionLocation(final String row)
|
||||
throws IOException {
|
||||
return connection.getRegionLocation(tableName, Bytes.toBytes(row), false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,17 +158,17 @@ public class HTable implements HConstants {
|
|||
return connection.getRegionLocation(tableName, row, false);
|
||||
}
|
||||
|
||||
/** @return the connection */
|
||||
public HConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
/** @return the table name */
|
||||
public byte [] getTableName() {
|
||||
return this.tableName;
|
||||
}
|
||||
|
||||
protected HConnection getConnection() {
|
||||
return this.connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Make the return read-only.
|
||||
* @return table metadata
|
||||
* @throws IOException
|
||||
*/
|
||||
|
@ -152,10 +195,12 @@ public class HTable implements HConstants {
|
|||
final List<byte[]> keyList = new ArrayList<byte[]>();
|
||||
|
||||
MetaScannerVisitor visitor = new MetaScannerVisitor() {
|
||||
public boolean processRow(RowResult rowResult,
|
||||
HRegionLocation metaLocation, HRegionInfo info)
|
||||
@SuppressWarnings("unused")
|
||||
public boolean processRow(@SuppressWarnings("unused") RowResult rowResult,
|
||||
@SuppressWarnings("unused") HRegionLocation metaLocation,
|
||||
HRegionInfo info)
|
||||
throws IOException {
|
||||
if (!(Bytes.equals(info.getTableDesc().getName(), tableName))) {
|
||||
if (!(Bytes.equals(info.getTableDesc().getName(), getTableName()))) {
|
||||
return false;
|
||||
}
|
||||
if (!(info.isOffline() || info.isSplit())) {
|
||||
|
@ -181,10 +226,11 @@ public class HTable implements HConstants {
|
|||
new HashMap<HRegionInfo, HServerAddress>();
|
||||
|
||||
MetaScannerVisitor visitor = new MetaScannerVisitor() {
|
||||
public boolean processRow(RowResult rowResult,
|
||||
@SuppressWarnings("unused")
|
||||
public boolean processRow(@SuppressWarnings("unused") RowResult rowResult,
|
||||
HRegionLocation metaLocation, HRegionInfo info)
|
||||
throws IOException {
|
||||
if (!(Bytes.equals(info.getTableDesc().getName(), tableName))) {
|
||||
if (!(Bytes.equals(info.getTableDesc().getName(), getTableName()))) {
|
||||
return false;
|
||||
}
|
||||
if (!(info.isOffline() || info.isSplit())) {
|
||||
|
@ -224,7 +270,34 @@ public class HTable implements HConstants {
|
|||
throws IOException {
|
||||
return get(row.getBytes(), column.getBytes(), numVersions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a single value for the specified row and column
|
||||
*
|
||||
* @param row row key
|
||||
* @param column column name
|
||||
* @return value for specified row/column
|
||||
* @throws IOException
|
||||
*/
|
||||
public Cell get(final String row, final String column)
|
||||
throws IOException {
|
||||
return get(Bytes.toBytes(row), Bytes.toBytes(column));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single value for the specified row and column
|
||||
*
|
||||
* @param row row key
|
||||
* @param column column name
|
||||
* @param numVersions - number of versions to retrieve
|
||||
* @return value for specified row/column
|
||||
* @throws IOException
|
||||
*/
|
||||
public Cell[] get(final String row, final String column, int numVersions)
|
||||
throws IOException {
|
||||
return get(Bytes.toBytes(row), Bytes.toBytes(column), numVersions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single value for the specified row and column
|
||||
*
|
||||
|
@ -293,7 +366,24 @@ public class HTable implements HConstants {
|
|||
throws IOException {
|
||||
return get(row.getBytes(), column.getBytes(), timestamp, numVersions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the specified number of versions of the specified row and column with
|
||||
* the specified timestamp.
|
||||
*
|
||||
* @param row - row key
|
||||
* @param column - column name
|
||||
* @param timestamp - timestamp
|
||||
* @param numVersions - number of versions to retrieve
|
||||
* @return - array of values that match the above criteria
|
||||
* @throws IOException
|
||||
*/
|
||||
public Cell[] get(final String row, final String column,
|
||||
final long timestamp, final int numVersions)
|
||||
throws IOException {
|
||||
return get(Bytes.toBytes(row), Bytes.toBytes(column), timestamp, numVersions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the specified number of versions of the specified row and column with
|
||||
* the specified timestamp.
|
||||
|
@ -339,6 +429,17 @@ public class HTable implements HConstants {
|
|||
return getRow(row.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the data for the specified row at the latest timestamp
|
||||
*
|
||||
* @param row row key
|
||||
* @return RowResult is empty if row does not exist.
|
||||
* @throws IOException
|
||||
*/
|
||||
public RowResult getRow(final String row) throws IOException {
|
||||
return getRow(Bytes.toBytes(row));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the data for the specified row at the latest timestamp
|
||||
*
|
||||
|
@ -363,6 +464,19 @@ public class HTable implements HConstants {
|
|||
return getRow(row.getBytes(), ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the data for the specified row at a specified timestamp
|
||||
*
|
||||
* @param row row key
|
||||
* @param ts timestamp
|
||||
* @return RowResult is empty if row does not exist.
|
||||
* @throws IOException
|
||||
*/
|
||||
public RowResult getRow(final String row, final long ts)
|
||||
throws IOException {
|
||||
return getRow(Bytes.toBytes(row), ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the data for the specified row at a specified timestamp
|
||||
*
|
||||
|
@ -382,6 +496,7 @@ public class HTable implements HConstants {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selected columns for the specified row at the latest timestamp
|
||||
*
|
||||
|
@ -394,7 +509,20 @@ public class HTable implements HConstants {
|
|||
throws IOException {
|
||||
return getRow(row.getBytes(), Bytes.toByteArrays(columns));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get selected columns for the specified row at the latest timestamp
|
||||
*
|
||||
* @param row row key
|
||||
* @param columns Array of column names you want to retrieve.
|
||||
* @return RowResult is empty if row does not exist.
|
||||
* @throws IOException
|
||||
*/
|
||||
public RowResult getRow(final String row, final String [] columns)
|
||||
throws IOException {
|
||||
return getRow(Bytes.toBytes(row), Bytes.toByteArrays(columns));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selected columns for the specified row at the latest timestamp
|
||||
*
|
||||
|
@ -417,12 +545,27 @@ public class HTable implements HConstants {
|
|||
* @return RowResult is empty if row does not exist.
|
||||
* @throws IOException
|
||||
*/
|
||||
public RowResult getRow(final Text row, final Text[] columns,
|
||||
public RowResult getRow(final Text row, final Text [] columns,
|
||||
final long ts)
|
||||
throws IOException {
|
||||
return getRow(row.getBytes(), Bytes.toByteArrays(columns), ts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get selected columns for the specified row at a specified timestamp
|
||||
*
|
||||
* @param row row key
|
||||
* @param columns Array of column names you want to retrieve.
|
||||
* @param ts timestamp
|
||||
* @return RowResult is empty if row does not exist.
|
||||
* @throws IOException
|
||||
*/
|
||||
public RowResult getRow(final String row, final String [] columns,
|
||||
final long ts)
|
||||
throws IOException {
|
||||
return getRow(Bytes.toBytes(row), Bytes.toByteArrays(columns), ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get selected columns for the specified row at a specified timestamp
|
||||
*
|
||||
|
@ -462,6 +605,23 @@ public class HTable implements HConstants {
|
|||
return getScanner(Bytes.toByteArrays(columns), HConstants.EMPTY_START_ROW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scanner on the current table starting at first row.
|
||||
* Return the specified columns.
|
||||
*
|
||||
* @param columns columns to scan. If column name is a column family, all
|
||||
* columns of the specified column family are returned. Its also possible
|
||||
* to pass a regex in the column qualifier. A column qualifier is judged to
|
||||
* be a regex if it contains at least one of the following characters:
|
||||
* <code>\+|^&*$[]]}{)(</code>.
|
||||
* @return scanner
|
||||
* @throws IOException
|
||||
*/
|
||||
public Scanner getScanner(final String [] columns)
|
||||
throws IOException {
|
||||
return getScanner(Bytes.toByteArrays(columns), HConstants.EMPTY_START_ROW);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scanner on the current table starting at the specified row.
|
||||
* Return the specified columns.
|
||||
|
@ -480,6 +640,24 @@ public class HTable implements HConstants {
|
|||
return getScanner(Bytes.toByteArrays(columns), startRow.getBytes());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scanner on the current table starting at the specified row.
|
||||
* Return the specified columns.
|
||||
*
|
||||
* @param columns columns to scan. If column name is a column family, all
|
||||
* columns of the specified column family are returned. Its also possible
|
||||
* to pass a regex in the column qualifier. A column qualifier is judged to
|
||||
* be a regex if it contains at least one of the following characters:
|
||||
* <code>\+|^&*$[]]}{)(</code>.
|
||||
* @param startRow starting row in table to scan
|
||||
* @return scanner
|
||||
* @throws IOException
|
||||
*/
|
||||
public Scanner getScanner(final String [] columns, final String startRow)
|
||||
throws IOException {
|
||||
return getScanner(Bytes.toByteArrays(columns), Bytes.toBytes(startRow));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scanner on the current table starting at first row.
|
||||
* Return the specified columns.
|
||||
|
@ -604,7 +782,32 @@ public class HTable implements HConstants {
|
|||
return getScanner(Bytes.toByteArrays(columns), startRow.getBytes(),
|
||||
stopRow.getBytes(), timestamp);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a scanner on the current table starting at the specified row and
|
||||
* ending just before <code>stopRow<code>.
|
||||
* Return the specified columns.
|
||||
*
|
||||
* @param columns columns to scan. If column name is a column family, all
|
||||
* columns of the specified column family are returned. Its also possible
|
||||
* to pass a regex in the column qualifier. A column qualifier is judged to
|
||||
* be a regex if it contains at least one of the following characters:
|
||||
* <code>\+|^&*$[]]}{)(</code>.
|
||||
* @param startRow starting row in table to scan
|
||||
* @param stopRow Row to stop scanning on. Once we hit this row we stop
|
||||
* returning values; i.e. we return the row before this one but not the
|
||||
* <code>stopRow</code> itself.
|
||||
* @param timestamp only return results whose timestamp <= this value
|
||||
* @return scanner
|
||||
* @throws IOException
|
||||
*/
|
||||
public Scanner getScanner(final String [] columns,
|
||||
final String startRow, final String stopRow, final long timestamp)
|
||||
throws IOException {
|
||||
return getScanner(Bytes.toByteArrays(columns), Bytes.toBytes(startRow),
|
||||
Bytes.toBytes(stopRow), timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scanner on the current table starting at the specified row and
|
||||
* ending just before <code>stopRow<code>.
|
||||
|
@ -651,7 +854,29 @@ public class HTable implements HConstants {
|
|||
return getScanner(Bytes.toByteArrays(columns), startRow.getBytes(),
|
||||
timestamp, filter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a scanner on the current table starting at the specified row.
|
||||
* Return the specified columns.
|
||||
*
|
||||
* @param columns columns to scan. If column name is a column family, all
|
||||
* columns of the specified column family are returned. Its also possible
|
||||
* to pass a regex in the column qualifier. A column qualifier is judged to
|
||||
* be a regex if it contains at least one of the following characters:
|
||||
* <code>\+|^&*$[]]}{)(</code>.
|
||||
* @param startRow starting row in table to scan
|
||||
* @param timestamp only return results whose timestamp <= this value
|
||||
* @param filter a row filter using row-key regexp and/or column data filter.
|
||||
* @return scanner
|
||||
* @throws IOException
|
||||
*/
|
||||
public Scanner getScanner(String[] columns,
|
||||
String startRow, long timestamp, RowFilterInterface filter)
|
||||
throws IOException {
|
||||
return getScanner(Bytes.toByteArrays(columns), Bytes.toBytes(startRow),
|
||||
timestamp, filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a scanner on the current table starting at the specified row.
|
||||
* Return the specified columns.
|
||||
|
@ -713,7 +938,18 @@ public class HTable implements HConstants {
|
|||
* @throws IOException
|
||||
*/
|
||||
public void deleteAll(final Text row, final Text column) throws IOException {
|
||||
deleteAll(row, column, LATEST_TIMESTAMP);
|
||||
deleteAll(row, column, HConstants.LATEST_TIMESTAMP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cells that match the passed row and column.
|
||||
* @param row Row to update
|
||||
* @param column name of column whose value is to be deleted
|
||||
* @throws IOException
|
||||
*/
|
||||
public void deleteAll(final String row, final String column)
|
||||
throws IOException {
|
||||
deleteAll(row, column, HConstants.LATEST_TIMESTAMP);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -729,6 +965,19 @@ public class HTable implements HConstants {
|
|||
deleteAll(row.getBytes(), column.getBytes(), ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cells that match the passed row and column and whose
|
||||
* timestamp is equal-to or older than the passed timestamp.
|
||||
* @param row Row to update
|
||||
* @param column name of column whose value is to be deleted
|
||||
* @param ts Delete all cells of the same timestamp or older.
|
||||
* @throws IOException
|
||||
*/
|
||||
public void deleteAll(final String row, final String column, final long ts)
|
||||
throws IOException {
|
||||
deleteAll(Bytes.toBytes(row), Bytes.toBytes(column), ts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cells that match the passed row and column and whose
|
||||
* timestamp is equal-to or older than the passed timestamp.
|
||||
|
@ -761,6 +1010,19 @@ public class HTable implements HConstants {
|
|||
deleteFamily(row.getBytes(), family.getBytes(),
|
||||
HConstants.LATEST_TIMESTAMP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cells for a row with matching column family at all timestamps.
|
||||
*
|
||||
* @param row The row to operate on
|
||||
* @param family The column family to match
|
||||
* @throws IOException
|
||||
*/
|
||||
public void deleteFamily(final String row, final String family)
|
||||
throws IOException{
|
||||
deleteFamily(Bytes.toBytes(row), Bytes.toBytes(family),
|
||||
HConstants.LATEST_TIMESTAMP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all cells for a row with matching column family with timestamps
|
||||
|
@ -809,6 +1071,7 @@ public class HTable implements HConstants {
|
|||
* through them all.
|
||||
*/
|
||||
private class ClientScanner implements Scanner {
|
||||
private final Log CLIENT_LOG = LogFactory.getLog(this.getClass());
|
||||
private byte[][] columns;
|
||||
private byte [] startRow;
|
||||
protected long scanTime;
|
||||
|
@ -828,8 +1091,8 @@ public class HTable implements HConstants {
|
|||
protected ClientScanner(final byte[][] columns, final byte [] startRow,
|
||||
final long timestamp, final RowFilterInterface filter)
|
||||
throws IOException {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Creating scanner over " + Bytes.toString(tableName) +
|
||||
if (CLIENT_LOG.isDebugEnabled()) {
|
||||
CLIENT_LOG.debug("Creating scanner over " + Bytes.toString(getTableName()) +
|
||||
" starting at key '" + Bytes.toString(startRow) + "'");
|
||||
}
|
||||
// save off the simple parameters
|
||||
|
@ -854,19 +1117,19 @@ public class HTable implements HConstants {
|
|||
// close the previous scanner if it's open
|
||||
if (this.callable != null) {
|
||||
this.callable.setClose();
|
||||
connection.getRegionServerWithRetries(callable);
|
||||
getConnection().getRegionServerWithRetries(callable);
|
||||
this.callable = null;
|
||||
}
|
||||
|
||||
// if we're at the end of the table, then close and return false
|
||||
// to stop iterating
|
||||
if (currentRegion != null){
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Advancing forward from region " + currentRegion);
|
||||
if (CLIENT_LOG.isDebugEnabled()) {
|
||||
CLIENT_LOG.debug("Advancing forward from region " + currentRegion);
|
||||
}
|
||||
|
||||
byte [] endKey = currentRegion.getEndKey();
|
||||
if (endKey == null || Bytes.equals(endKey, EMPTY_BYTE_ARRAY)) {
|
||||
if (endKey == null || Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY)) {
|
||||
close();
|
||||
return false;
|
||||
}
|
||||
|
@ -875,17 +1138,17 @@ public class HTable implements HConstants {
|
|||
HRegionInfo oldRegion = this.currentRegion;
|
||||
byte [] localStartKey = oldRegion == null? startRow: oldRegion.getEndKey();
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Advancing internal scanner to startKey at " +
|
||||
if (CLIENT_LOG.isDebugEnabled()) {
|
||||
CLIENT_LOG.debug("Advancing internal scanner to startKey at " +
|
||||
Bytes.toString(localStartKey));
|
||||
}
|
||||
|
||||
try {
|
||||
callable = new ScannerCallable(connection, tableName, columns,
|
||||
callable = new ScannerCallable(getConnection(), getTableName(), columns,
|
||||
localStartKey, scanTime, filter);
|
||||
// open a scanner on the region server starting at the
|
||||
// beginning of the region
|
||||
connection.getRegionServerWithRetries(callable);
|
||||
getConnection().getRegionServerWithRetries(callable);
|
||||
currentRegion = callable.getHRegionInfo();
|
||||
} catch (IOException e) {
|
||||
close();
|
||||
|
@ -902,7 +1165,7 @@ public class HTable implements HConstants {
|
|||
|
||||
RowResult values = null;
|
||||
do {
|
||||
values = connection.getRegionServerWithRetries(callable);
|
||||
values = getConnection().getRegionServerWithRetries(callable);
|
||||
} while (values != null && values.size() == 0 && nextScanner());
|
||||
|
||||
if (values != null && values.size() != 0) {
|
||||
|
@ -919,7 +1182,7 @@ public class HTable implements HConstants {
|
|||
if (callable != null) {
|
||||
callable.setClose();
|
||||
try {
|
||||
connection.getRegionServerWithRetries(callable);
|
||||
getConnection().getRegionServerWithRetries(callable);
|
||||
} catch (IOException e) {
|
||||
// We used to catch this error, interpret, and rethrow. However, we
|
||||
// have since decided that it's not nice for a scanner's close to
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.apache.hadoop.hbase.HServerLoad;
|
|||
import org.apache.hadoop.hbase.HServerAddress;
|
||||
import org.apache.hadoop.hbase.HMsg;
|
||||
import org.apache.hadoop.hbase.HRegionInfo;
|
||||
import org.apache.hadoop.hbase.LeaseException;
|
||||
import org.apache.hadoop.hbase.Leases;
|
||||
import org.apache.hadoop.hbase.LeaseListener;
|
||||
import org.apache.hadoop.hbase.HConstants;
|
||||
|
@ -520,7 +521,14 @@ class ServerManager implements HConstants {
|
|||
master.regionManager.unassignRootRegion();
|
||||
}
|
||||
LOG.info("Cancelling lease for " + serverName);
|
||||
serverLeases.cancelLease(serverName);
|
||||
try {
|
||||
serverLeases.cancelLease(serverName);
|
||||
} catch (LeaseException e) {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Cancelling " + serverName + " got " + e.getMessage() +
|
||||
"...continuing");
|
||||
}
|
||||
}
|
||||
leaseCancelled = true;
|
||||
|
||||
// update load information
|
||||
|
|
|
@ -251,7 +251,19 @@ public class Bytes {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param t
|
||||
* @return Array of byte arrays made from passed array of Text
|
||||
*/
|
||||
public static byte [][] toByteArrays(final String [] t) {
|
||||
byte [][] result = new byte[t.length][];
|
||||
for (int i = 0; i < t.length; i++) {
|
||||
result[i] = Bytes.toBytes(t[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param column
|
||||
* @return A byte array of a byte array where first and only entry is
|
||||
|
|
Loading…
Reference in New Issue