001/**
002 *
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements.  See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership.  The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License.  You may obtain a copy of the License at
010 *
011 *     http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 */
019
020package org.apache.hadoop.hbase.thrift;
021
022import static org.apache.hadoop.hbase.thrift.Constants.COALESCE_INC_KEY;
023import static org.apache.hadoop.hbase.util.Bytes.getBytes;
024
025import java.io.IOException;
026import java.nio.ByteBuffer;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.List;
031import java.util.Map;
032import java.util.TreeMap;
033
034import org.apache.hadoop.conf.Configuration;
035import org.apache.hadoop.hbase.Cell;
036import org.apache.hadoop.hbase.CellBuilder;
037import org.apache.hadoop.hbase.CellBuilderFactory;
038import org.apache.hadoop.hbase.CellBuilderType;
039import org.apache.hadoop.hbase.CellUtil;
040import org.apache.hadoop.hbase.HColumnDescriptor;
041import org.apache.hadoop.hbase.HConstants;
042import org.apache.hadoop.hbase.HRegionLocation;
043import org.apache.hadoop.hbase.HTableDescriptor;
044import org.apache.hadoop.hbase.KeyValue;
045import org.apache.hadoop.hbase.MetaTableAccessor;
046import org.apache.hadoop.hbase.ServerName;
047import org.apache.hadoop.hbase.TableName;
048import org.apache.hadoop.hbase.TableNotFoundException;
049import org.apache.hadoop.hbase.client.Append;
050import org.apache.hadoop.hbase.client.Delete;
051import org.apache.hadoop.hbase.client.Durability;
052import org.apache.hadoop.hbase.client.Get;
053import org.apache.hadoop.hbase.client.Increment;
054import org.apache.hadoop.hbase.client.OperationWithAttributes;
055import org.apache.hadoop.hbase.client.Put;
056import org.apache.hadoop.hbase.client.RegionInfo;
057import org.apache.hadoop.hbase.client.RegionLocator;
058import org.apache.hadoop.hbase.client.Result;
059import org.apache.hadoop.hbase.client.ResultScanner;
060import org.apache.hadoop.hbase.client.Scan;
061import org.apache.hadoop.hbase.client.Table;
062import org.apache.hadoop.hbase.filter.Filter;
063import org.apache.hadoop.hbase.filter.ParseFilter;
064import org.apache.hadoop.hbase.filter.PrefixFilter;
065import org.apache.hadoop.hbase.filter.WhileMatchFilter;
066import org.apache.hadoop.hbase.security.UserProvider;
067import org.apache.hadoop.hbase.thrift.generated.AlreadyExists;
068import org.apache.hadoop.hbase.thrift.generated.BatchMutation;
069import org.apache.hadoop.hbase.thrift.generated.ColumnDescriptor;
070import org.apache.hadoop.hbase.thrift.generated.Hbase;
071import org.apache.hadoop.hbase.thrift.generated.IOError;
072import org.apache.hadoop.hbase.thrift.generated.IllegalArgument;
073import org.apache.hadoop.hbase.thrift.generated.Mutation;
074import org.apache.hadoop.hbase.thrift.generated.TAppend;
075import org.apache.hadoop.hbase.thrift.generated.TCell;
076import org.apache.hadoop.hbase.thrift.generated.TIncrement;
077import org.apache.hadoop.hbase.thrift.generated.TRegionInfo;
078import org.apache.hadoop.hbase.thrift.generated.TRowResult;
079import org.apache.hadoop.hbase.thrift.generated.TScan;
080import org.apache.hadoop.hbase.util.Bytes;
081import org.apache.thrift.TException;
082import org.apache.yetus.audience.InterfaceAudience;
083import org.slf4j.Logger;
084import org.slf4j.LoggerFactory;
085
086import org.apache.hbase.thirdparty.com.google.common.base.Throwables;
087
088/**
089 * The HBaseServiceHandler is a glue object that connects Thrift RPC calls to the
090 * HBase client API primarily defined in the Admin and Table objects.
091 */
092@InterfaceAudience.Private
093@SuppressWarnings("deprecation")
094public class ThriftHBaseServiceHandler extends HBaseServiceHandler implements Hbase.Iface {
095  private static final Logger LOG = LoggerFactory.getLogger(ThriftHBaseServiceHandler.class);
096
097  public static final int HREGION_VERSION = 1;
098
099  // nextScannerId and scannerMap are used to manage scanner state
100  private int nextScannerId = 0;
101  private HashMap<Integer, ResultScannerWrapper> scannerMap;
102  IncrementCoalescer coalescer;
103
104  /**
105   * Returns a list of all the column families for a given Table.
106   *
107   * @param table table
108   * @throws IOException
109   */
110  byte[][] getAllColumns(Table table) throws IOException {
111    HColumnDescriptor[] cds = table.getTableDescriptor().getColumnFamilies();
112    byte[][] columns = new byte[cds.length][];
113    for (int i = 0; i < cds.length; i++) {
114      columns[i] = Bytes.add(cds[i].getName(),
115          KeyValue.COLUMN_FAMILY_DELIM_ARRAY);
116    }
117    return columns;
118  }
119
120
121  /**
122   * Assigns a unique ID to the scanner and adds the mapping to an internal
123   * hash-map.
124   *
125   * @param scanner the {@link ResultScanner} to add
126   * @return integer scanner id
127   */
128  protected synchronized int addScanner(ResultScanner scanner, boolean sortColumns) {
129    int id = nextScannerId++;
130    ResultScannerWrapper resultScannerWrapper =
131        new ResultScannerWrapper(scanner, sortColumns);
132    scannerMap.put(id, resultScannerWrapper);
133    return id;
134  }
135
136  /**
137   * Returns the scanner associated with the specified ID.
138   *
139   * @param id the ID of the scanner to get
140   * @return a Scanner, or null if ID was invalid.
141   */
142  private synchronized ResultScannerWrapper getScanner(int id) {
143    return scannerMap.get(id);
144  }
145
146  /**
147   * Removes the scanner associated with the specified ID from the internal
148   * id-&gt;scanner hash-map.
149   *
150   * @param id the ID of the scanner to remove
151   * @return a Scanner, or null if ID was invalid.
152   */
153  private synchronized ResultScannerWrapper removeScanner(int id) {
154    return scannerMap.remove(id);
155  }
156
157  protected ThriftHBaseServiceHandler(final Configuration c,
158      final UserProvider userProvider) throws IOException {
159    super(c, userProvider);
160    scannerMap = new HashMap<>();
161    this.coalescer = new IncrementCoalescer(this);
162  }
163
164
165  @Override
166  public void enableTable(ByteBuffer tableName) throws IOError {
167    try{
168      getAdmin().enableTable(getTableName(tableName));
169    } catch (IOException e) {
170      LOG.warn(e.getMessage(), e);
171      throw getIOError(e);
172    }
173  }
174
175  @Override
176  public void disableTable(ByteBuffer tableName) throws IOError{
177    try{
178      getAdmin().disableTable(getTableName(tableName));
179    } catch (IOException e) {
180      LOG.warn(e.getMessage(), e);
181      throw getIOError(e);
182    }
183  }
184
185  @Override
186  public boolean isTableEnabled(ByteBuffer tableName) throws IOError {
187    try {
188      return this.connectionCache.getAdmin().isTableEnabled(getTableName(tableName));
189    } catch (IOException e) {
190      LOG.warn(e.getMessage(), e);
191      throw getIOError(e);
192    }
193  }
194
195  // ThriftServerRunner.compact should be deprecated and replaced with methods specific to
196  // table and region.
197  @Override
198  public void compact(ByteBuffer tableNameOrRegionName) throws IOError {
199    try {
200      try {
201        getAdmin().compactRegion(getBytes(tableNameOrRegionName));
202      } catch (IllegalArgumentException e) {
203        // Invalid region, try table
204        getAdmin().compact(TableName.valueOf(getBytes(tableNameOrRegionName)));
205      }
206    } catch (IOException e) {
207      LOG.warn(e.getMessage(), e);
208      throw getIOError(e);
209    }
210  }
211
212  // ThriftServerRunner.majorCompact should be deprecated and replaced with methods specific
213  // to table and region.
214  @Override
215  public void majorCompact(ByteBuffer tableNameOrRegionName) throws IOError {
216    try {
217      try {
218        getAdmin().compactRegion(getBytes(tableNameOrRegionName));
219      } catch (IllegalArgumentException e) {
220        // Invalid region, try table
221        getAdmin().compact(TableName.valueOf(getBytes(tableNameOrRegionName)));
222      }
223    } catch (IOException e) {
224      LOG.warn(e.getMessage(), e);
225      throw getIOError(e);
226    }
227  }
228
229  @Override
230  public List<ByteBuffer> getTableNames() throws IOError {
231    try {
232      TableName[] tableNames = this.getAdmin().listTableNames();
233      ArrayList<ByteBuffer> list = new ArrayList<>(tableNames.length);
234      for (TableName tableName : tableNames) {
235        list.add(ByteBuffer.wrap(tableName.getName()));
236      }
237      return list;
238    } catch (IOException e) {
239      LOG.warn(e.getMessage(), e);
240      throw getIOError(e);
241    }
242  }
243
244  /**
245   * @return the list of regions in the given table, or an empty list if the table does not exist
246   */
247  @Override
248  public List<TRegionInfo> getTableRegions(ByteBuffer tableName) throws IOError {
249    try (RegionLocator locator = connectionCache.getRegionLocator(getBytes(tableName))) {
250      List<HRegionLocation> regionLocations = locator.getAllRegionLocations();
251      List<TRegionInfo> results = new ArrayList<>(regionLocations.size());
252      for (HRegionLocation regionLocation : regionLocations) {
253        RegionInfo info = regionLocation.getRegionInfo();
254        ServerName serverName = regionLocation.getServerName();
255        TRegionInfo region = new TRegionInfo();
256        region.serverName = ByteBuffer.wrap(
257            Bytes.toBytes(serverName.getHostname()));
258        region.port = serverName.getPort();
259        region.startKey = ByteBuffer.wrap(info.getStartKey());
260        region.endKey = ByteBuffer.wrap(info.getEndKey());
261        region.id = info.getRegionId();
262        region.name = ByteBuffer.wrap(info.getRegionName());
263        region.version = HREGION_VERSION; // HRegion now not versioned, PB encoding used
264        results.add(region);
265      }
266      return results;
267    } catch (TableNotFoundException e) {
268      // Return empty list for non-existing table
269      return Collections.emptyList();
270    } catch (IOException e){
271      LOG.warn(e.getMessage(), e);
272      throw getIOError(e);
273    }
274  }
275
276  @Override
277  public List<TCell> get(
278      ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
279      Map<ByteBuffer, ByteBuffer> attributes)
280      throws IOError {
281    byte [][] famAndQf = CellUtil.parseColumn(getBytes(column));
282    if (famAndQf.length == 1) {
283      return get(tableName, row, famAndQf[0], null, attributes);
284    }
285    if (famAndQf.length == 2) {
286      return get(tableName, row, famAndQf[0], famAndQf[1], attributes);
287    }
288    throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
289  }
290
291  /**
292   * Note: this internal interface is slightly different from public APIs in regard to handling
293   * of the qualifier. Here we differ from the public Java API in that null != byte[0]. Rather,
294   * we respect qual == null as a request for the entire column family. The caller (
295   * {@link #get(ByteBuffer, ByteBuffer, ByteBuffer, Map)}) interface IS consistent in that the
296   * column is parse like normal.
297   */
298  protected List<TCell> get(ByteBuffer tableName,
299      ByteBuffer row,
300      byte[] family,
301      byte[] qualifier,
302      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
303    Table table = null;
304    try {
305      table = getTable(tableName);
306      Get get = new Get(getBytes(row));
307      addAttributes(get, attributes);
308      if (qualifier == null) {
309        get.addFamily(family);
310      } else {
311        get.addColumn(family, qualifier);
312      }
313      Result result = table.get(get);
314      return ThriftUtilities.cellFromHBase(result.rawCells());
315    } catch (IOException e) {
316      LOG.warn(e.getMessage(), e);
317      throw getIOError(e);
318    } finally {
319      closeTable(table);
320    }
321  }
322
323  @Override
324  public List<TCell> getVer(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
325      int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
326    byte [][] famAndQf = CellUtil.parseColumn(getBytes(column));
327    if(famAndQf.length == 1) {
328      return getVer(tableName, row, famAndQf[0], null, numVersions, attributes);
329    }
330    if (famAndQf.length == 2) {
331      return getVer(tableName, row, famAndQf[0], famAndQf[1], numVersions, attributes);
332    }
333    throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
334
335  }
336
337  /**
338   * Note: this public interface is slightly different from public Java APIs in regard to
339   * handling of the qualifier. Here we differ from the public Java API in that null != byte[0].
340   * Rather, we respect qual == null as a request for the entire column family. If you want to
341   * access the entire column family, use
342   * {@link #getVer(ByteBuffer, ByteBuffer, ByteBuffer, int, Map)} with a {@code column} value
343   * that lacks a {@code ':'}.
344   */
345  public List<TCell> getVer(ByteBuffer tableName, ByteBuffer row, byte[] family,
346      byte[] qualifier, int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
347
348    Table table = null;
349    try {
350      table = getTable(tableName);
351      Get get = new Get(getBytes(row));
352      addAttributes(get, attributes);
353      if (null == qualifier) {
354        get.addFamily(family);
355      } else {
356        get.addColumn(family, qualifier);
357      }
358      get.setMaxVersions(numVersions);
359      Result result = table.get(get);
360      return ThriftUtilities.cellFromHBase(result.rawCells());
361    } catch (IOException e) {
362      LOG.warn(e.getMessage(), e);
363      throw getIOError(e);
364    } finally{
365      closeTable(table);
366    }
367  }
368
369  @Override
370  public List<TCell> getVerTs(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
371      long timestamp, int numVersions, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
372    byte [][] famAndQf = CellUtil.parseColumn(getBytes(column));
373    if (famAndQf.length == 1) {
374      return getVerTs(tableName, row, famAndQf[0], null, timestamp, numVersions, attributes);
375    }
376    if (famAndQf.length == 2) {
377      return getVerTs(tableName, row, famAndQf[0], famAndQf[1], timestamp, numVersions,
378          attributes);
379    }
380    throw new IllegalArgumentException("Invalid familyAndQualifier provided.");
381  }
382
383  /**
384   * Note: this internal interface is slightly different from public APIs in regard to handling
385   * of the qualifier. Here we differ from the public Java API in that null != byte[0]. Rather,
386   * we respect qual == null as a request for the entire column family. The caller (
387   * {@link #getVerTs(ByteBuffer, ByteBuffer, ByteBuffer, long, int, Map)}) interface IS
388   * consistent in that the column is parse like normal.
389   */
390  protected List<TCell> getVerTs(ByteBuffer tableName, ByteBuffer row, byte[] family,
391      byte[] qualifier, long timestamp, int numVersions, Map<ByteBuffer, ByteBuffer> attributes)
392      throws IOError {
393
394    Table table = null;
395    try {
396      table = getTable(tableName);
397      Get get = new Get(getBytes(row));
398      addAttributes(get, attributes);
399      if (null == qualifier) {
400        get.addFamily(family);
401      } else {
402        get.addColumn(family, qualifier);
403      }
404      get.setTimeRange(0, timestamp);
405      get.setMaxVersions(numVersions);
406      Result result = table.get(get);
407      return ThriftUtilities.cellFromHBase(result.rawCells());
408    } catch (IOException e) {
409      LOG.warn(e.getMessage(), e);
410      throw getIOError(e);
411    } finally{
412      closeTable(table);
413    }
414  }
415
416  @Override
417  public List<TRowResult> getRow(ByteBuffer tableName, ByteBuffer row,
418      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
419    return getRowWithColumnsTs(tableName, row, null,
420        HConstants.LATEST_TIMESTAMP,
421        attributes);
422  }
423
424  @Override
425  public List<TRowResult> getRowWithColumns(ByteBuffer tableName,
426      ByteBuffer row,
427      List<ByteBuffer> columns,
428      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
429    return getRowWithColumnsTs(tableName, row, columns,
430        HConstants.LATEST_TIMESTAMP,
431        attributes);
432  }
433
434  @Override
435  public List<TRowResult> getRowTs(ByteBuffer tableName, ByteBuffer row,
436      long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
437    return getRowWithColumnsTs(tableName, row, null,
438        timestamp, attributes);
439  }
440
441  @Override
442  public List<TRowResult> getRowWithColumnsTs(
443      ByteBuffer tableName, ByteBuffer row, List<ByteBuffer> columns,
444      long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
445
446    Table table = null;
447    try {
448      table = getTable(tableName);
449      if (columns == null) {
450        Get get = new Get(getBytes(row));
451        addAttributes(get, attributes);
452        get.setTimeRange(0, timestamp);
453        Result result = table.get(get);
454        return ThriftUtilities.rowResultFromHBase(result);
455      }
456      Get get = new Get(getBytes(row));
457      addAttributes(get, attributes);
458      for(ByteBuffer column : columns) {
459        byte [][] famAndQf = CellUtil.parseColumn(getBytes(column));
460        if (famAndQf.length == 1) {
461          get.addFamily(famAndQf[0]);
462        } else {
463          get.addColumn(famAndQf[0], famAndQf[1]);
464        }
465      }
466      get.setTimeRange(0, timestamp);
467      Result result = table.get(get);
468      return ThriftUtilities.rowResultFromHBase(result);
469    } catch (IOException e) {
470      LOG.warn(e.getMessage(), e);
471      throw getIOError(e);
472    } finally{
473      closeTable(table);
474    }
475  }
476
477  @Override
478  public List<TRowResult> getRows(ByteBuffer tableName,
479      List<ByteBuffer> rows,
480      Map<ByteBuffer, ByteBuffer> attributes)
481      throws IOError {
482    return getRowsWithColumnsTs(tableName, rows, null,
483        HConstants.LATEST_TIMESTAMP,
484        attributes);
485  }
486
487  @Override
488  public List<TRowResult> getRowsWithColumns(ByteBuffer tableName,
489      List<ByteBuffer> rows,
490      List<ByteBuffer> columns,
491      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
492    return getRowsWithColumnsTs(tableName, rows, columns,
493        HConstants.LATEST_TIMESTAMP,
494        attributes);
495  }
496
497  @Override
498  public List<TRowResult> getRowsTs(ByteBuffer tableName,
499      List<ByteBuffer> rows,
500      long timestamp,
501      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
502    return getRowsWithColumnsTs(tableName, rows, null,
503        timestamp, attributes);
504  }
505
506  @Override
507  public List<TRowResult> getRowsWithColumnsTs(ByteBuffer tableName,
508      List<ByteBuffer> rows,
509      List<ByteBuffer> columns, long timestamp,
510      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
511
512    Table table= null;
513    try {
514      List<Get> gets = new ArrayList<>(rows.size());
515      table = getTable(tableName);
516      if (metrics != null) {
517        metrics.incNumRowKeysInBatchGet(rows.size());
518      }
519      for (ByteBuffer row : rows) {
520        Get get = new Get(getBytes(row));
521        addAttributes(get, attributes);
522        if (columns != null) {
523
524          for(ByteBuffer column : columns) {
525            byte [][] famAndQf = CellUtil.parseColumn(getBytes(column));
526            if (famAndQf.length == 1) {
527              get.addFamily(famAndQf[0]);
528            } else {
529              get.addColumn(famAndQf[0], famAndQf[1]);
530            }
531          }
532        }
533        get.setTimeRange(0, timestamp);
534        gets.add(get);
535      }
536      Result[] result = table.get(gets);
537      return ThriftUtilities.rowResultFromHBase(result);
538    } catch (IOException e) {
539      LOG.warn(e.getMessage(), e);
540      throw getIOError(e);
541    } finally{
542      closeTable(table);
543    }
544  }
545
546  @Override
547  public void deleteAll(
548      ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
549      Map<ByteBuffer, ByteBuffer> attributes)
550      throws IOError {
551    deleteAllTs(tableName, row, column, HConstants.LATEST_TIMESTAMP,
552        attributes);
553  }
554
555  @Override
556  public void deleteAllTs(ByteBuffer tableName,
557      ByteBuffer row,
558      ByteBuffer column,
559      long timestamp, Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
560    Table table = null;
561    try {
562      table = getTable(tableName);
563      Delete delete  = new Delete(getBytes(row));
564      addAttributes(delete, attributes);
565      byte [][] famAndQf = CellUtil.parseColumn(getBytes(column));
566      if (famAndQf.length == 1) {
567        delete.addFamily(famAndQf[0], timestamp);
568      } else {
569        delete.addColumns(famAndQf[0], famAndQf[1], timestamp);
570      }
571      table.delete(delete);
572
573    } catch (IOException e) {
574      LOG.warn(e.getMessage(), e);
575      throw getIOError(e);
576    } finally {
577      closeTable(table);
578    }
579  }
580
581  @Override
582  public void deleteAllRow(
583      ByteBuffer tableName, ByteBuffer row,
584      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
585    deleteAllRowTs(tableName, row, HConstants.LATEST_TIMESTAMP, attributes);
586  }
587
588  @Override
589  public void deleteAllRowTs(
590      ByteBuffer tableName, ByteBuffer row, long timestamp,
591      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
592    Table table = null;
593    try {
594      table = getTable(tableName);
595      Delete delete  = new Delete(getBytes(row), timestamp);
596      addAttributes(delete, attributes);
597      table.delete(delete);
598    } catch (IOException e) {
599      LOG.warn(e.getMessage(), e);
600      throw getIOError(e);
601    } finally {
602      closeTable(table);
603    }
604  }
605
606  @Override
607  public void createTable(ByteBuffer in_tableName,
608      List<ColumnDescriptor> columnFamilies) throws IOError, IllegalArgument, AlreadyExists {
609    TableName tableName = getTableName(in_tableName);
610    try {
611      if (getAdmin().tableExists(tableName)) {
612        throw new AlreadyExists("table name already in use");
613      }
614      HTableDescriptor desc = new HTableDescriptor(tableName);
615      for (ColumnDescriptor col : columnFamilies) {
616        HColumnDescriptor colDesc = ThriftUtilities.colDescFromThrift(col);
617        desc.addFamily(colDesc);
618      }
619      getAdmin().createTable(desc);
620    } catch (IOException e) {
621      LOG.warn(e.getMessage(), e);
622      throw getIOError(e);
623    } catch (IllegalArgumentException e) {
624      LOG.warn(e.getMessage(), e);
625      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
626    }
627  }
628
629  private static TableName getTableName(ByteBuffer buffer) {
630    return TableName.valueOf(getBytes(buffer));
631  }
632
633  @Override
634  public void deleteTable(ByteBuffer in_tableName) throws IOError {
635    TableName tableName = getTableName(in_tableName);
636    if (LOG.isDebugEnabled()) {
637      LOG.debug("deleteTable: table={}", tableName);
638    }
639    try {
640      if (!getAdmin().tableExists(tableName)) {
641        throw new IOException("table does not exist");
642      }
643      getAdmin().deleteTable(tableName);
644    } catch (IOException e) {
645      LOG.warn(e.getMessage(), e);
646      throw getIOError(e);
647    }
648  }
649
650  @Override
651  public void mutateRow(ByteBuffer tableName, ByteBuffer row,
652      List<Mutation> mutations, Map<ByteBuffer, ByteBuffer> attributes)
653      throws IOError, IllegalArgument {
654    mutateRowTs(tableName, row, mutations, HConstants.LATEST_TIMESTAMP, attributes);
655  }
656
657  @Override
658  public void mutateRowTs(ByteBuffer tableName, ByteBuffer row,
659      List<Mutation> mutations, long timestamp,
660      Map<ByteBuffer, ByteBuffer> attributes)
661      throws IOError, IllegalArgument {
662    Table table = null;
663    try {
664      table = getTable(tableName);
665      Put put = new Put(getBytes(row), timestamp);
666      addAttributes(put, attributes);
667
668      Delete delete = new Delete(getBytes(row));
669      addAttributes(delete, attributes);
670      if (metrics != null) {
671        metrics.incNumRowKeysInBatchMutate(mutations.size());
672      }
673
674      // I apologize for all this mess :)
675      CellBuilder builder = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY);
676      for (Mutation m : mutations) {
677        byte[][] famAndQf = CellUtil.parseColumn(getBytes(m.column));
678        if (m.isDelete) {
679          if (famAndQf.length == 1) {
680            delete.addFamily(famAndQf[0], timestamp);
681          } else {
682            delete.addColumns(famAndQf[0], famAndQf[1], timestamp);
683          }
684          delete.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
685        } else {
686          if(famAndQf.length == 1) {
687            LOG.warn("No column qualifier specified. Delete is the only mutation supported "
688                + "over the whole column family.");
689          } else {
690            put.add(builder.clear()
691                .setRow(put.getRow())
692                .setFamily(famAndQf[0])
693                .setQualifier(famAndQf[1])
694                .setTimestamp(put.getTimestamp())
695                .setType(Cell.Type.Put)
696                .setValue(m.value != null ? getBytes(m.value)
697                    : HConstants.EMPTY_BYTE_ARRAY)
698                .build());
699          }
700          put.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
701        }
702      }
703      if (!delete.isEmpty()) {
704        table.delete(delete);
705      }
706      if (!put.isEmpty()) {
707        table.put(put);
708      }
709    } catch (IOException e) {
710      LOG.warn(e.getMessage(), e);
711      throw getIOError(e);
712    } catch (IllegalArgumentException e) {
713      LOG.warn(e.getMessage(), e);
714      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
715    } finally{
716      closeTable(table);
717    }
718  }
719
720  @Override
721  public void mutateRows(ByteBuffer tableName, List<BatchMutation> rowBatches,
722      Map<ByteBuffer, ByteBuffer> attributes)
723      throws IOError, IllegalArgument, TException {
724    mutateRowsTs(tableName, rowBatches, HConstants.LATEST_TIMESTAMP, attributes);
725  }
726
727  @Override
728  public void mutateRowsTs(
729      ByteBuffer tableName, List<BatchMutation> rowBatches, long timestamp,
730      Map<ByteBuffer, ByteBuffer> attributes)
731      throws IOError, IllegalArgument, TException {
732    List<Put> puts = new ArrayList<>();
733    List<Delete> deletes = new ArrayList<>();
734    CellBuilder builder = CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY);
735    for (BatchMutation batch : rowBatches) {
736      byte[] row = getBytes(batch.row);
737      List<Mutation> mutations = batch.mutations;
738      Delete delete = new Delete(row);
739      addAttributes(delete, attributes);
740      Put put = new Put(row, timestamp);
741      addAttributes(put, attributes);
742      for (Mutation m : mutations) {
743        byte[][] famAndQf = CellUtil.parseColumn(getBytes(m.column));
744        if (m.isDelete) {
745          // no qualifier, family only.
746          if (famAndQf.length == 1) {
747            delete.addFamily(famAndQf[0], timestamp);
748          } else {
749            delete.addColumns(famAndQf[0], famAndQf[1], timestamp);
750          }
751          delete.setDurability(m.writeToWAL ? Durability.SYNC_WAL
752              : Durability.SKIP_WAL);
753        } else {
754          if (famAndQf.length == 1) {
755            LOG.warn("No column qualifier specified. Delete is the only mutation supported "
756                + "over the whole column family.");
757          }
758          if (famAndQf.length == 2) {
759            try {
760              put.add(builder.clear()
761                  .setRow(put.getRow())
762                  .setFamily(famAndQf[0])
763                  .setQualifier(famAndQf[1])
764                  .setTimestamp(put.getTimestamp())
765                  .setType(Cell.Type.Put)
766                  .setValue(m.value != null ? getBytes(m.value)
767                      : HConstants.EMPTY_BYTE_ARRAY)
768                  .build());
769            } catch (IOException e) {
770              throw new IllegalArgumentException(e);
771            }
772          } else {
773            throw new IllegalArgumentException("Invalid famAndQf provided.");
774          }
775          put.setDurability(m.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
776        }
777      }
778      if (!delete.isEmpty()) {
779        deletes.add(delete);
780      }
781      if (!put.isEmpty()) {
782        puts.add(put);
783      }
784    }
785
786    Table table = null;
787    try {
788      table = getTable(tableName);
789      if (!puts.isEmpty()) {
790        table.put(puts);
791      }
792      if (!deletes.isEmpty()) {
793        table.delete(deletes);
794      }
795    } catch (IOException e) {
796      LOG.warn(e.getMessage(), e);
797      throw getIOError(e);
798    } catch (IllegalArgumentException e) {
799      LOG.warn(e.getMessage(), e);
800      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
801    } finally{
802      closeTable(table);
803    }
804  }
805
806  @Override
807  public long atomicIncrement(
808      ByteBuffer tableName, ByteBuffer row, ByteBuffer column, long amount)
809      throws IOError, IllegalArgument, TException {
810    byte [][] famAndQf = CellUtil.parseColumn(getBytes(column));
811    if(famAndQf.length == 1) {
812      return atomicIncrement(tableName, row, famAndQf[0], HConstants.EMPTY_BYTE_ARRAY, amount);
813    }
814    return atomicIncrement(tableName, row, famAndQf[0], famAndQf[1], amount);
815  }
816
817  protected long atomicIncrement(ByteBuffer tableName, ByteBuffer row,
818      byte [] family, byte [] qualifier, long amount)
819      throws IOError, IllegalArgument, TException {
820    Table table = null;
821    try {
822      table = getTable(tableName);
823      return table.incrementColumnValue(
824          getBytes(row), family, qualifier, amount);
825    } catch (IOException e) {
826      LOG.warn(e.getMessage(), e);
827      throw getIOError(e);
828    } finally {
829      closeTable(table);
830    }
831  }
832
833  @Override
834  public void scannerClose(int id) throws IOError, IllegalArgument {
835    LOG.debug("scannerClose: id={}", id);
836    ResultScannerWrapper resultScannerWrapper = getScanner(id);
837    if (resultScannerWrapper == null) {
838      LOG.warn("scanner ID is invalid");
839      throw new IllegalArgument("scanner ID is invalid");
840    }
841    resultScannerWrapper.getScanner().close();
842    removeScanner(id);
843  }
844
845  @Override
846  public List<TRowResult> scannerGetList(int id,int nbRows)
847      throws IllegalArgument, IOError {
848    LOG.debug("scannerGetList: id={}", id);
849    ResultScannerWrapper resultScannerWrapper = getScanner(id);
850    if (null == resultScannerWrapper) {
851      String message = "scanner ID is invalid";
852      LOG.warn(message);
853      throw new IllegalArgument("scanner ID is invalid");
854    }
855
856    Result [] results;
857    try {
858      results = resultScannerWrapper.getScanner().next(nbRows);
859      if (null == results) {
860        return new ArrayList<>();
861      }
862    } catch (IOException e) {
863      LOG.warn(e.getMessage(), e);
864      throw getIOError(e);
865    }
866    return ThriftUtilities.rowResultFromHBase(results, resultScannerWrapper.isColumnSorted());
867  }
868
869  @Override
870  public List<TRowResult> scannerGet(int id) throws IllegalArgument, IOError {
871    return scannerGetList(id,1);
872  }
873
874  @Override
875  public int scannerOpenWithScan(ByteBuffer tableName, TScan tScan,
876      Map<ByteBuffer, ByteBuffer> attributes)
877      throws IOError {
878
879    Table table = null;
880    try {
881      table = getTable(tableName);
882      Scan scan = new Scan();
883      addAttributes(scan, attributes);
884      if (tScan.isSetStartRow()) {
885        scan.setStartRow(tScan.getStartRow());
886      }
887      if (tScan.isSetStopRow()) {
888        scan.setStopRow(tScan.getStopRow());
889      }
890      if (tScan.isSetTimestamp()) {
891        scan.setTimeRange(0, tScan.getTimestamp());
892      }
893      if (tScan.isSetCaching()) {
894        scan.setCaching(tScan.getCaching());
895      }
896      if (tScan.isSetBatchSize()) {
897        scan.setBatch(tScan.getBatchSize());
898      }
899      if (tScan.isSetColumns() && !tScan.getColumns().isEmpty()) {
900        for(ByteBuffer column : tScan.getColumns()) {
901          byte [][] famQf = CellUtil.parseColumn(getBytes(column));
902          if(famQf.length == 1) {
903            scan.addFamily(famQf[0]);
904          } else {
905            scan.addColumn(famQf[0], famQf[1]);
906          }
907        }
908      }
909      if (tScan.isSetFilterString()) {
910        ParseFilter parseFilter = new ParseFilter();
911        scan.setFilter(
912            parseFilter.parseFilterString(tScan.getFilterString()));
913      }
914      if (tScan.isSetReversed()) {
915        scan.setReversed(tScan.isReversed());
916      }
917      if (tScan.isSetCacheBlocks()) {
918        scan.setCacheBlocks(tScan.isCacheBlocks());
919      }
920      return addScanner(table.getScanner(scan), tScan.sortColumns);
921    } catch (IOException e) {
922      LOG.warn(e.getMessage(), e);
923      throw getIOError(e);
924    } finally{
925      closeTable(table);
926    }
927  }
928
929  @Override
930  public int scannerOpen(ByteBuffer tableName, ByteBuffer startRow,
931      List<ByteBuffer> columns,
932      Map<ByteBuffer, ByteBuffer> attributes) throws IOError {
933
934    Table table = null;
935    try {
936      table = getTable(tableName);
937      Scan scan = new Scan(getBytes(startRow));
938      addAttributes(scan, attributes);
939      if(columns != null && !columns.isEmpty()) {
940        for(ByteBuffer column : columns) {
941          byte [][] famQf = CellUtil.parseColumn(getBytes(column));
942          if(famQf.length == 1) {
943            scan.addFamily(famQf[0]);
944          } else {
945            scan.addColumn(famQf[0], famQf[1]);
946          }
947        }
948      }
949      return addScanner(table.getScanner(scan), false);
950    } catch (IOException e) {
951      LOG.warn(e.getMessage(), e);
952      throw getIOError(e);
953    } finally{
954      closeTable(table);
955    }
956  }
957
958  @Override
959  public int scannerOpenWithStop(ByteBuffer tableName, ByteBuffer startRow,
960      ByteBuffer stopRow, List<ByteBuffer> columns,
961      Map<ByteBuffer, ByteBuffer> attributes)
962      throws IOError, TException {
963
964    Table table = null;
965    try {
966      table = getTable(tableName);
967      Scan scan = new Scan(getBytes(startRow), getBytes(stopRow));
968      addAttributes(scan, attributes);
969      if(columns != null && !columns.isEmpty()) {
970        for(ByteBuffer column : columns) {
971          byte [][] famQf = CellUtil.parseColumn(getBytes(column));
972          if(famQf.length == 1) {
973            scan.addFamily(famQf[0]);
974          } else {
975            scan.addColumn(famQf[0], famQf[1]);
976          }
977        }
978      }
979      return addScanner(table.getScanner(scan), false);
980    } catch (IOException e) {
981      LOG.warn(e.getMessage(), e);
982      throw getIOError(e);
983    } finally{
984      closeTable(table);
985    }
986  }
987
988  @Override
989  public int scannerOpenWithPrefix(ByteBuffer tableName,
990      ByteBuffer startAndPrefix,
991      List<ByteBuffer> columns,
992      Map<ByteBuffer, ByteBuffer> attributes)
993      throws IOError, TException {
994
995    Table table = null;
996    try {
997      table = getTable(tableName);
998      Scan scan = new Scan(getBytes(startAndPrefix));
999      addAttributes(scan, attributes);
1000      Filter f = new WhileMatchFilter(
1001          new PrefixFilter(getBytes(startAndPrefix)));
1002      scan.setFilter(f);
1003      if (columns != null && !columns.isEmpty()) {
1004        for(ByteBuffer column : columns) {
1005          byte [][] famQf = CellUtil.parseColumn(getBytes(column));
1006          if(famQf.length == 1) {
1007            scan.addFamily(famQf[0]);
1008          } else {
1009            scan.addColumn(famQf[0], famQf[1]);
1010          }
1011        }
1012      }
1013      return addScanner(table.getScanner(scan), false);
1014    } catch (IOException e) {
1015      LOG.warn(e.getMessage(), e);
1016      throw getIOError(e);
1017    } finally{
1018      closeTable(table);
1019    }
1020  }
1021
1022  @Override
1023  public int scannerOpenTs(ByteBuffer tableName, ByteBuffer startRow,
1024      List<ByteBuffer> columns, long timestamp,
1025      Map<ByteBuffer, ByteBuffer> attributes) throws IOError, TException {
1026
1027    Table table = null;
1028    try {
1029      table = getTable(tableName);
1030      Scan scan = new Scan(getBytes(startRow));
1031      addAttributes(scan, attributes);
1032      scan.setTimeRange(0, timestamp);
1033      if (columns != null && !columns.isEmpty()) {
1034        for (ByteBuffer column : columns) {
1035          byte [][] famQf = CellUtil.parseColumn(getBytes(column));
1036          if(famQf.length == 1) {
1037            scan.addFamily(famQf[0]);
1038          } else {
1039            scan.addColumn(famQf[0], famQf[1]);
1040          }
1041        }
1042      }
1043      return addScanner(table.getScanner(scan), false);
1044    } catch (IOException e) {
1045      LOG.warn(e.getMessage(), e);
1046      throw getIOError(e);
1047    } finally{
1048      closeTable(table);
1049    }
1050  }
1051
1052  @Override
1053  public int scannerOpenWithStopTs(ByteBuffer tableName, ByteBuffer startRow,
1054      ByteBuffer stopRow, List<ByteBuffer> columns, long timestamp,
1055      Map<ByteBuffer, ByteBuffer> attributes)
1056      throws IOError, TException {
1057
1058    Table table = null;
1059    try {
1060      table = getTable(tableName);
1061      Scan scan = new Scan(getBytes(startRow), getBytes(stopRow));
1062      addAttributes(scan, attributes);
1063      scan.setTimeRange(0, timestamp);
1064      if (columns != null && !columns.isEmpty()) {
1065        for (ByteBuffer column : columns) {
1066          byte [][] famQf = CellUtil.parseColumn(getBytes(column));
1067          if(famQf.length == 1) {
1068            scan.addFamily(famQf[0]);
1069          } else {
1070            scan.addColumn(famQf[0], famQf[1]);
1071          }
1072        }
1073      }
1074      scan.setTimeRange(0, timestamp);
1075      return addScanner(table.getScanner(scan), false);
1076    } catch (IOException e) {
1077      LOG.warn(e.getMessage(), e);
1078      throw getIOError(e);
1079    } finally{
1080      closeTable(table);
1081    }
1082  }
1083
1084  @Override
1085  public Map<ByteBuffer, ColumnDescriptor> getColumnDescriptors(
1086      ByteBuffer tableName) throws IOError, TException {
1087
1088    Table table = null;
1089    try {
1090      TreeMap<ByteBuffer, ColumnDescriptor> columns = new TreeMap<>();
1091
1092      table = getTable(tableName);
1093      HTableDescriptor desc = table.getTableDescriptor();
1094
1095      for (HColumnDescriptor e : desc.getFamilies()) {
1096        ColumnDescriptor col = ThriftUtilities.colDescFromHbase(e);
1097        columns.put(col.name, col);
1098      }
1099      return columns;
1100    } catch (IOException e) {
1101      LOG.warn(e.getMessage(), e);
1102      throw getIOError(e);
1103    } finally {
1104      closeTable(table);
1105    }
1106  }
1107
1108  private void closeTable(Table table) throws IOError {
1109    try{
1110      if(table != null){
1111        table.close();
1112      }
1113    } catch (IOException e){
1114      LOG.error(e.getMessage(), e);
1115      throw getIOError(e);
1116    }
1117  }
1118
1119  @Override
1120  public TRegionInfo getRegionInfo(ByteBuffer searchRow) throws IOError {
1121    try {
1122      byte[] row = getBytes(searchRow);
1123      Result startRowResult = getReverseScanResult(TableName.META_TABLE_NAME.getName(), row,
1124          HConstants.CATALOG_FAMILY);
1125
1126      if (startRowResult == null) {
1127        throw new IOException("Cannot find row in "+ TableName.META_TABLE_NAME+", row="
1128            + Bytes.toStringBinary(row));
1129      }
1130
1131      // find region start and end keys
1132      RegionInfo regionInfo = MetaTableAccessor.getRegionInfo(startRowResult);
1133      if (regionInfo == null) {
1134        throw new IOException("RegionInfo REGIONINFO was null or " +
1135            " empty in Meta for row="
1136            + Bytes.toStringBinary(row));
1137      }
1138      TRegionInfo region = new TRegionInfo();
1139      region.setStartKey(regionInfo.getStartKey());
1140      region.setEndKey(regionInfo.getEndKey());
1141      region.id = regionInfo.getRegionId();
1142      region.setName(regionInfo.getRegionName());
1143      region.version = HREGION_VERSION; // version not used anymore, PB encoding used.
1144
1145      // find region assignment to server
1146      ServerName serverName = MetaTableAccessor.getServerName(startRowResult, 0);
1147      if (serverName != null) {
1148        region.setServerName(Bytes.toBytes(serverName.getHostname()));
1149        region.port = serverName.getPort();
1150      }
1151      return region;
1152    } catch (IOException e) {
1153      LOG.warn(e.getMessage(), e);
1154      throw getIOError(e);
1155    }
1156  }
1157
1158  private Result getReverseScanResult(byte[] tableName, byte[] row, byte[] family)
1159      throws IOException {
1160    Scan scan = new Scan(row);
1161    scan.setReversed(true);
1162    scan.addFamily(family);
1163    scan.setStartRow(row);
1164    try (Table table = getTable(tableName);
1165         ResultScanner scanner = table.getScanner(scan)) {
1166      return scanner.next();
1167    }
1168  }
1169
1170  @Override
1171  public void increment(TIncrement tincrement) throws IOError, TException {
1172
1173    if (tincrement.getRow().length == 0 || tincrement.getTable().length == 0) {
1174      throw new TException("Must supply a table and a row key; can't increment");
1175    }
1176
1177    if (conf.getBoolean(COALESCE_INC_KEY, false)) {
1178      this.coalescer.queueIncrement(tincrement);
1179      return;
1180    }
1181
1182    Table table = null;
1183    try {
1184      table = getTable(tincrement.getTable());
1185      Increment inc = ThriftUtilities.incrementFromThrift(tincrement);
1186      table.increment(inc);
1187    } catch (IOException e) {
1188      LOG.warn(e.getMessage(), e);
1189      throw getIOError(e);
1190    } finally{
1191      closeTable(table);
1192    }
1193  }
1194
1195  @Override
1196  public void incrementRows(List<TIncrement> tincrements) throws IOError, TException {
1197    if (conf.getBoolean(COALESCE_INC_KEY, false)) {
1198      this.coalescer.queueIncrements(tincrements);
1199      return;
1200    }
1201    for (TIncrement tinc : tincrements) {
1202      increment(tinc);
1203    }
1204  }
1205
1206  @Override
1207  public List<TCell> append(TAppend tappend) throws IOError, TException {
1208    if (tappend.getRow().length == 0 || tappend.getTable().length == 0) {
1209      throw new TException("Must supply a table and a row key; can't append");
1210    }
1211
1212    Table table = null;
1213    try {
1214      table = getTable(tappend.getTable());
1215      Append append = ThriftUtilities.appendFromThrift(tappend);
1216      Result result = table.append(append);
1217      return ThriftUtilities.cellFromHBase(result.rawCells());
1218    } catch (IOException e) {
1219      LOG.warn(e.getMessage(), e);
1220      throw getIOError(e);
1221    } finally{
1222      closeTable(table);
1223    }
1224  }
1225
1226  @Override
1227  public boolean checkAndPut(ByteBuffer tableName, ByteBuffer row, ByteBuffer column,
1228      ByteBuffer value, Mutation mput, Map<ByteBuffer, ByteBuffer> attributes) throws IOError,
1229      IllegalArgument, TException {
1230    Put put;
1231    try {
1232      put = new Put(getBytes(row), HConstants.LATEST_TIMESTAMP);
1233      addAttributes(put, attributes);
1234
1235      byte[][] famAndQf = CellUtil.parseColumn(getBytes(mput.column));
1236      put.add(CellBuilderFactory.create(CellBuilderType.SHALLOW_COPY)
1237          .setRow(put.getRow())
1238          .setFamily(famAndQf[0])
1239          .setQualifier(famAndQf[1])
1240          .setTimestamp(put.getTimestamp())
1241          .setType(Cell.Type.Put)
1242          .setValue(mput.value != null ? getBytes(mput.value)
1243              : HConstants.EMPTY_BYTE_ARRAY)
1244          .build());
1245      put.setDurability(mput.writeToWAL ? Durability.SYNC_WAL : Durability.SKIP_WAL);
1246    } catch (IOException | IllegalArgumentException e) {
1247      LOG.warn(e.getMessage(), e);
1248      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
1249    }
1250
1251    Table table = null;
1252    try {
1253      table = getTable(tableName);
1254      byte[][] famAndQf = CellUtil.parseColumn(getBytes(column));
1255      Table.CheckAndMutateBuilder mutateBuilder =
1256          table.checkAndMutate(getBytes(row), famAndQf[0]).qualifier(famAndQf[1]);
1257      if (value != null) {
1258        return mutateBuilder.ifEquals(getBytes(value)).thenPut(put);
1259      } else {
1260        return mutateBuilder.ifNotExists().thenPut(put);
1261      }
1262    } catch (IOException e) {
1263      LOG.warn(e.getMessage(), e);
1264      throw getIOError(e);
1265    } catch (IllegalArgumentException e) {
1266      LOG.warn(e.getMessage(), e);
1267      throw new IllegalArgument(Throwables.getStackTraceAsString(e));
1268    } finally {
1269      closeTable(table);
1270    }
1271  }
1272
1273  private static IOError getIOError(Throwable throwable) {
1274    IOError error = new IOErrorWithCause(throwable);
1275    error.setMessage(Throwables.getStackTraceAsString(throwable));
1276    return error;
1277  }
1278
1279  /**
1280   * Adds all the attributes into the Operation object
1281   */
1282  private static void addAttributes(OperationWithAttributes op,
1283      Map<ByteBuffer, ByteBuffer> attributes) {
1284    if (attributes == null || attributes.isEmpty()) {
1285      return;
1286    }
1287    for (Map.Entry<ByteBuffer, ByteBuffer> entry : attributes.entrySet()) {
1288      String name = Bytes.toStringBinary(getBytes(entry.getKey()));
1289      byte[] value =  getBytes(entry.getValue());
1290      op.setAttribute(name, value);
1291    }
1292  }
1293
1294  protected static class ResultScannerWrapper {
1295
1296    private final ResultScanner scanner;
1297    private final boolean sortColumns;
1298    public ResultScannerWrapper(ResultScanner resultScanner,
1299        boolean sortResultColumns) {
1300      scanner = resultScanner;
1301      sortColumns = sortResultColumns;
1302    }
1303
1304    public ResultScanner getScanner() {
1305      return scanner;
1306    }
1307
1308    public boolean isColumnSorted() {
1309      return sortColumns;
1310    }
1311  }
1312
1313  public static class IOErrorWithCause extends IOError {
1314    private final Throwable cause;
1315    public IOErrorWithCause(Throwable cause) {
1316      this.cause = cause;
1317    }
1318
1319    @Override
1320    public synchronized Throwable getCause() {
1321      return cause;
1322    }
1323
1324    @Override
1325    public boolean equals(Object other) {
1326      if (super.equals(other) &&
1327          other instanceof IOErrorWithCause) {
1328        Throwable otherCause = ((IOErrorWithCause) other).getCause();
1329        if (this.getCause() != null) {
1330          return otherCause != null && this.getCause().equals(otherCause);
1331        } else {
1332          return otherCause == null;
1333        }
1334      }
1335      return false;
1336    }
1337
1338    @Override
1339    public int hashCode() {
1340      int result = super.hashCode();
1341      result = 31 * result + (cause != null ? cause.hashCode() : 0);
1342      return result;
1343    }
1344  }
1345
1346
1347}