You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IndexCursorImpl.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. /*
  2. Copyright (c) 2011 James Ahlborn
  3. This library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public
  5. License as published by the Free Software Foundation; either
  6. version 2.1 of the License, or (at your option) any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public
  12. License along with this library; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  14. USA
  15. */
  16. package com.healthmarketscience.jackcess.impl;
  17. import java.io.IOException;
  18. import java.util.Collection;
  19. import java.util.HashSet;
  20. import java.util.Iterator;
  21. import java.util.LinkedHashMap;
  22. import java.util.Map;
  23. import java.util.Set;
  24. import com.healthmarketscience.jackcess.Index;
  25. import com.healthmarketscience.jackcess.IndexCursor;
  26. import com.healthmarketscience.jackcess.Row;
  27. import com.healthmarketscience.jackcess.RuntimeIOException;
  28. import com.healthmarketscience.jackcess.impl.TableImpl.RowState;
  29. import com.healthmarketscience.jackcess.util.CaseInsensitiveColumnMatcher;
  30. import com.healthmarketscience.jackcess.util.ColumnMatcher;
  31. import com.healthmarketscience.jackcess.util.EntryIterableBuilder;
  32. import com.healthmarketscience.jackcess.util.SimpleColumnMatcher;
  33. import org.apache.commons.logging.Log;
  34. import org.apache.commons.logging.LogFactory;
  35. /**
  36. * Cursor backed by an index with extended traversal options.
  37. *
  38. * @author James Ahlborn
  39. */
  40. public class IndexCursorImpl extends CursorImpl implements IndexCursor
  41. {
  42. private static final Log LOG = LogFactory.getLog(IndexCursorImpl.class);
  43. /** IndexDirHandler for forward traversal */
  44. private final IndexDirHandler _forwardDirHandler =
  45. new ForwardIndexDirHandler();
  46. /** IndexDirHandler for backward traversal */
  47. private final IndexDirHandler _reverseDirHandler =
  48. new ReverseIndexDirHandler();
  49. /** logical index which this cursor is using */
  50. private final IndexImpl _index;
  51. /** Cursor over the entries of the relevant index */
  52. private final IndexData.EntryCursor _entryCursor;
  53. /** column names for the index entry columns */
  54. private Set<String> _indexEntryPattern;
  55. private IndexCursorImpl(TableImpl table, IndexImpl index,
  56. IndexData.EntryCursor entryCursor)
  57. throws IOException
  58. {
  59. super(new IdImpl(table, index), table,
  60. new IndexPosition(entryCursor.getFirstEntry()),
  61. new IndexPosition(entryCursor.getLastEntry()));
  62. _index = index;
  63. _index.initialize();
  64. _entryCursor = entryCursor;
  65. }
  66. /**
  67. * Creates an indexed cursor for the given table, narrowed to the given
  68. * range.
  69. * <p>
  70. * Note, index based table traversal may not include all rows, as certain
  71. * types of indexes do not include all entries (namely, some indexes ignore
  72. * null entries, see {@link Index#shouldIgnoreNulls}).
  73. *
  74. * @param table the table over which this cursor will traverse
  75. * @param index index for the table which will define traversal order as
  76. * well as enhance certain lookups
  77. * @param startRow the first row of data for the cursor, or {@code null} for
  78. * the first entry
  79. * @param startInclusive whether or not startRow is inclusive or exclusive
  80. * @param endRow the last row of data for the cursor, or {@code null} for
  81. * the last entry
  82. * @param endInclusive whether or not endRow is inclusive or exclusive
  83. */
  84. public static IndexCursorImpl createCursor(TableImpl table, IndexImpl index,
  85. Object[] startRow,
  86. boolean startInclusive,
  87. Object[] endRow,
  88. boolean endInclusive)
  89. throws IOException
  90. {
  91. if(table != index.getTable()) {
  92. throw new IllegalArgumentException(
  93. "Given index is not for given table: " + index + ", " + table);
  94. }
  95. if(!table.getFormat().INDEXES_SUPPORTED) {
  96. throw new IllegalArgumentException(
  97. "JetFormat " + table.getFormat() +
  98. " does not currently support index lookups");
  99. }
  100. if(index.getIndexData().getUnsupportedReason() != null) {
  101. throw new IllegalArgumentException(
  102. "Given index " + index +
  103. " is not usable for indexed lookups due to " +
  104. index.getIndexData().getUnsupportedReason());
  105. }
  106. IndexCursorImpl cursor = new IndexCursorImpl(
  107. table, index, index.cursor(startRow, startInclusive,
  108. endRow, endInclusive));
  109. // init the column matcher appropriately for the index type
  110. cursor.setColumnMatcher(null);
  111. return cursor;
  112. }
  113. private Set<String> getIndexEntryPattern()
  114. {
  115. if(_indexEntryPattern == null) {
  116. // init our set of index column names
  117. _indexEntryPattern = new HashSet<String>();
  118. for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
  119. _indexEntryPattern.add(col.getName());
  120. }
  121. }
  122. return _indexEntryPattern;
  123. }
  124. public IndexImpl getIndex() {
  125. return _index;
  126. }
  127. public Row findRowByEntry(Object... entryValues)
  128. throws IOException
  129. {
  130. if(findFirstRowByEntry(entryValues)) {
  131. return getCurrentRow();
  132. }
  133. return null;
  134. }
  135. public boolean findFirstRowByEntry(Object... entryValues)
  136. throws IOException
  137. {
  138. PositionImpl curPos = _curPos;
  139. PositionImpl prevPos = _prevPos;
  140. boolean found = false;
  141. try {
  142. found = findFirstRowByEntryImpl(toRowValues(entryValues), true,
  143. _columnMatcher);
  144. return found;
  145. } finally {
  146. if(!found) {
  147. try {
  148. restorePosition(curPos, prevPos);
  149. } catch(IOException e) {
  150. LOG.error("Failed restoring position", e);
  151. }
  152. }
  153. }
  154. }
  155. public void findClosestRowByEntry(Object... entryValues)
  156. throws IOException
  157. {
  158. PositionImpl curPos = _curPos;
  159. PositionImpl prevPos = _prevPos;
  160. boolean found = false;
  161. try {
  162. findFirstRowByEntryImpl(toRowValues(entryValues), false,
  163. _columnMatcher);
  164. found = true;
  165. } finally {
  166. if(!found) {
  167. try {
  168. restorePosition(curPos, prevPos);
  169. } catch(IOException e) {
  170. LOG.error("Failed restoring position", e);
  171. }
  172. }
  173. }
  174. }
  175. public boolean currentRowMatchesEntry(Object... entryValues)
  176. throws IOException
  177. {
  178. return currentRowMatchesEntryImpl(toRowValues(entryValues), _columnMatcher);
  179. }
  180. public EntryIterableBuilder newEntryIterable(Object... entryValues) {
  181. return new EntryIterableBuilder(this, entryValues);
  182. }
  183. public Iterator<Row> entryIterator(EntryIterableBuilder iterBuilder) {
  184. return new EntryIterator(iterBuilder.getColumnNames(),
  185. toRowValues(iterBuilder.getEntryValues()),
  186. iterBuilder.getColumnMatcher());
  187. }
  188. @Override
  189. protected IndexDirHandler getDirHandler(boolean moveForward) {
  190. return (moveForward ? _forwardDirHandler : _reverseDirHandler);
  191. }
  192. @Override
  193. protected boolean isUpToDate() {
  194. return(super.isUpToDate() && _entryCursor.isUpToDate());
  195. }
  196. @Override
  197. protected void reset(boolean moveForward) {
  198. _entryCursor.reset(moveForward);
  199. super.reset(moveForward);
  200. }
  201. @Override
  202. protected void restorePositionImpl(PositionImpl curPos, PositionImpl prevPos)
  203. throws IOException
  204. {
  205. if(!(curPos instanceof IndexPosition) ||
  206. !(prevPos instanceof IndexPosition)) {
  207. throw new IllegalArgumentException(
  208. "Restored positions must be index positions");
  209. }
  210. _entryCursor.restorePosition(((IndexPosition)curPos).getEntry(),
  211. ((IndexPosition)prevPos).getEntry());
  212. super.restorePositionImpl(curPos, prevPos);
  213. }
  214. @Override
  215. protected PositionImpl getRowPosition(RowIdImpl rowId) throws IOException
  216. {
  217. // we need to get the index entry which corresponds with this row
  218. Row row = getTable().getRow(getRowState(), rowId, getIndexEntryPattern());
  219. _entryCursor.beforeEntry(getTable().asRow(row));
  220. return new IndexPosition(_entryCursor.getNextEntry());
  221. }
  222. @Override
  223. protected boolean findAnotherRowImpl(
  224. ColumnImpl columnPattern, Object valuePattern, boolean moveForward,
  225. ColumnMatcher columnMatcher)
  226. throws IOException
  227. {
  228. if(!isAtBeginning(moveForward)) {
  229. // use the default table scan for finding rows mid-cursor
  230. return super.findAnotherRowImpl(columnPattern, valuePattern, moveForward,
  231. columnMatcher);
  232. }
  233. // searching for the first match
  234. Object[] rowValues = _entryCursor.getIndexData().constructIndexRow(
  235. columnPattern.getName(), valuePattern);
  236. if(rowValues == null) {
  237. // bummer, use the default table scan
  238. return super.findAnotherRowImpl(columnPattern, valuePattern, moveForward,
  239. columnMatcher);
  240. }
  241. // sweet, we can use our index
  242. if(!findPotentialRow(rowValues, true)) {
  243. return false;
  244. }
  245. // either we found a row with the given value, or none exist in the
  246. // table
  247. return currentRowMatchesImpl(columnPattern, valuePattern, columnMatcher);
  248. }
  249. /**
  250. * Moves to the first row (as defined by the cursor) where the index entries
  251. * match the given values. Caller manages save/restore on failure.
  252. *
  253. * @param rowValues the column values built from the index column values
  254. * @param requireMatch whether or not an exact match is found
  255. * @return {@code true} if a valid row was found with the given values,
  256. * {@code false} if no row was found
  257. */
  258. protected boolean findFirstRowByEntryImpl(Object[] rowValues,
  259. boolean requireMatch,
  260. ColumnMatcher columnMatcher)
  261. throws IOException
  262. {
  263. if(!findPotentialRow(rowValues, requireMatch)) {
  264. return false;
  265. } else if(!requireMatch) {
  266. // nothing more to do, we have moved to the closest row
  267. return true;
  268. }
  269. return currentRowMatchesEntryImpl(rowValues, columnMatcher);
  270. }
  271. @Override
  272. protected boolean findAnotherRowImpl(Map<String,?> rowPattern,
  273. boolean moveForward,
  274. ColumnMatcher columnMatcher)
  275. throws IOException
  276. {
  277. if(!isAtBeginning(moveForward)) {
  278. // use the default table scan for finding rows mid-cursor
  279. return super.findAnotherRowImpl(rowPattern, moveForward, columnMatcher);
  280. }
  281. // searching for the first match
  282. IndexData indexData = _entryCursor.getIndexData();
  283. Object[] rowValues = indexData.constructIndexRow(rowPattern);
  284. if(rowValues == null) {
  285. // bummer, use the default table scan
  286. return super.findAnotherRowImpl(rowPattern, moveForward, columnMatcher);
  287. }
  288. // sweet, we can use our index
  289. if(!findPotentialRow(rowValues, true)) {
  290. // at end of index, no potential matches
  291. return false;
  292. }
  293. // find actual matching row
  294. Map<String,?> indexRowPattern = null;
  295. if(rowPattern.size() == indexData.getColumns().size()) {
  296. // the rowPattern matches our index columns exactly, so we can
  297. // streamline our testing below
  298. indexRowPattern = rowPattern;
  299. } else {
  300. // the rowPattern has more columns than just the index, so we need to
  301. // do more work when testing below
  302. Map<String,Object> tmpRowPattern = new LinkedHashMap<String,Object>();
  303. indexRowPattern = tmpRowPattern;
  304. for(IndexData.ColumnDescriptor idxCol : indexData.getColumns()) {
  305. tmpRowPattern.put(idxCol.getName(), rowValues[idxCol.getColumnIndex()]);
  306. }
  307. }
  308. // there may be multiple columns which fit the pattern subset used by
  309. // the index, so we need to keep checking until our index values no
  310. // longer match
  311. do {
  312. if(!currentRowMatchesImpl(indexRowPattern, columnMatcher)) {
  313. // there are no more rows which could possibly match
  314. break;
  315. }
  316. // note, if rowPattern == indexRowPattern, no need to do an extra
  317. // comparison with the current row
  318. if((rowPattern == indexRowPattern) ||
  319. currentRowMatchesImpl(rowPattern, columnMatcher)) {
  320. // found it!
  321. return true;
  322. }
  323. } while(moveToAnotherRow(moveForward));
  324. // none of the potential rows matched
  325. return false;
  326. }
  327. private boolean currentRowMatchesEntryImpl(Object[] rowValues,
  328. ColumnMatcher columnMatcher)
  329. throws IOException
  330. {
  331. // check the next row to see if it actually matches
  332. Row row = getCurrentRow(getIndexEntryPattern());
  333. for(IndexData.ColumnDescriptor col : getIndex().getColumns()) {
  334. String columnName = col.getName();
  335. Object patValue = rowValues[col.getColumnIndex()];
  336. Object rowValue = row.get(columnName);
  337. if(!columnMatcher.matches(getTable(), columnName, patValue, rowValue)) {
  338. return false;
  339. }
  340. }
  341. return true;
  342. }
  343. private boolean findPotentialRow(Object[] rowValues, boolean requireMatch)
  344. throws IOException
  345. {
  346. _entryCursor.beforeEntry(rowValues);
  347. IndexData.Entry startEntry = _entryCursor.getNextEntry();
  348. if(requireMatch && !startEntry.getRowId().isValid()) {
  349. // at end of index, no potential matches
  350. return false;
  351. }
  352. // move to position and check it out
  353. restorePosition(new IndexPosition(startEntry));
  354. return true;
  355. }
  356. private Object[] toRowValues(Object[] entryValues)
  357. {
  358. return _entryCursor.getIndexData().constructIndexRowFromEntry(entryValues);
  359. }
  360. @Override
  361. protected PositionImpl findAnotherPosition(
  362. RowState rowState, PositionImpl curPos, boolean moveForward)
  363. throws IOException
  364. {
  365. IndexDirHandler handler = getDirHandler(moveForward);
  366. IndexPosition endPos = (IndexPosition)handler.getEndPosition();
  367. IndexData.Entry entry = handler.getAnotherEntry();
  368. return ((!entry.equals(endPos.getEntry())) ?
  369. new IndexPosition(entry) : endPos);
  370. }
  371. @Override
  372. protected ColumnMatcher getDefaultColumnMatcher() {
  373. if(getIndex().isUnique()) {
  374. // text indexes are case-insensitive, therefore we should always use a
  375. // case-insensitive matcher for unique indexes.
  376. return CaseInsensitiveColumnMatcher.INSTANCE;
  377. }
  378. return SimpleColumnMatcher.INSTANCE;
  379. }
  380. /**
  381. * Handles moving the table index cursor in a given direction. Separates
  382. * cursor logic from value storage.
  383. */
  384. private abstract class IndexDirHandler extends DirHandler {
  385. public abstract IndexData.Entry getAnotherEntry()
  386. throws IOException;
  387. }
  388. /**
  389. * Handles moving the table index cursor forward.
  390. */
  391. private final class ForwardIndexDirHandler extends IndexDirHandler {
  392. @Override
  393. public PositionImpl getBeginningPosition() {
  394. return getFirstPosition();
  395. }
  396. @Override
  397. public PositionImpl getEndPosition() {
  398. return getLastPosition();
  399. }
  400. @Override
  401. public IndexData.Entry getAnotherEntry() throws IOException {
  402. return _entryCursor.getNextEntry();
  403. }
  404. }
  405. /**
  406. * Handles moving the table index cursor backward.
  407. */
  408. private final class ReverseIndexDirHandler extends IndexDirHandler {
  409. @Override
  410. public PositionImpl getBeginningPosition() {
  411. return getLastPosition();
  412. }
  413. @Override
  414. public PositionImpl getEndPosition() {
  415. return getFirstPosition();
  416. }
  417. @Override
  418. public IndexData.Entry getAnotherEntry() throws IOException {
  419. return _entryCursor.getPreviousEntry();
  420. }
  421. }
  422. /**
  423. * Value object which maintains the current position of an IndexCursor.
  424. */
  425. private static final class IndexPosition extends PositionImpl
  426. {
  427. private final IndexData.Entry _entry;
  428. private IndexPosition(IndexData.Entry entry) {
  429. _entry = entry;
  430. }
  431. @Override
  432. public RowIdImpl getRowId() {
  433. return getEntry().getRowId();
  434. }
  435. public IndexData.Entry getEntry() {
  436. return _entry;
  437. }
  438. @Override
  439. protected boolean equalsImpl(Object o) {
  440. return getEntry().equals(((IndexPosition)o).getEntry());
  441. }
  442. @Override
  443. public String toString() {
  444. return "Entry = " + getEntry();
  445. }
  446. }
  447. /**
  448. * Row iterator (by matching entry) for this cursor, modifiable.
  449. */
  450. private final class EntryIterator extends BaseIterator
  451. {
  452. private final Object[] _rowValues;
  453. private EntryIterator(Collection<String> columnNames, Object[] rowValues,
  454. ColumnMatcher columnMatcher)
  455. {
  456. super(columnNames, false, MOVE_FORWARD, columnMatcher);
  457. _rowValues = rowValues;
  458. try {
  459. _hasNext = findFirstRowByEntryImpl(rowValues, true, _columnMatcher);
  460. _validRow = _hasNext;
  461. } catch(IOException e) {
  462. throw new RuntimeIOException(e);
  463. }
  464. }
  465. @Override
  466. protected boolean findNext() throws IOException {
  467. return (moveToNextRow() &&
  468. currentRowMatchesEntryImpl(_rowValues, _colMatcher));
  469. }
  470. }
  471. }