/*
* Copyright 2011 The Apache Software Foundation
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lealone.hbase.dbobject.index;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.HRegionInfo;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.regionserver.HRegionServer;
import org.apache.hadoop.hbase.regionserver.InternalScanner;
import org.apache.hadoop.hbase.util.Bytes;
import org.lealone.command.Prepared;
import org.lealone.dbobject.index.Cursor;
import org.lealone.dbobject.table.Column;
import org.lealone.dbobject.table.TableFilter;
import org.lealone.hbase.command.dml.WithWhereClause;
import org.lealone.hbase.dbobject.table.HBaseTable;
import org.lealone.hbase.engine.HBaseSession;
import org.lealone.hbase.result.HBaseRow;
import org.lealone.hbase.transaction.ValidityChecker;
import org.lealone.hbase.util.HBaseUtils;
import org.lealone.message.DbException;
import org.lealone.result.Row;
import org.lealone.result.SearchRow;
import org.lealone.value.Value;
import org.lealone.value.ValueString;
//执行select、delete、update语句都会触发此类
public class HBasePrimaryIndexCursor implements Cursor {
private final HBaseSession session;
private final byte[] regionName;
private final int fetchSize;
private final byte[] defaultColumnFamilyName;
//表所有列的个数
private final int columnCount;
//所要查询的列,不一定是表中的所有列,所以columnCount >= columns.size()
private final List<Column> columns;
private final boolean isGet;
private long scannerId;
private Result[] result;
private int index = -1;
private InternalScanner scanner;
private boolean isEnd = false;
private ArrayList<Result> tmpResultList;
/**
*
* @param filter 表过滤器
* @param first 所要查找的主键列的开始值
* @param last 所要查找的主键列的结束值
*/
public HBasePrimaryIndexCursor(TableFilter filter, SearchRow first, SearchRow last) {
session = (HBaseSession) filter.getSession();
HRegionServer rs = session.getRegionServer();
Prepared p = filter.getPrepared();
if (!(p instanceof WithWhereClause))
throw DbException.throwInternalError("not instanceof WithWhereClause: " + p);
regionName = Bytes.toBytes(((WithWhereClause) p).getWhereClauseSupport().getRegionName());
if (regionName == null)
throw DbException.throwInternalError("regionName is null");
fetchSize = p.getFetchSize();
defaultColumnFamilyName = ((HBaseTable) filter.getTable()).getDefaultColumnFamilyNameAsBytes();
columnCount = filter.getTable().getColumns().length;
//select语句
//对于下面两种类型的sql,columns会是null
//1: select count(*) from t (不带where条件)
//2: select _rowkey_ from t
if (filter.getSelect() != null)
columns = filter.getSelect().getColumns(filter);
else
columns = Arrays.asList(filter.getTable().getColumns()); //delete、update语句
Value startValue = null;
Value endValue = null;
if (first != null)
startValue = first.getRowKey();
if (last != null)
endValue = last.getRowKey();
//优化where pk = xxx,对于这样的等号查询,startValue和endValue相等,直接使用get方式获取数据
if (startValue != null && endValue != null && (startValue == endValue || startValue.equals(endValue))) {
try {
Result r = rs.get(regionName, new Get(Bytes.toBytes(startValue.getString())));
r = ValidityChecker.checkResult(defaultColumnFamilyName, session, rs, regionName, session.getTransaction(), r);
if (r != null)
result = new Result[] { r };
isGet = true;
scannerId = -1;
} catch (Exception e) {
throw DbException.convert(e);
}
} else {
isGet = false;
Scan scan = new Scan();
scan.setMaxVersions(1); //只取一个版本
byte[] startKey = HConstants.EMPTY_BYTE_ARRAY;
byte[] endKey = HConstants.EMPTY_BYTE_ARRAY;
if (startValue != null)
startKey = HBaseUtils.toBytes(startValue);
if (endValue != null)
endKey = HBaseUtils.toBytes(endValue);
//调整start和stop位置,不能直接使用原有的startValue和endValue,因为它们有可能不是正确的Region开始和结束范围
try {
HRegionInfo info = rs.getRegionInfo(regionName);
if (Bytes.compareTo(startKey, info.getStartKey()) >= 0)
scan.setStartRow(startKey);
else
scan.setStartRow(info.getStartKey());
if (Bytes.equals(endKey, HConstants.EMPTY_BYTE_ARRAY))
scan.setStopRow(info.getEndKey());
else if (Bytes.compareTo(endKey, info.getEndKey()) < 0)
scan.setStopRow(endKey);
else
scan.setStopRow(info.getEndKey());
} catch (Exception e) {
throw DbException.convert(e);
}
if (columns != null) {
for (Column c : columns) {
if (!c.isRowKeyColumn()) {
//只指定列族而不指定具体的列会得到更好的性能
//scan.addColumn(c.getColumnFamilyNameAsBytes(), c.getNameAsBytes());
scan.addFamily(c.getColumnFamilyNameAsBytes());
}
}
}
scan.addFamily(defaultColumnFamilyName);
//对于聚合运算,直接使用InternalScanner性能会更好,也无需启用ScannerListener,
//因为聚合运算完成后InternalScanner就会被主动关闭,
//通过scannerId的方式可能延迟到client端读取所有结果后才关闭或直到超时为止。
if (filter.getSelect() != null && filter.getSelect().isGroupQuery()) {
tmpResultList = new ArrayList<Result>(fetchSize);
try {
//TODO 这里绕过HRegionServer的检查流程了
//如何像HRegionServer.execCoprocessor那样走正常的流程
scanner = rs.getOnlineRegion(regionName).getScanner(scan);
} catch (Exception e) {
throw DbException.convert(e);
}
} else {
try {
scannerId = rs.openScanner(regionName, scan);
} catch (Exception e) {
throw DbException.convert(e);
}
}
}
}
@Override
public Row get() {
if (result != null && index < result.length) {
Result r = result[index];
Value[] data = new Value[columnCount];
Value rowKey = ValueString.get(Bytes.toString(r.getRow()));
if (columns != null) {
int i = 0;
for (Column c : columns) {
i = c.getColumnId();
if (c.isRowKeyColumn())
data[i] = rowKey;
else
data[i] = HBaseUtils.toValue( //
r.getValue(c.getColumnFamilyNameAsBytes(), c.getNameAsBytes()), c.getType());
}
}
return new HBaseRow(regionName, rowKey, data, Row.MEMORY_CALCULATE, r);
}
return null;
}
@Override
public SearchRow getSearchRow() {
return get();
}
@Override
public boolean next() {
index++;
if (result != null && index < result.length)
return true;
else if (isGet)
return false;
if (isEnd)
return false;
try {
if (scanner != null) {
tmpResultList.clear();
isEnd = !ValidityChecker.fetchResults(defaultColumnFamilyName, session, regionName, scanner, fetchSize,
tmpResultList);
result = tmpResultList.toArray(new Result[tmpResultList.size()]);
if (isEnd)
close();
} else
result = ValidityChecker.fetchResults(defaultColumnFamilyName, session, regionName, scannerId, fetchSize);
} catch (Exception e) {
close();
throw DbException.convert(e);
}
index = 0;
if (result != null && result.length > 0)
return true;
close();
return false;
}
@Override
public boolean previous() {
return false;
}
private void close() {
try {
if (scanner != null)
scanner.close();
else
session.getRegionServer().close(scannerId);
} catch (IOException e) {
//ignore
}
}
}