All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.healthmarketscience.jackcess.impl.IndexCursorImpl Maven / Gradle / Ivy

/*
Copyright (c) 2011 James Ahlborn

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
USA
*/

package com.healthmarketscience.jackcess.impl;

import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import com.healthmarketscience.jackcess.Index;
import com.healthmarketscience.jackcess.IndexCursor;
import com.healthmarketscience.jackcess.Row;
import com.healthmarketscience.jackcess.RuntimeIOException;
import com.healthmarketscience.jackcess.impl.TableImpl.RowState;
import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
import com.healthmarketscience.jackcess.util.ColumnMatcher;
import com.healthmarketscience.jackcess.util.EntryIterableBuilder;
import com.healthmarketscience.jackcess.util.SimpleColumnMatcher;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Cursor backed by an index with extended traversal options.
 *
 * @author James Ahlborn
 */
public class IndexCursorImpl extends CursorImpl implements IndexCursor
{
  private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class);  

  /** IndexDirHandler for forward traversal */
  private final IndexDirHandler _forwardDirHandler =
    new ForwardIndexDirHandler();
  /** IndexDirHandler for backward traversal */
  private final IndexDirHandler _reverseDirHandler =
    new ReverseIndexDirHandler();
  /** logical index which this cursor is using */
  private final IndexImpl _index;
  /** Cursor over the entries of the relevant index */
  private final IndexData.EntryCursor _entryCursor;
  /** column names for the index entry columns */
  private Set _indexEntryPattern;

  private IndexCursorImpl(TableImpl table, IndexImpl index,
                          IndexData.EntryCursor entryCursor)
    throws IOException
  {
    super(new IdImpl(table, index), table,
          new IndexPosition(entryCursor.getFirstEntry()),
          new IndexPosition(entryCursor.getLastEntry()));
    _index = index;
    _index.initialize();
    _entryCursor = entryCursor;
  }
  
  /**
   * Creates an indexed cursor for the given table, narrowed to the given
   * range.
   * 

* Note, index based table traversal may not include all rows, as certain * types of indexes do not include all entries (namely, some indexes ignore * null entries, see {@link Index#shouldIgnoreNulls}). * * @param table the table over which this cursor will traverse * @param index index for the table which will define traversal order as * well as enhance certain lookups * @param startRow the first row of data for the cursor, or {@code null} for * the first entry * @param startInclusive whether or not startRow is inclusive or exclusive * @param endRow the last row of data for the cursor, or {@code null} for * the last entry * @param endInclusive whether or not endRow is inclusive or exclusive */ public static IndexCursorImpl createCursor(TableImpl table, IndexImpl index, Object[] startRow, boolean startInclusive, Object[] endRow, boolean endInclusive) throws IOException { if(table != index.getTable()) { throw new IllegalArgumentException( "Given index is not for given table: " + index + ", " + table); } if(!table.getFormat().INDEXES_SUPPORTED) { throw new IllegalArgumentException( "JetFormat " + table.getFormat() + " does not currently support index lookups"); } if(index.getIndexData().getUnsupportedReason() != null) { throw new IllegalArgumentException( "Given index " + index + " is not usable for indexed lookups due to " + index.getIndexData().getUnsupportedReason()); } IndexCursorImpl cursor = new IndexCursorImpl( table, index, index.cursor(startRow, startInclusive, endRow, endInclusive)); // init the column matcher appropriately for the index type cursor.setColumnMatcher(null); return cursor; } public IndexImpl getIndex() { return _index; } public boolean findFirstRowByEntry(Object... entryValues) throws IOException { PositionImpl curPos = _curPos; PositionImpl prevPos = _prevPos; boolean found = false; try { found = findFirstRowByEntryImpl(toRowValues(entryValues), true, _columnMatcher); return found; } finally { if(!found) { try { restorePosition(curPos, prevPos); } catch(IOException e) { LOG.error("Failed restoring position", e); } } } } public void findClosestRowByEntry(Object... entryValues) throws IOException { PositionImpl curPos = _curPos; PositionImpl prevPos = _prevPos; boolean found = false; try { findFirstRowByEntryImpl(toRowValues(entryValues), false, _columnMatcher); found = true; } finally { if(!found) { try { restorePosition(curPos, prevPos); } catch(IOException e) { LOG.error("Failed restoring position", e); } } } } public boolean currentRowMatchesEntry(Object... entryValues) throws IOException { return currentRowMatchesEntryImpl(toRowValues(entryValues), _columnMatcher); } public EntryIterableBuilder newEntryIterable(Object... entryValues) { return new EntryIterableBuilder(this, entryValues); } public Iterator entryIterator(EntryIterableBuilder iterBuilder) { return new EntryIterator(iterBuilder.getColumnNames(), toRowValues(iterBuilder.getEntryValues()), iterBuilder.getColumnMatcher()); } @Override protected IndexDirHandler getDirHandler(boolean moveForward) { return (moveForward ? _forwardDirHandler : _reverseDirHandler); } @Override protected boolean isUpToDate() { return(super.isUpToDate() && _entryCursor.isUpToDate()); } @Override protected void reset(boolean moveForward) { _entryCursor.reset(moveForward); super.reset(moveForward); } @Override protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos) throws IOException { if(!(curPos instanceof IndexPosition) || !(prevPos instanceof IndexPosition)) { throw new IllegalArgumentException( "Restored positions must be index positions"); } _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(), ((IndexPosition)prevPos).getEntry()); super.restorePositionImpl(curPos, prevPos); } @Override protected boolean findAnotherRowImpl( ColumnImpl columnPattern, Object valuePattern, boolean moveForward, ColumnMatcher columnMatcher) throws IOException { if(!isAtBeginning(moveForward)) { // use the default table scan for finding rows mid-cursor return super.findAnotherRowImpl(columnPattern, valuePattern, moveForward, columnMatcher); } // searching for the first match Object[] rowValues = _entryCursor.getIndexData().constructIndexRow( columnPattern.getName(), valuePattern); if(rowValues == null) { // bummer, use the default table scan return super.findAnotherRowImpl(columnPattern, valuePattern, moveForward, columnMatcher); } // sweet, we can use our index if(!findPotentialRow(rowValues, true)) { return false; } // either we found a row with the given value, or none exist in the // table return currentRowMatchesImpl(columnPattern, valuePattern, columnMatcher); } /** * Moves to the first row (as defined by the cursor) where the index entries * match the given values. Caller manages save/restore on failure. * * @param rowValues the column values built from the index column values * @param requireMatch whether or not an exact match is found * @return {@code true} if a valid row was found with the given values, * {@code false} if no row was found */ protected boolean findFirstRowByEntryImpl(Object[] rowValues, boolean requireMatch, ColumnMatcher columnMatcher) throws IOException { if(!findPotentialRow(rowValues, requireMatch)) { return false; } else if(!requireMatch) { // nothing more to do, we have moved to the closest row return true; } return currentRowMatchesEntryImpl(rowValues, columnMatcher); } @Override protected boolean findAnotherRowImpl(Map rowPattern, boolean moveForward, ColumnMatcher columnMatcher) throws IOException { if(!isAtBeginning(moveForward)) { // use the default table scan for finding rows mid-cursor return super.findAnotherRowImpl(rowPattern, moveForward, columnMatcher); } // searching for the first match IndexData indexData = _entryCursor.getIndexData(); Object[] rowValues = indexData.constructIndexRow(rowPattern); if(rowValues == null) { // bummer, use the default table scan return super.findAnotherRowImpl(rowPattern, moveForward, columnMatcher); } // sweet, we can use our index if(!findPotentialRow(rowValues, true)) { // at end of index, no potential matches return false; } // find actual matching row Map indexRowPattern = null; if(rowPattern.size() == indexData.getColumns().size()) { // the rowPattern matches our index columns exactly, so we can // streamline our testing below indexRowPattern = rowPattern; } else { // the rowPattern has more columns than just the index, so we need to // do more work when testing below Map tmpRowPattern = new LinkedHashMap(); indexRowPattern = tmpRowPattern; for(IndexData.ColumnDescriptor idxCol : indexData.getColumns()) { tmpRowPattern.put(idxCol.getName(), rowValues[idxCol.getColumnIndex()]); } } // there may be multiple columns which fit the pattern subset used by // the index, so we need to keep checking until our index values no // longer match do { if(!currentRowMatchesImpl(indexRowPattern, columnMatcher)) { // there are no more rows which could possibly match break; } // note, if rowPattern == indexRowPattern, no need to do an extra // comparison with the current row if((rowPattern == indexRowPattern) || currentRowMatchesImpl(rowPattern, columnMatcher)) { // found it! return true; } } while(moveToAnotherRow(moveForward)); // none of the potential rows matched return false; } private boolean currentRowMatchesEntryImpl(Object[] rowValues, ColumnMatcher columnMatcher) throws IOException { if(_indexEntryPattern == null) { // init our set of index column names _indexEntryPattern = new HashSet(); for(IndexData.ColumnDescriptor col : getIndex().getColumns()) { _indexEntryPattern.add(col.getName()); } } // check the next row to see if it actually matches Row row = getCurrentRow(_indexEntryPattern); for(IndexData.ColumnDescriptor col : getIndex().getColumns()) { String columnName = col.getName(); Object patValue = rowValues[col.getColumnIndex()]; Object rowValue = row.get(columnName); if(!columnMatcher.matches(getTable(), columnName, patValue, rowValue)) { return false; } } return true; } private boolean findPotentialRow(Object[] rowValues, boolean requireMatch) throws IOException { _entryCursor.beforeEntry(rowValues); IndexData.Entry startEntry = _entryCursor.getNextEntry(); if(requireMatch && !startEntry.getRowId().isValid()) { // at end of index, no potential matches return false; } // move to position and check it out restorePosition(new IndexPosition(startEntry)); return true; } private Object[] toRowValues(Object[] entryValues) { return _entryCursor.getIndexData().constructIndexRowFromEntry(entryValues); } @Override protected PositionImpl findAnotherPosition( RowState rowState, PositionImpl curPos, boolean moveForward) throws IOException { IndexDirHandler handler = getDirHandler(moveForward); IndexPosition endPos = (IndexPosition)handler.getEndPosition(); IndexData.Entry entry = handler.getAnotherEntry(); return ((!entry.equals(endPos.getEntry())) ? new IndexPosition(entry) : endPos); } @Override protected ColumnMatcher getDefaultColumnMatcher() { if(getIndex().isUnique()) { // text indexes are case-insensitive, therefore we should always use a // case-insensitive matcher for unique indexes. return CaseInsensitiveColumnMatcher.INSTANCE; } return SimpleColumnMatcher.INSTANCE; } /** * Handles moving the table index cursor in a given direction. Separates * cursor logic from value storage. */ private abstract class IndexDirHandler extends DirHandler { public abstract IndexData.Entry getAnotherEntry() throws IOException; } /** * Handles moving the table index cursor forward. */ private final class ForwardIndexDirHandler extends IndexDirHandler { @Override public PositionImpl getBeginningPosition() { return getFirstPosition(); } @Override public PositionImpl getEndPosition() { return getLastPosition(); } @Override public IndexData.Entry getAnotherEntry() throws IOException { return _entryCursor.getNextEntry(); } } /** * Handles moving the table index cursor backward. */ private final class ReverseIndexDirHandler extends IndexDirHandler { @Override public PositionImpl getBeginningPosition() { return getLastPosition(); } @Override public PositionImpl getEndPosition() { return getFirstPosition(); } @Override public IndexData.Entry getAnotherEntry() throws IOException { return _entryCursor.getPreviousEntry(); } } /** * Value object which maintains the current position of an IndexCursor. */ private static final class IndexPosition extends PositionImpl { private final IndexData.Entry _entry; private IndexPosition(IndexData.Entry entry) { _entry = entry; } @Override public RowIdImpl getRowId() { return getEntry().getRowId(); } public IndexData.Entry getEntry() { return _entry; } @Override protected boolean equalsImpl(Object o) { return getEntry().equals(((IndexPosition)o).getEntry()); } @Override public String toString() { return "Entry = " + getEntry(); } } /** * Row iterator (by matching entry) for this cursor, modifiable. */ private final class EntryIterator extends BaseIterator { private final Object[] _rowValues; private EntryIterator(Collection columnNames, Object[] rowValues, ColumnMatcher columnMatcher) { super(columnNames, false, MOVE_FORWARD, columnMatcher); _rowValues = rowValues; try { _hasNext = findFirstRowByEntryImpl(rowValues, true, _columnMatcher); _validRow = _hasNext; } catch(IOException e) { throw new RuntimeIOException(e); } } @Override protected boolean findNext() throws IOException { return (moveToNextRow() && currentRowMatchesEntryImpl(_rowValues, _colMatcher)); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy