@Override
  public ExecStatus execute(CloudataConf conf) {
    ExecStatus status = new ExecStatus();

    try {
      // 현재 버전에서는 from 절에 subquery나 여러개의 테이블이 나타나는 것을 지원하지 않음
      if (fromTables.size() > 1) {
        throw new IOException("Not support multi tables");
      }
      String tableName = fromTables.get(0).getTableName();

      CTable ctable = CTable.openTable(conf, tableName);
      if (ctable == null) {
        throw new IOException("No table [" + tableName + "]");
      }
      RowFilter rowFilter = new RowFilter();

      for (ColumnElement eachElement : selectColumns) {
        List<String> columnNames = eachElement.getColumnNames(ctable);
        for (String eachColumn : columnNames) {
          CellFilter cellFilter = new CellFilter(eachColumn);
          rowFilter.addCellFilter(cellFilter);
        }
      }

      if (where != null) {
        where.initFilter(rowFilter);
        Row[] rows = ctable.gets(rowFilter);

        status.setRowIterator(new RowArrayIterator(ctable, rows, rowFilter.getColumns()));
        int rowCount = rows == null ? 0 : rows.length;

        status.setMessage(rowCount + " selected.");
      } else {
        TableScanner scanner = ScannerFactory.openScanner(ctable, rowFilter);
        status.setRowIterator(new RowScannerIterator(ctable, scanner, rowFilter.getColumns()));
      }
    } catch (IOException e) {
      status.setException(e);
    }
    return status;
  }
  public TableJoinRecordReader(
      JobConf jobConf, CloudataConf conf, TableSplit tableSplit, Reporter reporter)
      throws IOException {
    this.conf = conf;

    String mergeEvaluatorClass = tableSplit.getInputTableInfo().getMergeEvaluatorClass();
    MergeEvaluator mergeEvaluator = null;
    if (mergeEvaluatorClass != null && mergeEvaluatorClass.length() > 0) {
      try {
        mergeEvaluator = (MergeEvaluator) Class.forName(mergeEvaluatorClass).newInstance();
      } catch (Exception e) {
        LOG.error("mergeEvaluator:" + mergeEvaluatorClass + "," + e.getMessage());
        IOException err = new IOException(e.getMessage() + ":" + mergeEvaluatorClass);
        err.initCause(e);
        throw err;
      }
    }

    RowFilter splitRowFilter = tableSplit.getRowFilter();
    InputTableInfo inputTableInfo = tableSplit.getInputTableInfo();

    this.startRowKey = splitRowFilter.getStartRowKey();
    this.endRowKey = splitRowFilter.getEndRowKey();

    RowFilter rowFilter = inputTableInfo.getRowFilter();
    rowFilter.setStartRowKey(startRowKey);
    rowFilter.setEndRowKey(endRowKey);

    CTable ctable = CTable.openTable(conf, inputTableInfo.getTableName());

    TableScanner pivotScanner = null;
    TableScanner targetScanner = null;
    try {
      pivotScanner =
          ScannerFactory.openScanner(ctable, rowFilter, TableScanner.SCANNER_OPEN_TIMEOUT);
      Row.Key firstRowKey = null;
      try {
        // 첫번째 Tablet이 아닌 경우에는 첫번째 row는 무시한다.
        if (!startRowKey.equals(Row.Key.MIN_KEY)) {
          Row pivotRow = pivotScanner.nextRow();
          if (pivotRow == null) {
            end = true;
            return;
          }

          if (firstRowKey == null) {
            firstRowKey = pivotRow.getKey();
          }

          if (firstRowKey.equals(pivotRow.getKey())) {
            pivotRow = pivotScanner.nextRow();
            if (pivotRow == null) {
              end = true;
              return;
            }
          }
          pivotScanner.close();
          rowFilter.setStartRowKey(firstRowKey);
          pivotScanner =
              ScannerFactory.openScanner(ctable, rowFilter, TableScanner.SCANNER_OPEN_TIMEOUT);
        } else {
          firstRowKey = startRowKey;
        }
      } catch (Exception e) {
        if (pivotScanner != null) {
          pivotScanner.close();
        }
        throw e;
      }

      RowFilter joinRowFilter = inputTableInfo.getJoinRowFilter();

      if (mergeEvaluator != null) {
        if (!firstRowKey.equals(Row.Key.MIN_KEY)) {
          joinRowFilter.setStartRowKey(mergeEvaluator.parseTargetRowKey(firstRowKey, 0));
        } else {
          joinRowFilter.setStartRowKey(Row.Key.MIN_KEY);
        }
        if (!rowFilter.getEndRowKey().equals(Row.Key.MAX_KEY)) {
          joinRowFilter.setEndRowKey(mergeEvaluator.parseTargetRowKey(rowFilter.getEndRowKey(), 0));
        } else {
          joinRowFilter.setEndRowKey(Row.Key.MAX_KEY);
        }
      } else {
        joinRowFilter.setStartRowKey(firstRowKey);
        joinRowFilter.setEndRowKey(rowFilter.getEndRowKey());
      }

      reporter.setStatus(
          inputTableInfo.getTableName()
              + ":"
              + startRowKey
              + " ~ "
              + endRowKey
              + ", "
              + inputTableInfo.getJoinTableName()
              + ":"
              + joinRowFilter.getStartRowKey()
              + " ~ "
              + joinRowFilter.getEndRowKey());

      // pivot table의 startRow, endRow에 대응하는 target table의 scanner 생성
      CTable targetTable = CTable.openTable(conf, inputTableInfo.getJoinTableName());
      targetScanner =
          ScannerFactory.openScanner(targetTable, joinRowFilter, TableScanner.SCANNER_OPEN_TIMEOUT);

      this.scanner =
          new MergeScanner(new TableScanner[] {pivotScanner, targetScanner}, mergeEvaluator);
    } catch (Exception e) {
      if (targetScanner != null) {
        targetScanner.close();
      }
      IOException err = new IOException(e.getMessage());
      err.initCause(e);
      throw err;
    }
  }