/** * 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.apache.hadoop.hbase.index.coprocessor.regionserver; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.KeyValue; import org.apache.hadoop.hbase.index.util.IndexUtils; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.util.Pair; public class IndexRegionScannerForAND implements IndexRegionScanner { private static final Log LOG = LogFactory.getLog(IndexRegionScannerForAND.class); private List<IndexRegionScanner> scanners = null; private Map<String, Pair<List<Boolean>, Integer>> rowCache = new HashMap<String, Pair<List<Boolean>, Integer>>(); private int scannersCount = 0; boolean hasRangeScanners = false; private int scannerIndex = -1; public IndexRegionScannerForAND(List<IndexRegionScanner> scanners) { this.scanners = scanners; scannersCount = scanners.size(); } @Override public void advance() { for (IndexRegionScanner scn : this.scanners) { scn.advance(); } } @Override public HRegionInfo getRegionInfo() { return null; } @Override public boolean isFilterDone() { return false; } @Override public boolean hasChildScanners() { return scannersCount != 0; }; @Override public void setRangeFlag(boolean range) { hasRangeScanners = range; } @Override public boolean isRange() { return this.hasRangeScanners; } @Override public void setScannerIndex(int index) { scannerIndex = index; } @Override public int getScannerIndex() { return scannerIndex; } @Override public synchronized boolean reseek(byte[] row) throws IOException { // ideally reseek on AND may not come as AND cannot be a child of another AND. if (this.scanners.isEmpty()) return false; for (IndexRegionScanner scn : this.scanners) { boolean reseek = scn.reseek(row); if (!reseek) return false; } return true; } @Override public synchronized void close() throws IOException { for (IndexRegionScanner scn : this.scanners) { scn.close(); } this.scanners.clear(); } @Override public synchronized boolean next(List<KeyValue> results) throws IOException { if (this.scanners != null && !this.scanners.isEmpty()) { List<Pair<byte[], IndexRegionScanner>> valueList = new ArrayList<Pair<byte[], IndexRegionScanner>>(); byte[] maxRowKey = null; while (results.size() < 1) { List<KeyValue> intermediateResult = new ArrayList<KeyValue>(); boolean haveSameRows = true; KeyValue kv = null; Iterator<IndexRegionScanner> scnItr = this.scanners.iterator(); while (scnItr.hasNext()) { IndexRegionScanner scn = scnItr.next(); if (!hasRangeScanners) { if (checkForScanner(valueList, scn.getScannerIndex())) continue; } boolean hasMore = scn.next(intermediateResult); if (!hasRangeScanners) { if (intermediateResult != null && !intermediateResult.isEmpty()) { byte[] rowKey = IndexUtils.getRowKeyFromKV(intermediateResult.get(0)); if (maxRowKey == null) { maxRowKey = rowKey; } else { int result = Bytes.compareTo(maxRowKey, rowKey); if (haveSameRows) haveSameRows = (result == 0); maxRowKey = result > 0 ? maxRowKey : rowKey; } if (kv == null) kv = intermediateResult.get(0); intermediateResult.clear(); valueList.add(new Pair<byte[], IndexRegionScanner>(rowKey, scn)); } } else { if (!intermediateResult.isEmpty()) { boolean matching = checkAndPutMatchingEntry(intermediateResult.get(0), scn.getScannerIndex()); if (matching) { results.addAll(intermediateResult); } intermediateResult.clear(); } } if (!hasMore) { if (LOG.isDebugEnabled()) { LOG.debug("Removing scanner " + scn + " from the list."); } scn.close(); scnItr.remove(); if (hasRangeScanners) { // TODO: we can remove unnecessary rows(which never become matching entries) from // cache on scanner close. if (this.scanners.isEmpty()) return false; } if (results.size() > 0) { break; } } if (results.size() > 0) { return !this.scanners.isEmpty(); } } if (!hasRangeScanners) { if (haveSameRows && valueList.size() == scannersCount) { if (kv != null) results.add(kv); return this.scanners.size() == scannersCount; } else if (haveSameRows && valueList.size() != scannersCount) { close(); return false; } else { // In case of AND if the reseek on any one scanner returns false // we can close the entire scanners in the AND subtree if (!reseekTheScanners(valueList, maxRowKey)) { close(); return false; } } } } if (hasRangeScanners) { return true; } else { return this.scanners.size() == scannersCount; } } return false; } private boolean checkForScanner(List<Pair<byte[], IndexRegionScanner>> valueList, int scnIndex) { for (Pair<byte[], IndexRegionScanner> value : valueList) { if (value.getSecond().getScannerIndex() == scnIndex) { return true; } } return false; } private boolean checkAndPutMatchingEntry(KeyValue intermediateResult, int index) { String rowKeyFromKV = Bytes.toString(IndexUtils.getRowKeyFromKV(intermediateResult)); Pair<List<Boolean>, Integer> countPair = rowCache.get(rowKeyFromKV); if (countPair == null) { // If present scanners count is not equal to actual scanners count,no need to put row key into // cache because this will never become matching entry. if (this.scanners.size() == scannersCount) { List<Boolean> scannerFlags = new ArrayList<Boolean>(scannerIndex); for (int i = 0; i < scannersCount; i++) { scannerFlags.add(false); } countPair = new Pair<List<Boolean>, Integer>(scannerFlags, 0); // TODO verify // rowCache.put(rowKeyFromKV, countPair); } else { return false; } } Boolean indexFlag = countPair.getFirst().get(index); Integer countObj = countPair.getSecond(); // If count is equal to scanner count before increment means its returned already. skip the // result. if (countObj == scannersCount) { return false; } if (!indexFlag) { countObj++; countPair.getFirst().set(index, true); countPair.setSecond(countObj); rowCache.put(rowKeyFromKV, countPair); } if (countObj == scannersCount) { return true; } else { // If the difference between actual scanner count(scannerCount) and number of scanners have // this row(countObj) is more than present scanners size then remove row from cache because // this never be maching entry. if ((scannersCount - countObj) > this.scanners.size()) { rowCache.remove(rowKeyFromKV); return false; } } return false; } private boolean reseekTheScanners(List<Pair<byte[], IndexRegionScanner>> valueList, byte[] maxRowKey) throws IOException { Iterator<Pair<byte[], IndexRegionScanner>> itr = valueList.iterator(); while (itr.hasNext()) { Pair<byte[], IndexRegionScanner> rowVsScanner = itr.next(); IndexRegionScanner scn = rowVsScanner.getSecond(); // We need to call reseek on OR scanner even the last returned row key is equal or more than // max row key to set reseek flag. if (scn instanceof IndexRegionScannerForOR) { rowVsScanner.getSecond().reseek(maxRowKey); itr.remove(); continue; } if (Bytes.compareTo(rowVsScanner.getFirst(), maxRowKey) < 0) { if (!scn.reseek(maxRowKey)) { return false; } itr.remove(); } } return true; } @Override public boolean next(List<KeyValue> result, int limit) throws IOException { // TODO Auto-generated method stub return false; } @Override public long getMvccReadPoint() { // TODO Implement RegionScanner.getMvccReadPoint return 0; } @Override public boolean nextRaw(List<KeyValue> result, String metric) throws IOException { // TODO Implement RegionScanner.nextRaw return false; } @Override public boolean nextRaw(List<KeyValue> result, int limit, String metric) throws IOException { // TODO Implement RegionScanner.nextRaw return false; } @Override public boolean next(List<KeyValue> results, String metric) throws IOException { // TODO Implement InternalScanner.next return false; } @Override public boolean next(List<KeyValue> result, int limit, String metric) throws IOException { // TODO Implement InternalScanner.next return false; } }