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->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}