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.

IndexData.java 69KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379
  1. /*
  2. Copyright (c) 2005 Health Market Science, Inc.
  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. You can contact Health Market Science at info@healthmarketscience.com
  16. or at the following address:
  17. Health Market Science
  18. 2700 Horizon Drive
  19. Suite 200
  20. King of Prussia, PA 19406
  21. */
  22. package com.healthmarketscience.jackcess;
  23. import java.io.IOException;
  24. import java.nio.ByteBuffer;
  25. import java.nio.ByteOrder;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.Collections;
  29. import java.util.Comparator;
  30. import java.util.List;
  31. import java.util.Map;
  32. import org.apache.commons.logging.Log;
  33. import org.apache.commons.logging.LogFactory;
  34. import static com.healthmarketscience.jackcess.IndexCodes.*;
  35. import static com.healthmarketscience.jackcess.ByteUtil.ByteStream;
  36. /**
  37. * Access table index data. This is the actual data which backs a logical
  38. * Index, where one or more logical indexes can be backed by the same index
  39. * data.
  40. *
  41. * @author Tim McCune
  42. */
  43. public abstract class IndexData {
  44. protected static final Log LOG = LogFactory.getLog(Index.class);
  45. /** special entry which is less than any other entry */
  46. public static final Entry FIRST_ENTRY =
  47. createSpecialEntry(RowId.FIRST_ROW_ID);
  48. /** special entry which is greater than any other entry */
  49. public static final Entry LAST_ENTRY =
  50. createSpecialEntry(RowId.LAST_ROW_ID);
  51. /** special object which will always be greater than any other value, when
  52. searching for an index entry range in a multi-value index */
  53. public static final Object MAX_VALUE = new Object();
  54. /** special object which will always be greater than any other value, when
  55. searching for an index entry range in a multi-value index */
  56. public static final Object MIN_VALUE = new Object();
  57. protected static final int INVALID_INDEX_PAGE_NUMBER = 0;
  58. /** Max number of columns in an index */
  59. static final int MAX_COLUMNS = 10;
  60. protected static final byte[] EMPTY_PREFIX = new byte[0];
  61. private static final short COLUMN_UNUSED = -1;
  62. static final byte ASCENDING_COLUMN_FLAG = (byte)0x01;
  63. static final byte UNIQUE_INDEX_FLAG = (byte)0x01;
  64. static final byte IGNORE_NULLS_INDEX_FLAG = (byte)0x02;
  65. static final byte SPECIAL_INDEX_FLAG = (byte)0x08; // set on MSysACEs and MSysAccessObjects indexes, purpose unknown
  66. static final byte UNKNOWN_INDEX_FLAG = (byte)0x80; // always seems to be set on indexes in access 2000+
  67. private static final int MAGIC_INDEX_NUMBER = 1923;
  68. private static final ByteOrder ENTRY_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
  69. /** type attributes for Entries which simplify comparisons */
  70. public enum EntryType {
  71. /** comparable type indicating this Entry should always compare less than
  72. valid RowIds */
  73. ALWAYS_FIRST,
  74. /** comparable type indicating this Entry should always compare less than
  75. other valid entries with equal entryBytes */
  76. FIRST_VALID,
  77. /** comparable type indicating this RowId should always compare
  78. normally */
  79. NORMAL,
  80. /** comparable type indicating this Entry should always compare greater
  81. than other valid entries with equal entryBytes */
  82. LAST_VALID,
  83. /** comparable type indicating this Entry should always compare greater
  84. than valid RowIds */
  85. ALWAYS_LAST;
  86. }
  87. static final Comparator<byte[]> BYTE_CODE_COMPARATOR =
  88. new Comparator<byte[]>() {
  89. public int compare(byte[] left, byte[] right) {
  90. if(left == right) {
  91. return 0;
  92. }
  93. if(left == null) {
  94. return -1;
  95. }
  96. if(right == null) {
  97. return 1;
  98. }
  99. int len = Math.min(left.length, right.length);
  100. int pos = 0;
  101. while((pos < len) && (left[pos] == right[pos])) {
  102. ++pos;
  103. }
  104. if(pos < len) {
  105. return ((ByteUtil.asUnsignedByte(left[pos]) <
  106. ByteUtil.asUnsignedByte(right[pos])) ? -1 : 1);
  107. }
  108. return ((left.length < right.length) ? -1 :
  109. ((left.length > right.length) ? 1 : 0));
  110. }
  111. };
  112. /** owning table */
  113. private final Table _table;
  114. /** 0-based index data number */
  115. private final int _number;
  116. /** Page number of the root index data */
  117. private int _rootPageNumber;
  118. /** offset within the tableDefinition buffer of the uniqueEntryCount for
  119. this index */
  120. private final int _uniqueEntryCountOffset;
  121. /** The number of unique entries which have been added to this index. note,
  122. however, that it is never decremented, only incremented (as observed in
  123. Access). */
  124. private int _uniqueEntryCount;
  125. /** List of columns and flags */
  126. private final List<ColumnDescriptor> _columns =
  127. new ArrayList<ColumnDescriptor>();
  128. /** the logical indexes which this index data backs */
  129. private final List<Index> _indexes = new ArrayList<Index>();
  130. /** flags for this index */
  131. private byte _indexFlags;
  132. /** Usage map of pages that this index owns */
  133. private UsageMap _ownedPages;
  134. /** <code>true</code> if the index entries have been initialized,
  135. <code>false</code> otherwise */
  136. private boolean _initialized;
  137. /** modification count for the table, keeps cursors up-to-date */
  138. private int _modCount;
  139. /** temp buffer used to read/write the index pages */
  140. private final TempBufferHolder _indexBufferH =
  141. TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
  142. /** temp buffer used to create index entries */
  143. private ByteStream _entryBuffer;
  144. /** max size for all the entries written to a given index data page */
  145. private final int _maxPageEntrySize;
  146. /** whether or not this index data is backing a primary key logical index */
  147. private boolean _primaryKey;
  148. /** FIXME, for SimpleIndex, we can't write multi-page indexes or indexes using the entry compression scheme */
  149. private boolean _readOnly;
  150. protected IndexData(Table table, int number, int uniqueEntryCount,
  151. int uniqueEntryCountOffset)
  152. {
  153. _table = table;
  154. _number = number;
  155. _uniqueEntryCount = uniqueEntryCount;
  156. _uniqueEntryCountOffset = uniqueEntryCountOffset;
  157. _maxPageEntrySize = calcMaxPageEntrySize(_table.getFormat());
  158. }
  159. /**
  160. * Creates an IndexData appropriate for the given table, using information
  161. * from the given table definition buffer.
  162. */
  163. public static IndexData create(Table table, ByteBuffer tableBuffer,
  164. int number, JetFormat format)
  165. throws IOException
  166. {
  167. int uniqueEntryCountOffset =
  168. (format.OFFSET_INDEX_DEF_BLOCK +
  169. (number * format.SIZE_INDEX_DEFINITION) + 4);
  170. int uniqueEntryCount = tableBuffer.getInt(uniqueEntryCountOffset);
  171. return(table.doUseBigIndex() ?
  172. new BigIndexData(table, number, uniqueEntryCount,
  173. uniqueEntryCountOffset) :
  174. new SimpleIndexData(table, number, uniqueEntryCount,
  175. uniqueEntryCountOffset));
  176. }
  177. public Table getTable() {
  178. return _table;
  179. }
  180. public JetFormat getFormat() {
  181. return getTable().getFormat();
  182. }
  183. public PageChannel getPageChannel() {
  184. return getTable().getPageChannel();
  185. }
  186. /**
  187. * @return the "main" logical index which is backed by this data.
  188. */
  189. public Index getPrimaryIndex() {
  190. return _indexes.get(0);
  191. }
  192. /**
  193. * @return All of the Indexes backed by this data (unmodifiable List)
  194. */
  195. public List<Index> getIndexes() {
  196. return Collections.unmodifiableList(_indexes);
  197. }
  198. /**
  199. * Adds a logical index which this data is backing.
  200. */
  201. void addIndex(Index index) {
  202. // we keep foreign key indexes at the back of the list. this way the
  203. // primary index will be a non-foreign key index (if any)
  204. if(index.isForeignKey()) {
  205. _indexes.add(index);
  206. } else {
  207. int pos = _indexes.size();
  208. while(pos > 0) {
  209. if(!_indexes.get(pos - 1).isForeignKey()) {
  210. break;
  211. }
  212. --pos;
  213. }
  214. _indexes.add(pos, index);
  215. // also, keep track of whether or not this is a primary key index
  216. _primaryKey |= index.isPrimaryKey();
  217. }
  218. }
  219. public byte getIndexFlags() {
  220. return _indexFlags;
  221. }
  222. public int getIndexDataNumber() {
  223. return _number;
  224. }
  225. public int getUniqueEntryCount() {
  226. return _uniqueEntryCount;
  227. }
  228. public int getUniqueEntryCountOffset() {
  229. return _uniqueEntryCountOffset;
  230. }
  231. protected boolean isBackingPrimaryKey() {
  232. return _primaryKey;
  233. }
  234. /**
  235. * Whether or not {@code null} values are actually recorded in the index.
  236. */
  237. public boolean shouldIgnoreNulls() {
  238. return((_indexFlags & IGNORE_NULLS_INDEX_FLAG) != 0);
  239. }
  240. /**
  241. * Whether or not index entries must be unique.
  242. * <p>
  243. * Some notes about uniqueness:
  244. * <ul>
  245. * <li>Access does not seem to consider multiple {@code null} entries
  246. * invalid for a unique index</li>
  247. * <li>text indexes collapse case, and Access seems to compare <b>only</b>
  248. * the index entry bytes, therefore two strings which differ only in
  249. * case <i>will violate</i> the unique constraint</li>
  250. * </ul>
  251. */
  252. public boolean isUnique() {
  253. return(isBackingPrimaryKey() || ((_indexFlags & UNIQUE_INDEX_FLAG) != 0));
  254. }
  255. /**
  256. * Returns the Columns for this index (unmodifiable)
  257. */
  258. public List<ColumnDescriptor> getColumns() {
  259. return Collections.unmodifiableList(_columns);
  260. }
  261. /**
  262. * Whether or not the complete index state has been read.
  263. */
  264. public boolean isInitialized() {
  265. return _initialized;
  266. }
  267. protected int getRootPageNumber() {
  268. return _rootPageNumber;
  269. }
  270. protected void setReadOnly() {
  271. _readOnly = true;
  272. }
  273. protected boolean isReadOnly() {
  274. return _readOnly;
  275. }
  276. protected int getMaxPageEntrySize() {
  277. return _maxPageEntrySize;
  278. }
  279. /**
  280. * Returns the number of database pages owned by this index data.
  281. */
  282. public int getOwnedPageCount() {
  283. return _ownedPages.getPageCount();
  284. }
  285. void addOwnedPage(int pageNumber) throws IOException {
  286. _ownedPages.addPageNumber(pageNumber);
  287. }
  288. /**
  289. * Returns the number of index entries in the index. Only called by unit
  290. * tests.
  291. * <p>
  292. * Forces index initialization.
  293. */
  294. protected int getEntryCount()
  295. throws IOException
  296. {
  297. initialize();
  298. EntryCursor cursor = cursor();
  299. Entry endEntry = cursor.getLastEntry();
  300. int count = 0;
  301. while(!endEntry.equals(cursor.getNextEntry())) {
  302. ++count;
  303. }
  304. return count;
  305. }
  306. /**
  307. * Forces initialization of this index (actual parsing of index pages).
  308. * normally, the index will not be initialized until the entries are
  309. * actually needed.
  310. */
  311. public void initialize() throws IOException {
  312. if(!_initialized) {
  313. readIndexEntries();
  314. _initialized = true;
  315. }
  316. }
  317. /**
  318. * Writes the current index state to the database.
  319. * <p>
  320. * Forces index initialization.
  321. */
  322. public void update() throws IOException
  323. {
  324. // make sure we've parsed the entries
  325. initialize();
  326. if(_readOnly) {
  327. throw new UnsupportedOperationException(
  328. "FIXME cannot write indexes of this type yet, see Database javadoc for info on enabling large index support");
  329. }
  330. updateImpl();
  331. }
  332. /**
  333. * Read the rest of the index info from a tableBuffer
  334. * @param tableBuffer table definition buffer to read from initial info
  335. * @param availableColumns Columns that this index may use
  336. */
  337. public void read(ByteBuffer tableBuffer, List<Column> availableColumns)
  338. throws IOException
  339. {
  340. ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX); //Forward past Unknown
  341. for (int i = 0; i < MAX_COLUMNS; i++) {
  342. short columnNumber = tableBuffer.getShort();
  343. byte colFlags = tableBuffer.get();
  344. if (columnNumber != COLUMN_UNUSED) {
  345. // find the desired column by column number (which is not necessarily
  346. // the same as the column index)
  347. Column idxCol = null;
  348. for(Column col : availableColumns) {
  349. if(col.getColumnNumber() == columnNumber) {
  350. idxCol = col;
  351. break;
  352. }
  353. }
  354. if(idxCol == null) {
  355. throw new IOException("Could not find column with number "
  356. + columnNumber + " for index");
  357. }
  358. _columns.add(newColumnDescriptor(idxCol, colFlags));
  359. }
  360. }
  361. int umapRowNum = tableBuffer.get();
  362. int umapPageNum = ByteUtil.get3ByteInt(tableBuffer);
  363. _ownedPages = UsageMap.read(getTable().getDatabase(), umapPageNum,
  364. umapRowNum, false);
  365. _rootPageNumber = tableBuffer.getInt();
  366. ByteUtil.forward(tableBuffer, getFormat().SKIP_BEFORE_INDEX_FLAGS); //Forward past Unknown
  367. _indexFlags = tableBuffer.get();
  368. ByteUtil.forward(tableBuffer, getFormat().SKIP_AFTER_INDEX_FLAGS); //Forward past other stuff
  369. }
  370. /**
  371. * Writes the index row count definitions into a table definition buffer.
  372. * @param buffer Buffer to write to
  373. * @param indexes List of IndexBuilders to write definitions for
  374. */
  375. protected static void writeRowCountDefinitions(
  376. TableCreator creator, ByteBuffer buffer)
  377. {
  378. // index row counts (empty data)
  379. ByteUtil.forward(buffer, (creator.getIndexCount() *
  380. creator.getFormat().SIZE_INDEX_DEFINITION));
  381. }
  382. /**
  383. * Writes the index definitions into a table definition buffer.
  384. * @param buffer Buffer to write to
  385. * @param indexes List of IndexBuilders to write definitions for
  386. */
  387. protected static void writeDefinitions(
  388. TableCreator creator, ByteBuffer buffer)
  389. throws IOException
  390. {
  391. ByteBuffer rootPageBuffer = creator.getPageChannel().createPageBuffer();
  392. writeDataPage(rootPageBuffer, SimpleIndexData.NEW_ROOT_DATA_PAGE,
  393. creator.getTdefPageNumber(), creator.getFormat());
  394. for(IndexBuilder idx : creator.getIndexes()) {
  395. buffer.putInt(MAGIC_INDEX_NUMBER); // seemingly constant magic value
  396. // write column information (always MAX_COLUMNS entries)
  397. List<IndexBuilder.Column> idxColumns = idx.getColumns();
  398. for(int i = 0; i < MAX_COLUMNS; ++i) {
  399. short columnNumber = COLUMN_UNUSED;
  400. byte flags = 0;
  401. if(i < idxColumns.size()) {
  402. // determine column info
  403. IndexBuilder.Column idxCol = idxColumns.get(i);
  404. flags = idxCol.getFlags();
  405. // find actual table column number
  406. for(Column col : creator.getColumns()) {
  407. if(col.getName().equalsIgnoreCase(idxCol.getName())) {
  408. columnNumber = col.getColumnNumber();
  409. break;
  410. }
  411. }
  412. if(columnNumber == COLUMN_UNUSED) {
  413. // should never happen as this is validated before
  414. throw new IllegalArgumentException(
  415. "Column with name " + idxCol.getName() + " not found");
  416. }
  417. }
  418. buffer.putShort(columnNumber); // table column number
  419. buffer.put(flags); // column flags (e.g. ordering)
  420. }
  421. TableCreator.IndexState idxState = creator.getIndexState(idx);
  422. buffer.put(idxState.getUmapRowNumber()); // umap row
  423. ByteUtil.put3ByteInt(buffer, creator.getUmapPageNumber()); // umap page
  424. // write empty root index page
  425. creator.getPageChannel().writePage(rootPageBuffer,
  426. idxState.getRootPageNumber());
  427. buffer.putInt(idxState.getRootPageNumber());
  428. buffer.putInt(0); // unknown
  429. buffer.put(idx.getFlags()); // index flags (unique, etc.)
  430. ByteUtil.forward(buffer, 5); // unknown
  431. }
  432. }
  433. /**
  434. * Adds a row to this index
  435. * <p>
  436. * Forces index initialization.
  437. *
  438. * @param row Row to add
  439. * @param rowId rowId of the row to be added
  440. */
  441. public void addRow(Object[] row, RowId rowId)
  442. throws IOException
  443. {
  444. int nullCount = countNullValues(row);
  445. boolean isNullEntry = (nullCount == _columns.size());
  446. if(shouldIgnoreNulls() && isNullEntry) {
  447. // nothing to do
  448. return;
  449. }
  450. if(isBackingPrimaryKey() && (nullCount > 0)) {
  451. throw new IOException("Null value found in row " + Arrays.asList(row)
  452. + " for primary key index " + this);
  453. }
  454. // make sure we've parsed the entries
  455. initialize();
  456. Entry newEntry = new Entry(createEntryBytes(row), rowId);
  457. if(addEntry(newEntry, isNullEntry, row)) {
  458. ++_modCount;
  459. } else {
  460. LOG.warn("Added duplicate index entry " + newEntry + " for row: " +
  461. Arrays.asList(row));
  462. }
  463. }
  464. /**
  465. * Adds an entry to the correct index dataPage, maintaining the order.
  466. */
  467. private boolean addEntry(Entry newEntry, boolean isNullEntry, Object[] row)
  468. throws IOException
  469. {
  470. DataPage dataPage = findDataPage(newEntry);
  471. int idx = dataPage.findEntry(newEntry);
  472. if(idx < 0) {
  473. // this is a new entry
  474. idx = missingIndexToInsertionPoint(idx);
  475. Position newPos = new Position(dataPage, idx, newEntry, true);
  476. Position nextPos = getNextPosition(newPos);
  477. Position prevPos = getPreviousPosition(newPos);
  478. // determine if the addition of this entry would break the uniqueness
  479. // constraint. See isUnique() for some notes about uniqueness as
  480. // defined by Access.
  481. boolean isDupeEntry =
  482. (((nextPos != null) &&
  483. newEntry.equalsEntryBytes(nextPos.getEntry())) ||
  484. ((prevPos != null) &&
  485. newEntry.equalsEntryBytes(prevPos.getEntry())));
  486. if(isUnique() && !isNullEntry && isDupeEntry) {
  487. throw new IOException(
  488. "New row " + Arrays.asList(row) +
  489. " violates uniqueness constraint for index " + this);
  490. }
  491. if(!isDupeEntry) {
  492. ++_uniqueEntryCount;
  493. }
  494. dataPage.addEntry(idx, newEntry);
  495. return true;
  496. }
  497. return false;
  498. }
  499. /**
  500. * Removes a row from this index
  501. * <p>
  502. * Forces index initialization.
  503. *
  504. * @param row Row to remove
  505. * @param rowId rowId of the row to be removed
  506. */
  507. public void deleteRow(Object[] row, RowId rowId)
  508. throws IOException
  509. {
  510. int nullCount = countNullValues(row);
  511. if(shouldIgnoreNulls() && (nullCount == _columns.size())) {
  512. // nothing to do
  513. return;
  514. }
  515. // make sure we've parsed the entries
  516. initialize();
  517. Entry oldEntry = new Entry(createEntryBytes(row), rowId);
  518. if(removeEntry(oldEntry)) {
  519. ++_modCount;
  520. } else {
  521. LOG.warn("Failed removing index entry " + oldEntry + " for row: " +
  522. Arrays.asList(row));
  523. }
  524. }
  525. /**
  526. * Removes an entry from the relevant index dataPage, maintaining the order.
  527. * Will search by RowId if entry is not found (in case a partial entry was
  528. * provided).
  529. */
  530. private boolean removeEntry(Entry oldEntry)
  531. throws IOException
  532. {
  533. DataPage dataPage = findDataPage(oldEntry);
  534. int idx = dataPage.findEntry(oldEntry);
  535. boolean doRemove = false;
  536. if(idx < 0) {
  537. // the caller may have only read some of the row data, if this is the
  538. // case, just search for the page/row numbers
  539. // FIXME, we could force caller to get relevant values?
  540. EntryCursor cursor = cursor();
  541. Position tmpPos = null;
  542. Position endPos = cursor._lastPos;
  543. while(!endPos.equals(
  544. tmpPos = cursor.getAnotherPosition(Cursor.MOVE_FORWARD))) {
  545. if(tmpPos.getEntry().getRowId().equals(oldEntry.getRowId())) {
  546. dataPage = tmpPos.getDataPage();
  547. idx = tmpPos.getIndex();
  548. doRemove = true;
  549. break;
  550. }
  551. }
  552. } else {
  553. doRemove = true;
  554. }
  555. if(doRemove) {
  556. // found it!
  557. dataPage.removeEntry(idx);
  558. }
  559. return doRemove;
  560. }
  561. /**
  562. * Gets a new cursor for this index.
  563. * <p>
  564. * Forces index initialization.
  565. */
  566. public EntryCursor cursor()
  567. throws IOException
  568. {
  569. return cursor(null, true, null, true);
  570. }
  571. /**
  572. * Gets a new cursor for this index, narrowed to the range defined by the
  573. * given startRow and endRow.
  574. * <p>
  575. * Forces index initialization.
  576. *
  577. * @param startRow the first row of data for the cursor, or {@code null} for
  578. * the first entry
  579. * @param startInclusive whether or not startRow is inclusive or exclusive
  580. * @param endRow the last row of data for the cursor, or {@code null} for
  581. * the last entry
  582. * @param endInclusive whether or not endRow is inclusive or exclusive
  583. */
  584. public EntryCursor cursor(Object[] startRow,
  585. boolean startInclusive,
  586. Object[] endRow,
  587. boolean endInclusive)
  588. throws IOException
  589. {
  590. initialize();
  591. Entry startEntry = FIRST_ENTRY;
  592. byte[] startEntryBytes = null;
  593. if(startRow != null) {
  594. startEntryBytes = createEntryBytes(startRow);
  595. startEntry = new Entry(startEntryBytes,
  596. (startInclusive ?
  597. RowId.FIRST_ROW_ID : RowId.LAST_ROW_ID));
  598. }
  599. Entry endEntry = LAST_ENTRY;
  600. if(endRow != null) {
  601. // reuse startEntryBytes if startRow and endRow are same array. this is
  602. // common for "lookup" code
  603. byte[] endEntryBytes = ((startRow == endRow) ?
  604. startEntryBytes :
  605. createEntryBytes(endRow));
  606. endEntry = new Entry(endEntryBytes,
  607. (endInclusive ?
  608. RowId.LAST_ROW_ID : RowId.FIRST_ROW_ID));
  609. }
  610. return new EntryCursor(findEntryPosition(startEntry),
  611. findEntryPosition(endEntry));
  612. }
  613. private Position findEntryPosition(Entry entry)
  614. throws IOException
  615. {
  616. DataPage dataPage = findDataPage(entry);
  617. int idx = dataPage.findEntry(entry);
  618. boolean between = false;
  619. if(idx < 0) {
  620. // given entry was not found exactly. our current position is now
  621. // really between two indexes, but we cannot support that as an integer
  622. // value, so we set a flag instead
  623. idx = missingIndexToInsertionPoint(idx);
  624. between = true;
  625. }
  626. return new Position(dataPage, idx, entry, between);
  627. }
  628. private Position getNextPosition(Position curPos)
  629. throws IOException
  630. {
  631. // get the next index (between-ness is handled internally)
  632. int nextIdx = curPos.getNextIndex();
  633. Position nextPos = null;
  634. if(nextIdx < curPos.getDataPage().getEntries().size()) {
  635. nextPos = new Position(curPos.getDataPage(), nextIdx);
  636. } else {
  637. int nextPageNumber = curPos.getDataPage().getNextPageNumber();
  638. DataPage nextDataPage = null;
  639. while(nextPageNumber != INVALID_INDEX_PAGE_NUMBER) {
  640. DataPage dp = getDataPage(nextPageNumber);
  641. if(!dp.isEmpty()) {
  642. nextDataPage = dp;
  643. break;
  644. }
  645. nextPageNumber = dp.getNextPageNumber();
  646. }
  647. if(nextDataPage != null) {
  648. nextPos = new Position(nextDataPage, 0);
  649. }
  650. }
  651. return nextPos;
  652. }
  653. /**
  654. * Returns the Position before the given one, or {@code null} if none.
  655. */
  656. private Position getPreviousPosition(Position curPos)
  657. throws IOException
  658. {
  659. // get the previous index (between-ness is handled internally)
  660. int prevIdx = curPos.getPrevIndex();
  661. Position prevPos = null;
  662. if(prevIdx >= 0) {
  663. prevPos = new Position(curPos.getDataPage(), prevIdx);
  664. } else {
  665. int prevPageNumber = curPos.getDataPage().getPrevPageNumber();
  666. DataPage prevDataPage = null;
  667. while(prevPageNumber != INVALID_INDEX_PAGE_NUMBER) {
  668. DataPage dp = getDataPage(prevPageNumber);
  669. if(!dp.isEmpty()) {
  670. prevDataPage = dp;
  671. break;
  672. }
  673. prevPageNumber = dp.getPrevPageNumber();
  674. }
  675. if(prevDataPage != null) {
  676. prevPos = new Position(prevDataPage,
  677. (prevDataPage.getEntries().size() - 1));
  678. }
  679. }
  680. return prevPos;
  681. }
  682. /**
  683. * Returns the valid insertion point for an index indicating a missing
  684. * entry.
  685. */
  686. protected static int missingIndexToInsertionPoint(int idx) {
  687. return -(idx + 1);
  688. }
  689. /**
  690. * Constructs an array of values appropriate for this index from the given
  691. * column values, expected to match the columns for this index.
  692. * @return the appropriate sparse array of data
  693. * @throws IllegalArgumentException if the wrong number of values are
  694. * provided
  695. */
  696. public Object[] constructIndexRowFromEntry(Object... values)
  697. {
  698. if(values.length != _columns.size()) {
  699. throw new IllegalArgumentException(
  700. "Wrong number of column values given " + values.length +
  701. ", expected " + _columns.size());
  702. }
  703. int valIdx = 0;
  704. Object[] idxRow = new Object[getTable().getColumnCount()];
  705. for(ColumnDescriptor col : _columns) {
  706. idxRow[col.getColumnIndex()] = values[valIdx++];
  707. }
  708. return idxRow;
  709. }
  710. /**
  711. * Constructs an array of values appropriate for this index from the given
  712. * column value.
  713. * @return the appropriate sparse array of data or {@code null} if not all
  714. * columns for this index were provided
  715. */
  716. public Object[] constructIndexRow(String colName, Object value)
  717. {
  718. return constructIndexRow(Collections.singletonMap(colName, value));
  719. }
  720. /**
  721. * Constructs an array of values appropriate for this index from the given
  722. * column values.
  723. * @return the appropriate sparse array of data or {@code null} if not all
  724. * columns for this index were provided
  725. */
  726. public Object[] constructIndexRow(Map<String,?> row)
  727. {
  728. for(ColumnDescriptor col : _columns) {
  729. if(!row.containsKey(col.getName())) {
  730. return null;
  731. }
  732. }
  733. Object[] idxRow = new Object[getTable().getColumnCount()];
  734. for(ColumnDescriptor col : _columns) {
  735. idxRow[col.getColumnIndex()] = row.get(col.getName());
  736. }
  737. return idxRow;
  738. }
  739. @Override
  740. public String toString() {
  741. StringBuilder rtn = new StringBuilder();
  742. rtn.append("\n\tData number: ").append(_number);
  743. rtn.append("\n\tPage number: ").append(_rootPageNumber);
  744. rtn.append("\n\tIs Backing Primary Key: ").append(isBackingPrimaryKey());
  745. rtn.append("\n\tIs Unique: ").append(isUnique());
  746. rtn.append("\n\tIgnore Nulls: ").append(shouldIgnoreNulls());
  747. rtn.append("\n\tColumns: ").append(_columns);
  748. rtn.append("\n\tInitialized: ").append(_initialized);
  749. if(_initialized) {
  750. try {
  751. rtn.append("\n\tEntryCount: ").append(getEntryCount());
  752. } catch(IOException e) {
  753. throw new RuntimeException(e);
  754. }
  755. }
  756. return rtn.toString();
  757. }
  758. /**
  759. * Write the given index page out to a buffer
  760. */
  761. protected void writeDataPage(DataPage dataPage)
  762. throws IOException
  763. {
  764. if(dataPage.getCompressedEntrySize() > _maxPageEntrySize) {
  765. if(this instanceof SimpleIndexData) {
  766. throw new UnsupportedOperationException(
  767. "FIXME cannot write large index yet, see Database javadoc for info on enabling large index support");
  768. }
  769. throw new IllegalStateException("data page is too large");
  770. }
  771. ByteBuffer buffer = _indexBufferH.getPageBuffer(getPageChannel());
  772. writeDataPage(buffer, dataPage, getTable().getTableDefPageNumber(),
  773. getFormat());
  774. getPageChannel().writePage(buffer, dataPage.getPageNumber());
  775. }
  776. /**
  777. * Writes the data page info to the given buffer.
  778. */
  779. protected static void writeDataPage(ByteBuffer buffer, DataPage dataPage,
  780. int tdefPageNumber, JetFormat format)
  781. throws IOException
  782. {
  783. buffer.put(dataPage.isLeaf() ?
  784. PageTypes.INDEX_LEAF :
  785. PageTypes.INDEX_NODE ); //Page type
  786. buffer.put((byte) 0x01); //Unknown
  787. buffer.putShort((short) 0); //Free space
  788. buffer.putInt(tdefPageNumber);
  789. buffer.putInt(0); //Unknown
  790. buffer.putInt(dataPage.getPrevPageNumber()); //Prev page
  791. buffer.putInt(dataPage.getNextPageNumber()); //Next page
  792. buffer.putInt(dataPage.getChildTailPageNumber()); //ChildTail page
  793. byte[] entryPrefix = dataPage.getEntryPrefix();
  794. buffer.putShort((short) entryPrefix.length); // entry prefix byte count
  795. buffer.put((byte) 0); //Unknown
  796. byte[] entryMask = new byte[format.SIZE_INDEX_ENTRY_MASK];
  797. // first entry includes the prefix
  798. int totalSize = entryPrefix.length;
  799. for(Entry entry : dataPage.getEntries()) {
  800. totalSize += (entry.size() - entryPrefix.length);
  801. int idx = totalSize / 8;
  802. entryMask[idx] |= (1 << (totalSize % 8));
  803. }
  804. buffer.put(entryMask);
  805. // first entry includes the prefix
  806. buffer.put(entryPrefix);
  807. for(Entry entry : dataPage.getEntries()) {
  808. entry.write(buffer, entryPrefix);
  809. }
  810. // update free space
  811. buffer.putShort(2, (short) (format.PAGE_SIZE - buffer.position()));
  812. }
  813. /**
  814. * Reads an index page, populating the correct collection based on the page
  815. * type (node or leaf).
  816. */
  817. protected void readDataPage(DataPage dataPage)
  818. throws IOException
  819. {
  820. ByteBuffer buffer = _indexBufferH.getPageBuffer(getPageChannel());
  821. getPageChannel().readPage(buffer, dataPage.getPageNumber());
  822. boolean isLeaf = isLeafPage(buffer);
  823. dataPage.setLeaf(isLeaf);
  824. // note, "header" data is in LITTLE_ENDIAN format, entry data is in
  825. // BIG_ENDIAN format
  826. int entryPrefixLength = ByteUtil.getUnsignedShort(
  827. buffer, getFormat().OFFSET_INDEX_COMPRESSED_BYTE_COUNT);
  828. int entryMaskLength = getFormat().SIZE_INDEX_ENTRY_MASK;
  829. int entryMaskPos = getFormat().OFFSET_INDEX_ENTRY_MASK;
  830. int entryPos = entryMaskPos + entryMaskLength;
  831. int lastStart = 0;
  832. int totalEntrySize = 0;
  833. byte[] entryPrefix = null;
  834. List<Entry> entries = new ArrayList<Entry>();
  835. TempBufferHolder tmpEntryBufferH =
  836. TempBufferHolder.newHolder(TempBufferHolder.Type.HARD, true,
  837. ENTRY_BYTE_ORDER);
  838. Entry prevEntry = FIRST_ENTRY;
  839. for (int i = 0; i < entryMaskLength; i++) {
  840. byte entryMask = buffer.get(entryMaskPos + i);
  841. for (int j = 0; j < 8; j++) {
  842. if ((entryMask & (1 << j)) != 0) {
  843. int length = (i * 8) + j - lastStart;
  844. buffer.position(entryPos + lastStart);
  845. // determine if we can read straight from the index page (if no
  846. // entryPrefix). otherwise, create temp buf with complete entry.
  847. ByteBuffer curEntryBuffer = buffer;
  848. int curEntryLen = length;
  849. if(entryPrefix != null) {
  850. curEntryBuffer = getTempEntryBuffer(
  851. buffer, length, entryPrefix, tmpEntryBufferH);
  852. curEntryLen += entryPrefix.length;
  853. }
  854. totalEntrySize += curEntryLen;
  855. Entry entry = newEntry(curEntryBuffer, curEntryLen, isLeaf);
  856. if(prevEntry.compareTo(entry) >= 0) {
  857. throw new IOException("Unexpected order in index entries, " +
  858. prevEntry + " >= " + entry);
  859. }
  860. entries.add(entry);
  861. if((entries.size() == 1) && (entryPrefixLength > 0)) {
  862. // read any shared entry prefix
  863. entryPrefix = new byte[entryPrefixLength];
  864. buffer.position(entryPos + lastStart);
  865. buffer.get(entryPrefix);
  866. }
  867. lastStart += length;
  868. prevEntry = entry;
  869. }
  870. }
  871. }
  872. dataPage.setEntryPrefix(entryPrefix != null ? entryPrefix : EMPTY_PREFIX);
  873. dataPage.setEntries(entries);
  874. dataPage.setTotalEntrySize(totalEntrySize);
  875. int prevPageNumber = buffer.getInt(getFormat().OFFSET_PREV_INDEX_PAGE);
  876. int nextPageNumber = buffer.getInt(getFormat().OFFSET_NEXT_INDEX_PAGE);
  877. int childTailPageNumber =
  878. buffer.getInt(getFormat().OFFSET_CHILD_TAIL_INDEX_PAGE);
  879. dataPage.setPrevPageNumber(prevPageNumber);
  880. dataPage.setNextPageNumber(nextPageNumber);
  881. dataPage.setChildTailPageNumber(childTailPageNumber);
  882. }
  883. /**
  884. * Returns a new Entry of the correct type for the given data and page type.
  885. */
  886. private static Entry newEntry(ByteBuffer buffer, int entryLength,
  887. boolean isLeaf)
  888. throws IOException
  889. {
  890. if(isLeaf) {
  891. return new Entry(buffer, entryLength);
  892. }
  893. return new NodeEntry(buffer, entryLength);
  894. }
  895. /**
  896. * Returns an entry buffer containing the relevant data for an entry given
  897. * the valuePrefix.
  898. */
  899. private ByteBuffer getTempEntryBuffer(
  900. ByteBuffer indexPage, int entryLen, byte[] valuePrefix,
  901. TempBufferHolder tmpEntryBufferH)
  902. {
  903. ByteBuffer tmpEntryBuffer = tmpEntryBufferH.getBuffer(
  904. getPageChannel(), valuePrefix.length + entryLen);
  905. // combine valuePrefix and rest of entry from indexPage, then prep for
  906. // reading
  907. tmpEntryBuffer.put(valuePrefix);
  908. tmpEntryBuffer.put(indexPage.array(), indexPage.position(), entryLen);
  909. tmpEntryBuffer.flip();
  910. return tmpEntryBuffer;
  911. }
  912. /**
  913. * Determines if the given index page is a leaf or node page.
  914. */
  915. private static boolean isLeafPage(ByteBuffer buffer)
  916. throws IOException
  917. {
  918. byte pageType = buffer.get(0);
  919. if(pageType == PageTypes.INDEX_LEAF) {
  920. return true;
  921. } else if(pageType == PageTypes.INDEX_NODE) {
  922. return false;
  923. }
  924. throw new IOException("Unexpected page type " + pageType);
  925. }
  926. /**
  927. * Determines the number of {@code null} values for this index from the
  928. * given row.
  929. */
  930. private int countNullValues(Object[] values)
  931. {
  932. if(values == null) {
  933. return _columns.size();
  934. }
  935. // annoyingly, the values array could come from different sources, one
  936. // of which will make it a different size than the other. we need to
  937. // handle both situations.
  938. int nullCount = 0;
  939. for(ColumnDescriptor col : _columns) {
  940. Object value = values[col.getColumnIndex()];
  941. if(col.isNullValue(value)) {
  942. ++nullCount;
  943. }
  944. }
  945. return nullCount;
  946. }
  947. /**
  948. * Creates the entry bytes for a row of values.
  949. */
  950. private byte[] createEntryBytes(Object[] values) throws IOException
  951. {
  952. if(values == null) {
  953. return null;
  954. }
  955. if(_entryBuffer == null) {
  956. _entryBuffer = new ByteStream();
  957. }
  958. _entryBuffer.reset();
  959. for(ColumnDescriptor col : _columns) {
  960. Object value = values[col.getColumnIndex()];
  961. if(Column.isRawData(value)) {
  962. // ignore it, we could not parse it
  963. continue;
  964. }
  965. if(value == MIN_VALUE) {
  966. // null is the "least" value
  967. _entryBuffer.write(getNullEntryFlag(col.isAscending()));
  968. continue;
  969. }
  970. if(value == MAX_VALUE) {
  971. // the opposite null is the "greatest" value
  972. _entryBuffer.write(getNullEntryFlag(!col.isAscending()));
  973. continue;
  974. }
  975. col.writeValue(value, _entryBuffer);
  976. }
  977. return _entryBuffer.toByteArray();
  978. }
  979. /**
  980. * Writes the current index state to the database. Index has already been
  981. * initialized.
  982. */
  983. protected abstract void updateImpl() throws IOException;
  984. /**
  985. * Reads the actual index entries.
  986. */
  987. protected abstract void readIndexEntries()
  988. throws IOException;
  989. /**
  990. * Finds the data page for the given entry.
  991. */
  992. protected abstract DataPage findDataPage(Entry entry)
  993. throws IOException;
  994. /**
  995. * Gets the data page for the pageNumber.
  996. */
  997. protected abstract DataPage getDataPage(int pageNumber)
  998. throws IOException;
  999. /**
  1000. * Flips the first bit in the byte at the given index.
  1001. */
  1002. private static byte[] flipFirstBitInByte(byte[] value, int index)
  1003. {
  1004. value[index] = (byte)(value[index] ^ 0x80);
  1005. return value;
  1006. }
  1007. /**
  1008. * Flips all the bits in the byte array.
  1009. */
  1010. private static byte[] flipBytes(byte[] value) {
  1011. return flipBytes(value, 0, value.length);
  1012. }
  1013. /**
  1014. * Flips the bits in the specified bytes in the byte array.
  1015. */
  1016. static byte[] flipBytes(byte[] value, int offset, int length) {
  1017. for(int i = offset; i < (offset + length); ++i) {
  1018. value[i] = (byte)(~value[i]);
  1019. }
  1020. return value;
  1021. }
  1022. /**
  1023. * Writes the value of the given column type to a byte array and returns it.
  1024. */
  1025. private static byte[] encodeNumberColumnValue(Object value, Column column)
  1026. throws IOException
  1027. {
  1028. // always write in big endian order
  1029. return column.write(value, 0, ENTRY_BYTE_ORDER).array();
  1030. }
  1031. /**
  1032. * Creates one of the special index entries.
  1033. */
  1034. private static Entry createSpecialEntry(RowId rowId) {
  1035. return new Entry((byte[])null, rowId);
  1036. }
  1037. /**
  1038. * Constructs a ColumnDescriptor of the relevant type for the given Column.
  1039. */
  1040. private ColumnDescriptor newColumnDescriptor(Column col, byte flags)
  1041. throws IOException
  1042. {
  1043. switch(col.getType()) {
  1044. case TEXT:
  1045. case MEMO:
  1046. Column.SortOrder sortOrder = col.getTextSortOrder();
  1047. if(Column.GENERAL_LEGACY_SORT_ORDER.equals(sortOrder)) {
  1048. return new GenLegTextColumnDescriptor(col, flags);
  1049. }
  1050. if(Column.GENERAL_SORT_ORDER.equals(sortOrder)) {
  1051. return new GenTextColumnDescriptor(col, flags);
  1052. }
  1053. // unsupported sort order
  1054. LOG.warn("Unsupported collating sort order " + sortOrder +
  1055. " for text index, making read-only");
  1056. setReadOnly();
  1057. return new ReadOnlyColumnDescriptor(col, flags);
  1058. case INT:
  1059. case LONG:
  1060. case MONEY:
  1061. case COMPLEX_TYPE:
  1062. return new IntegerColumnDescriptor(col, flags);
  1063. case FLOAT:
  1064. case DOUBLE:
  1065. case SHORT_DATE_TIME:
  1066. return new FloatingPointColumnDescriptor(col, flags);
  1067. case NUMERIC:
  1068. return (col.getFormat().LEGACY_NUMERIC_INDEXES ?
  1069. new LegacyFixedPointColumnDescriptor(col, flags) :
  1070. new FixedPointColumnDescriptor(col, flags));
  1071. case BYTE:
  1072. return new ByteColumnDescriptor(col, flags);
  1073. case BOOLEAN:
  1074. return new BooleanColumnDescriptor(col, flags);
  1075. case GUID:
  1076. return new GuidColumnDescriptor(col, flags);
  1077. default:
  1078. // FIXME we can't modify this index at this point in time
  1079. LOG.warn("Unsupported data type " + col.getType() +
  1080. " for index, making read-only");
  1081. setReadOnly();
  1082. return new ReadOnlyColumnDescriptor(col, flags);
  1083. }
  1084. }
  1085. /**
  1086. * Returns the EntryType based on the given entry info.
  1087. */
  1088. private static EntryType determineEntryType(byte[] entryBytes, RowId rowId)
  1089. {
  1090. if(entryBytes != null) {
  1091. return ((rowId.getType() == RowId.Type.NORMAL) ?
  1092. EntryType.NORMAL :
  1093. ((rowId.getType() == RowId.Type.ALWAYS_FIRST) ?
  1094. EntryType.FIRST_VALID : EntryType.LAST_VALID));
  1095. } else if(!rowId.isValid()) {
  1096. // this is a "special" entry (first/last)
  1097. return ((rowId.getType() == RowId.Type.ALWAYS_FIRST) ?
  1098. EntryType.ALWAYS_FIRST : EntryType.ALWAYS_LAST);
  1099. }
  1100. throw new IllegalArgumentException("Values was null for valid entry");
  1101. }
  1102. /**
  1103. * Returns the maximum amount of entry data which can be encoded on any
  1104. * index page.
  1105. */
  1106. private static int calcMaxPageEntrySize(JetFormat format)
  1107. {
  1108. // the max data we can fit on a page is the min of the space on the page
  1109. // vs the number of bytes which can be encoded in the entry mask
  1110. int pageDataSize = (format.PAGE_SIZE -
  1111. (format.OFFSET_INDEX_ENTRY_MASK +
  1112. format.SIZE_INDEX_ENTRY_MASK));
  1113. int entryMaskSize = (format.SIZE_INDEX_ENTRY_MASK * 8);
  1114. return Math.min(pageDataSize, entryMaskSize);
  1115. }
  1116. /**
  1117. * Information about the columns in an index. Also encodes new index
  1118. * values.
  1119. */
  1120. public static abstract class ColumnDescriptor
  1121. {
  1122. private final Column _column;
  1123. private final byte _flags;
  1124. private ColumnDescriptor(Column column, byte flags)
  1125. throws IOException
  1126. {
  1127. _column = column;
  1128. _flags = flags;
  1129. }
  1130. public Column getColumn() {
  1131. return _column;
  1132. }
  1133. public byte getFlags() {
  1134. return _flags;
  1135. }
  1136. public boolean isAscending() {
  1137. return((getFlags() & ASCENDING_COLUMN_FLAG) != 0);
  1138. }
  1139. public int getColumnIndex() {
  1140. return getColumn().getColumnIndex();
  1141. }
  1142. public String getName() {
  1143. return getColumn().getName();
  1144. }
  1145. protected boolean isNullValue(Object value) {
  1146. return (value == null);
  1147. }
  1148. protected final void writeValue(Object value, ByteStream bout)
  1149. throws IOException
  1150. {
  1151. if(isNullValue(value)) {
  1152. // write null value
  1153. bout.write(getNullEntryFlag(isAscending()));
  1154. return;
  1155. }
  1156. // write the start flag
  1157. bout.write(getStartEntryFlag(isAscending()));
  1158. // write the rest of the value
  1159. writeNonNullValue(value, bout);
  1160. }
  1161. protected abstract void writeNonNullValue(
  1162. Object value, ByteStream bout)
  1163. throws IOException;
  1164. @Override
  1165. public String toString() {
  1166. return "ColumnDescriptor " + getColumn() + "\nflags: " + getFlags();
  1167. }
  1168. }
  1169. /**
  1170. * ColumnDescriptor for integer based columns.
  1171. */
  1172. private static final class IntegerColumnDescriptor extends ColumnDescriptor
  1173. {
  1174. private IntegerColumnDescriptor(Column column, byte flags)
  1175. throws IOException
  1176. {
  1177. super(column, flags);
  1178. }
  1179. @Override
  1180. protected void writeNonNullValue(
  1181. Object value, ByteStream bout)
  1182. throws IOException
  1183. {
  1184. byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
  1185. // bit twiddling rules:
  1186. // - isAsc => flipFirstBit
  1187. // - !isAsc => flipFirstBit, flipBytes
  1188. flipFirstBitInByte(valueBytes, 0);
  1189. if(!isAscending()) {
  1190. flipBytes(valueBytes);
  1191. }
  1192. bout.write(valueBytes);
  1193. }
  1194. }
  1195. /**
  1196. * ColumnDescriptor for floating point based columns.
  1197. */
  1198. private static final class FloatingPointColumnDescriptor
  1199. extends ColumnDescriptor
  1200. {
  1201. private FloatingPointColumnDescriptor(Column column, byte flags)
  1202. throws IOException
  1203. {
  1204. super(column, flags);
  1205. }
  1206. @Override
  1207. protected void writeNonNullValue(
  1208. Object value, ByteStream bout)
  1209. throws IOException
  1210. {
  1211. byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
  1212. // determine if the number is negative by testing if the first bit is
  1213. // set
  1214. boolean isNegative = ((valueBytes[0] & 0x80) != 0);
  1215. // bit twiddling rules:
  1216. // isAsc && !isNeg => flipFirstBit
  1217. // isAsc && isNeg => flipBytes
  1218. // !isAsc && !isNeg => flipFirstBit, flipBytes
  1219. // !isAsc && isNeg => nothing
  1220. if(!isNegative) {
  1221. flipFirstBitInByte(valueBytes, 0);
  1222. }
  1223. if(isNegative == isAscending()) {
  1224. flipBytes(valueBytes);
  1225. }
  1226. bout.write(valueBytes);
  1227. }
  1228. }
  1229. /**
  1230. * ColumnDescriptor for fixed point based columns (legacy sort order).
  1231. */
  1232. private static class LegacyFixedPointColumnDescriptor
  1233. extends ColumnDescriptor
  1234. {
  1235. private LegacyFixedPointColumnDescriptor(Column column, byte flags)
  1236. throws IOException
  1237. {
  1238. super(column, flags);
  1239. }
  1240. protected void handleNegationAndOrder(boolean isNegative,
  1241. byte[] valueBytes)
  1242. {
  1243. if(isNegative == isAscending()) {
  1244. flipBytes(valueBytes);
  1245. }
  1246. // reverse the sign byte (after any previous byte flipping)
  1247. valueBytes[0] = (isNegative ? (byte)0x00 : (byte)0xFF);
  1248. }
  1249. @Override
  1250. protected void writeNonNullValue(
  1251. Object value, ByteStream bout)
  1252. throws IOException
  1253. {
  1254. byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
  1255. // determine if the number is negative by testing if the first bit is
  1256. // set
  1257. boolean isNegative = ((valueBytes[0] & 0x80) != 0);
  1258. // bit twiddling rules:
  1259. // isAsc && !isNeg => setReverseSignByte => FF 00 00 ...
  1260. // isAsc && isNeg => flipBytes, setReverseSignByte => 00 FF FF ...
  1261. // !isAsc && !isNeg => flipBytes, setReverseSignByte => FF FF FF ...
  1262. // !isAsc && isNeg => setReverseSignByte => 00 00 00 ...
  1263. // v2007 bit twiddling rules (old ordering was a bug, MS kb 837148):
  1264. // isAsc && !isNeg => setSignByte 0xFF => FF 00 00 ...
  1265. // isAsc && isNeg => setSignByte 0xFF, flipBytes => 00 FF FF ...
  1266. // !isAsc && !isNeg => setSignByte 0xFF => FF 00 00 ...
  1267. // !isAsc && isNeg => setSignByte 0xFF, flipBytes => 00 FF FF ...
  1268. handleNegationAndOrder(isNegative, valueBytes);
  1269. bout.write(valueBytes);
  1270. }
  1271. }
  1272. /**
  1273. * ColumnDescriptor for new-style fixed point based columns.
  1274. */
  1275. private static final class FixedPointColumnDescriptor
  1276. extends LegacyFixedPointColumnDescriptor
  1277. {
  1278. private FixedPointColumnDescriptor(Column column, byte flags)
  1279. throws IOException
  1280. {
  1281. super(column, flags);
  1282. }
  1283. @Override
  1284. protected void handleNegationAndOrder(boolean isNegative,
  1285. byte[] valueBytes)
  1286. {
  1287. // see notes above in FixedPointColumnDescriptor for bit twiddling rules
  1288. // reverse the sign byte (before any byte flipping)
  1289. valueBytes[0] = (byte)0xFF;
  1290. if(isNegative == isAscending()) {
  1291. flipBytes(valueBytes);
  1292. }
  1293. }
  1294. }
  1295. /**
  1296. * ColumnDescriptor for byte based columns.
  1297. */
  1298. private static final class ByteColumnDescriptor extends ColumnDescriptor
  1299. {
  1300. private ByteColumnDescriptor(Column column, byte flags)
  1301. throws IOException
  1302. {
  1303. super(column, flags);
  1304. }
  1305. @Override
  1306. protected void writeNonNullValue(
  1307. Object value, ByteStream bout)
  1308. throws IOException
  1309. {
  1310. byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
  1311. // bit twiddling rules:
  1312. // - isAsc => nothing
  1313. // - !isAsc => flipBytes
  1314. if(!isAscending()) {
  1315. flipBytes(valueBytes);
  1316. }
  1317. bout.write(valueBytes);
  1318. }
  1319. }
  1320. /**
  1321. * ColumnDescriptor for boolean columns.
  1322. */
  1323. private static final class BooleanColumnDescriptor extends ColumnDescriptor
  1324. {
  1325. private BooleanColumnDescriptor(Column column, byte flags)
  1326. throws IOException
  1327. {
  1328. super(column, flags);
  1329. }
  1330. @Override
  1331. protected boolean isNullValue(Object value) {
  1332. // null values are handled as booleans
  1333. return false;
  1334. }
  1335. @Override
  1336. protected void writeNonNullValue(Object value, ByteStream bout)
  1337. throws IOException
  1338. {
  1339. bout.write(
  1340. Column.toBooleanValue(value) ?
  1341. (isAscending() ? ASC_BOOLEAN_TRUE : DESC_BOOLEAN_TRUE) :
  1342. (isAscending() ? ASC_BOOLEAN_FALSE : DESC_BOOLEAN_FALSE));
  1343. }
  1344. }
  1345. /**
  1346. * ColumnDescriptor for "general legacy" sort order text based columns.
  1347. */
  1348. private static final class GenLegTextColumnDescriptor
  1349. extends ColumnDescriptor
  1350. {
  1351. private GenLegTextColumnDescriptor(Column column, byte flags)
  1352. throws IOException
  1353. {
  1354. super(column, flags);
  1355. }
  1356. @Override
  1357. protected void writeNonNullValue(
  1358. Object value, ByteStream bout)
  1359. throws IOException
  1360. {
  1361. GeneralLegacyIndexCodes.GEN_LEG_INSTANCE.writeNonNullIndexTextValue(
  1362. value, bout, isAscending());
  1363. }
  1364. }
  1365. /**
  1366. * ColumnDescriptor for "general" sort order (2010+) text based columns.
  1367. */
  1368. private static final class GenTextColumnDescriptor extends ColumnDescriptor
  1369. {
  1370. private GenTextColumnDescriptor(Column column, byte flags)
  1371. throws IOException
  1372. {
  1373. super(column, flags);
  1374. }
  1375. @Override
  1376. protected void writeNonNullValue(
  1377. Object value, ByteStream bout)
  1378. throws IOException
  1379. {
  1380. GeneralIndexCodes.GEN_INSTANCE.writeNonNullIndexTextValue(
  1381. value, bout, isAscending());
  1382. }
  1383. }
  1384. /**
  1385. * ColumnDescriptor for guid columns.
  1386. */
  1387. private static final class GuidColumnDescriptor extends ColumnDescriptor
  1388. {
  1389. private GuidColumnDescriptor(Column column, byte flags)
  1390. throws IOException
  1391. {
  1392. super(column, flags);
  1393. }
  1394. @Override
  1395. protected void writeNonNullValue(
  1396. Object value, ByteStream bout)
  1397. throws IOException
  1398. {
  1399. byte[] valueBytes = encodeNumberColumnValue(value, getColumn());
  1400. // index format <8-bytes> 0x09 <8-bytes> 0x08
  1401. // bit twiddling rules:
  1402. // - isAsc => nothing
  1403. // - !isAsc => flipBytes, _but keep 09 unflipped_!
  1404. if(!isAscending()) {
  1405. flipBytes(valueBytes);
  1406. }
  1407. bout.write(valueBytes, 0, 8);
  1408. bout.write(MID_GUID);
  1409. bout.write(valueBytes, 8, 8);
  1410. bout.write(isAscending() ? ASC_END_GUID : DESC_END_GUID);
  1411. }
  1412. }
  1413. /**
  1414. * ColumnDescriptor for columns which we cannot currently write.
  1415. */
  1416. private static final class ReadOnlyColumnDescriptor extends ColumnDescriptor
  1417. {
  1418. private ReadOnlyColumnDescriptor(Column column, byte flags)
  1419. throws IOException
  1420. {
  1421. super(column, flags);
  1422. }
  1423. @Override
  1424. protected void writeNonNullValue(Object value, ByteStream bout)
  1425. throws IOException
  1426. {
  1427. throw new UnsupportedOperationException("should not be called");
  1428. }
  1429. }
  1430. /**
  1431. * A single leaf entry in an index (points to a single row)
  1432. */
  1433. public static class Entry implements Comparable<Entry>
  1434. {
  1435. /** page/row on which this row is stored */
  1436. private final RowId _rowId;
  1437. /** the entry value */
  1438. private final byte[] _entryBytes;
  1439. /** comparable type for the entry */
  1440. private final EntryType _type;
  1441. /**
  1442. * Create a new entry
  1443. * @param entryBytes encoded bytes for this index entry
  1444. * @param rowId rowId in which the row is stored
  1445. * @param type the type of the entry
  1446. */
  1447. private Entry(byte[] entryBytes, RowId rowId, EntryType type) {
  1448. _rowId = rowId;
  1449. _entryBytes = entryBytes;
  1450. _type = type;
  1451. }
  1452. /**
  1453. * Create a new entry
  1454. * @param entryBytes encoded bytes for this index entry
  1455. * @param rowId rowId in which the row is stored
  1456. */
  1457. private Entry(byte[] entryBytes, RowId rowId)
  1458. {
  1459. this(entryBytes, rowId, determineEntryType(entryBytes, rowId));
  1460. }
  1461. /**
  1462. * Read an existing entry in from a buffer
  1463. */
  1464. private Entry(ByteBuffer buffer, int entryLen)
  1465. throws IOException
  1466. {
  1467. this(buffer, entryLen, 0);
  1468. }
  1469. /**
  1470. * Read an existing entry in from a buffer
  1471. */
  1472. private Entry(ByteBuffer buffer, int entryLen, int extraTrailingLen)
  1473. throws IOException
  1474. {
  1475. // we need 4 trailing bytes for the rowId, plus whatever the caller
  1476. // wants
  1477. int colEntryLen = entryLen - (4 + extraTrailingLen);
  1478. // read the entry bytes
  1479. _entryBytes = ByteUtil.getBytes(buffer, colEntryLen);
  1480. // read the rowId
  1481. int page = ByteUtil.get3ByteInt(buffer, ENTRY_BYTE_ORDER);
  1482. int row = ByteUtil.getUnsignedByte(buffer);
  1483. _rowId = new RowId(page, row);
  1484. _type = EntryType.NORMAL;
  1485. }
  1486. public RowId getRowId() {
  1487. return _rowId;
  1488. }
  1489. public EntryType getType() {
  1490. return _type;
  1491. }
  1492. public Integer getSubPageNumber() {
  1493. throw new UnsupportedOperationException();
  1494. }
  1495. public boolean isLeafEntry() {
  1496. return true;
  1497. }
  1498. public boolean isValid() {
  1499. return(_entryBytes != null);
  1500. }
  1501. protected final byte[] getEntryBytes() {
  1502. return _entryBytes;
  1503. }
  1504. /**
  1505. * Size of this entry in the db.
  1506. */
  1507. protected int size() {
  1508. // need 4 trailing bytes for the rowId
  1509. return _entryBytes.length + 4;
  1510. }
  1511. /**
  1512. * Write this entry into a buffer
  1513. */
  1514. protected void write(ByteBuffer buffer,
  1515. byte[] prefix)
  1516. throws IOException
  1517. {
  1518. if(prefix.length <= _entryBytes.length) {
  1519. // write entry bytes, not including prefix
  1520. buffer.put(_entryBytes, prefix.length,
  1521. (_entryBytes.length - prefix.length));
  1522. ByteUtil.put3ByteInt(buffer, getRowId().getPageNumber(),
  1523. ENTRY_BYTE_ORDER);
  1524. } else if(prefix.length <= (_entryBytes.length + 3)) {
  1525. // the prefix includes part of the page number, write to temp buffer
  1526. // and copy last bytes to output buffer
  1527. ByteBuffer tmp = ByteBuffer.allocate(3);
  1528. ByteUtil.put3ByteInt(tmp, getRowId().getPageNumber(),
  1529. ENTRY_BYTE_ORDER);
  1530. tmp.flip();
  1531. tmp.position(prefix.length - _entryBytes.length);
  1532. buffer.put(tmp);
  1533. } else {
  1534. // since the row number would never be the same if the page number is
  1535. // the same, nothing past the page number should ever be included in
  1536. // the prefix.
  1537. // FIXME, this could happen if page has only one row...
  1538. throw new IllegalStateException("prefix should never be this long");
  1539. }
  1540. buffer.put((byte)getRowId().getRowNumber());
  1541. }
  1542. protected final String entryBytesToString() {
  1543. return (isValid() ? ", Bytes = " + ByteUtil.toHexString(
  1544. ByteBuffer.wrap(_entryBytes), _entryBytes.length) :
  1545. "");
  1546. }
  1547. @Override
  1548. public String toString() {
  1549. return "RowId = " + _rowId + entryBytesToString() + "\n";
  1550. }
  1551. @Override
  1552. public int hashCode() {
  1553. return _rowId.hashCode();
  1554. }
  1555. @Override
  1556. public boolean equals(Object o) {
  1557. return((this == o) ||
  1558. ((o != null) && (getClass() == o.getClass()) &&
  1559. (compareTo((Entry)o) == 0)));
  1560. }
  1561. /**
  1562. * @return {@code true} iff the entryBytes are equal between this
  1563. * Entry and the given Entry
  1564. */
  1565. public boolean equalsEntryBytes(Entry o) {
  1566. return(BYTE_CODE_COMPARATOR.compare(_entryBytes, o._entryBytes) == 0);
  1567. }
  1568. public int compareTo(Entry other) {
  1569. if (this == other) {
  1570. return 0;
  1571. }
  1572. if(isValid() && other.isValid()) {
  1573. // comparing two valid entries. first, compare by actual byte values
  1574. int entryCmp = BYTE_CODE_COMPARATOR.compare(
  1575. _entryBytes, other._entryBytes);
  1576. if(entryCmp != 0) {
  1577. return entryCmp;
  1578. }
  1579. } else {
  1580. // if the entries are of mixed validity (or both invalid), we defer
  1581. // next to the EntryType
  1582. int typeCmp = _type.compareTo(other._type);
  1583. if(typeCmp != 0) {
  1584. return typeCmp;
  1585. }
  1586. }
  1587. // at this point we let the RowId decide the final result
  1588. return _rowId.compareTo(other.getRowId());
  1589. }
  1590. /**
  1591. * Returns a copy of this entry as a node Entry with the given
  1592. * subPageNumber.
  1593. */
  1594. protected Entry asNodeEntry(Integer subPageNumber) {
  1595. return new NodeEntry(_entryBytes, _rowId, _type, subPageNumber);
  1596. }
  1597. }
  1598. /**
  1599. * A single node entry in an index (points to a sub-page in the index)
  1600. */
  1601. private static final class NodeEntry extends Entry {
  1602. /** index page number of the page to which this node entry refers */
  1603. private final Integer _subPageNumber;
  1604. /**
  1605. * Create a new node entry
  1606. * @param entryBytes encoded bytes for this index entry
  1607. * @param rowId rowId in which the row is stored
  1608. * @param type the type of the entry
  1609. * @param subPageNumber the sub-page to which this node entry refers
  1610. */
  1611. private NodeEntry(byte[] entryBytes, RowId rowId, EntryType type,
  1612. Integer subPageNumber) {
  1613. super(entryBytes, rowId, type);
  1614. _subPageNumber = subPageNumber;
  1615. }
  1616. /**
  1617. * Read an existing node entry in from a buffer
  1618. */
  1619. private NodeEntry(ByteBuffer buffer, int entryLen)
  1620. throws IOException
  1621. {
  1622. // we need 4 trailing bytes for the sub-page number
  1623. super(buffer, entryLen, 4);
  1624. _subPageNumber = ByteUtil.getInt(buffer, ENTRY_BYTE_ORDER);
  1625. }
  1626. @Override
  1627. public Integer getSubPageNumber() {
  1628. return _subPageNumber;
  1629. }
  1630. @Override
  1631. public boolean isLeafEntry() {
  1632. return false;
  1633. }
  1634. @Override
  1635. protected int size() {
  1636. // need 4 trailing bytes for the sub-page number
  1637. return super.size() + 4;
  1638. }
  1639. @Override
  1640. protected void write(ByteBuffer buffer, byte[] prefix) throws IOException {
  1641. super.write(buffer, prefix);
  1642. ByteUtil.putInt(buffer, _subPageNumber, ENTRY_BYTE_ORDER);
  1643. }
  1644. @Override
  1645. public boolean equals(Object o) {
  1646. return((this == o) ||
  1647. ((o != null) && (getClass() == o.getClass()) &&
  1648. (compareTo((Entry)o) == 0) &&
  1649. (getSubPageNumber().equals(((Entry)o).getSubPageNumber()))));
  1650. }
  1651. @Override
  1652. public String toString() {
  1653. return ("Node RowId = " + getRowId() +
  1654. ", SubPage = " + _subPageNumber + entryBytesToString() + "\n");
  1655. }
  1656. }
  1657. /**
  1658. * Utility class to traverse the entries in the Index. Remains valid in the
  1659. * face of index entry modifications.
  1660. */
  1661. public final class EntryCursor
  1662. {
  1663. /** handler for moving the page cursor forward */
  1664. private final DirHandler _forwardDirHandler = new ForwardDirHandler();
  1665. /** handler for moving the page cursor backward */
  1666. private final DirHandler _reverseDirHandler = new ReverseDirHandler();
  1667. /** the first (exclusive) row id for this cursor */
  1668. private Position _firstPos;
  1669. /** the last (exclusive) row id for this cursor */
  1670. private Position _lastPos;
  1671. /** the current entry */
  1672. private Position _curPos;
  1673. /** the previous entry */
  1674. private Position _prevPos;
  1675. /** the last read modification count on the Index. we track this so that
  1676. the cursor can detect updates to the index while traversing and act
  1677. accordingly */
  1678. private int _lastModCount;
  1679. private EntryCursor(Position firstPos, Position lastPos)
  1680. {
  1681. _firstPos = firstPos;
  1682. _lastPos = lastPos;
  1683. _lastModCount = getIndexModCount();
  1684. reset();
  1685. }
  1686. /**
  1687. * Returns the DirHandler for the given direction
  1688. */
  1689. private DirHandler getDirHandler(boolean moveForward) {
  1690. return (moveForward ? _forwardDirHandler : _reverseDirHandler);
  1691. }
  1692. public IndexData getIndexData() {
  1693. return IndexData.this;
  1694. }
  1695. private int getIndexModCount() {
  1696. return IndexData.this._modCount;
  1697. }
  1698. /**
  1699. * Returns the first entry (exclusive) as defined by this cursor.
  1700. */
  1701. public Entry getFirstEntry() {
  1702. return _firstPos.getEntry();
  1703. }
  1704. /**
  1705. * Returns the last entry (exclusive) as defined by this cursor.
  1706. */
  1707. public Entry getLastEntry() {
  1708. return _lastPos.getEntry();
  1709. }
  1710. /**
  1711. * Returns {@code true} if this cursor is up-to-date with respect to its
  1712. * index.
  1713. */
  1714. public boolean isUpToDate() {
  1715. return(getIndexModCount() == _lastModCount);
  1716. }
  1717. public void reset() {
  1718. beforeFirst();
  1719. }
  1720. public void beforeFirst() {
  1721. reset(Cursor.MOVE_FORWARD);
  1722. }
  1723. public void afterLast() {
  1724. reset(Cursor.MOVE_REVERSE);
  1725. }
  1726. protected void reset(boolean moveForward)
  1727. {
  1728. _curPos = getDirHandler(moveForward).getBeginningPosition();
  1729. _prevPos = _curPos;
  1730. }
  1731. /**
  1732. * Repositions the cursor so that the next row will be the first entry
  1733. * >= the given row.
  1734. */
  1735. public void beforeEntry(Object[] row)
  1736. throws IOException
  1737. {
  1738. restorePosition(
  1739. new Entry(IndexData.this.createEntryBytes(row), RowId.FIRST_ROW_ID));
  1740. }
  1741. /**
  1742. * Repositions the cursor so that the previous row will be the first
  1743. * entry <= the given row.
  1744. */
  1745. public void afterEntry(Object[] row)
  1746. throws IOException
  1747. {
  1748. restorePosition(
  1749. new Entry(IndexData.this.createEntryBytes(row), RowId.LAST_ROW_ID));
  1750. }
  1751. /**
  1752. * @return valid entry if there was a next entry,
  1753. * {@code #getLastEntry} otherwise
  1754. */
  1755. public Entry getNextEntry() throws IOException {
  1756. return getAnotherPosition(Cursor.MOVE_FORWARD).getEntry();
  1757. }
  1758. /**
  1759. * @return valid entry if there was a next entry,
  1760. * {@code #getFirstEntry} otherwise
  1761. */
  1762. public Entry getPreviousEntry() throws IOException {
  1763. return getAnotherPosition(Cursor.MOVE_REVERSE).getEntry();
  1764. }
  1765. /**
  1766. * Restores a current position for the cursor (current position becomes
  1767. * previous position).
  1768. */
  1769. protected void restorePosition(Entry curEntry)
  1770. throws IOException
  1771. {
  1772. restorePosition(curEntry, _curPos.getEntry());
  1773. }
  1774. /**
  1775. * Restores a current and previous position for the cursor.
  1776. */
  1777. protected void restorePosition(Entry curEntry, Entry prevEntry)
  1778. throws IOException
  1779. {
  1780. if(!_curPos.equalsEntry(curEntry) ||
  1781. !_prevPos.equalsEntry(prevEntry))
  1782. {
  1783. if(!isUpToDate()) {
  1784. updateBounds();
  1785. _lastModCount = getIndexModCount();
  1786. }
  1787. _prevPos = updatePosition(prevEntry);
  1788. _curPos = updatePosition(curEntry);
  1789. } else {
  1790. checkForModification();
  1791. }
  1792. }
  1793. /**
  1794. * Gets another entry in the given direction, returning the new entry.
  1795. */
  1796. private Position getAnotherPosition(boolean moveForward)
  1797. throws IOException
  1798. {
  1799. DirHandler handler = getDirHandler(moveForward);
  1800. if(_curPos.equals(handler.getEndPosition())) {
  1801. if(!isUpToDate()) {
  1802. restorePosition(_prevPos.getEntry());
  1803. // drop through and retry moving to another entry
  1804. } else {
  1805. // at end, no more
  1806. return _curPos;
  1807. }
  1808. }
  1809. checkForModification();
  1810. _prevPos = _curPos;
  1811. _curPos = handler.getAnotherPosition(_curPos);
  1812. return _curPos;
  1813. }
  1814. /**
  1815. * Checks the index for modifications and updates state accordingly.
  1816. */
  1817. private void checkForModification()
  1818. throws IOException
  1819. {
  1820. if(!isUpToDate()) {
  1821. updateBounds();
  1822. _prevPos = updatePosition(_prevPos.getEntry());
  1823. _curPos = updatePosition(_curPos.getEntry());
  1824. _lastModCount = getIndexModCount();
  1825. }
  1826. }
  1827. /**
  1828. * Updates the given position, taking boundaries into account.
  1829. */
  1830. private Position updatePosition(Entry entry)
  1831. throws IOException
  1832. {
  1833. if(!entry.isValid()) {
  1834. // no use searching if "updating" the first/last pos
  1835. if(_firstPos.equalsEntry(entry)) {
  1836. return _firstPos;
  1837. } else if(_lastPos.equalsEntry(entry)) {
  1838. return _lastPos;
  1839. } else {
  1840. throw new IllegalArgumentException("Invalid entry given " + entry);
  1841. }
  1842. }
  1843. Position pos = findEntryPosition(entry);
  1844. if(pos.compareTo(_lastPos) >= 0) {
  1845. return _lastPos;
  1846. } else if(pos.compareTo(_firstPos) <= 0) {
  1847. return _firstPos;
  1848. }
  1849. return pos;
  1850. }
  1851. /**
  1852. * Updates any the boundary info (_firstPos/_lastPos).
  1853. */
  1854. private void updateBounds()
  1855. throws IOException
  1856. {
  1857. _firstPos = findEntryPosition(_firstPos.getEntry());
  1858. _lastPos = findEntryPosition(_lastPos.getEntry());
  1859. }
  1860. @Override
  1861. public String toString() {
  1862. return getClass().getSimpleName() + " CurPosition " + _curPos +
  1863. ", PrevPosition " + _prevPos;
  1864. }
  1865. /**
  1866. * Handles moving the cursor in a given direction. Separates cursor
  1867. * logic from value storage.
  1868. */
  1869. private abstract class DirHandler {
  1870. public abstract Position getAnotherPosition(Position curPos)
  1871. throws IOException;
  1872. public abstract Position getBeginningPosition();
  1873. public abstract Position getEndPosition();
  1874. }
  1875. /**
  1876. * Handles moving the cursor forward.
  1877. */
  1878. private final class ForwardDirHandler extends DirHandler {
  1879. @Override
  1880. public Position getAnotherPosition(Position curPos)
  1881. throws IOException
  1882. {
  1883. Position newPos = getNextPosition(curPos);
  1884. if((newPos == null) || (newPos.compareTo(_lastPos) >= 0)) {
  1885. newPos = _lastPos;
  1886. }
  1887. return newPos;
  1888. }
  1889. @Override
  1890. public Position getBeginningPosition() {
  1891. return _firstPos;
  1892. }
  1893. @Override
  1894. public Position getEndPosition() {
  1895. return _lastPos;
  1896. }
  1897. }
  1898. /**
  1899. * Handles moving the cursor backward.
  1900. */
  1901. private final class ReverseDirHandler extends DirHandler {
  1902. @Override
  1903. public Position getAnotherPosition(Position curPos)
  1904. throws IOException
  1905. {
  1906. Position newPos = getPreviousPosition(curPos);
  1907. if((newPos == null) || (newPos.compareTo(_firstPos) <= 0)) {
  1908. newPos = _firstPos;
  1909. }
  1910. return newPos;
  1911. }
  1912. @Override
  1913. public Position getBeginningPosition() {
  1914. return _lastPos;
  1915. }
  1916. @Override
  1917. public Position getEndPosition() {
  1918. return _firstPos;
  1919. }
  1920. }
  1921. }
  1922. /**
  1923. * Simple value object for maintaining some cursor state.
  1924. */
  1925. private static final class Position implements Comparable<Position> {
  1926. /** the last known page of the given entry */
  1927. private final DataPage _dataPage;
  1928. /** the last known index of the given entry */
  1929. private final int _idx;
  1930. /** the entry at the given index */
  1931. private final Entry _entry;
  1932. /** {@code true} if this entry does not currently exist in the entry list,
  1933. {@code false} otherwise (this is equivalent to adding -0.5 to the
  1934. _idx) */
  1935. private final boolean _between;
  1936. private Position(DataPage dataPage, int idx)
  1937. {
  1938. this(dataPage, idx, dataPage.getEntries().get(idx), false);
  1939. }
  1940. private Position(DataPage dataPage, int idx, Entry entry, boolean between)
  1941. {
  1942. _dataPage = dataPage;
  1943. _idx = idx;
  1944. _entry = entry;
  1945. _between = between;
  1946. }
  1947. public DataPage getDataPage() {
  1948. return _dataPage;
  1949. }
  1950. public int getIndex() {
  1951. return _idx;
  1952. }
  1953. public int getNextIndex() {
  1954. // note, _idx does not need to be advanced if it was pointing at a
  1955. // between position
  1956. return(_between ? _idx : (_idx + 1));
  1957. }
  1958. public int getPrevIndex() {
  1959. // note, we ignore the between flag here because the index will be
  1960. // pointing at the correct next index in either the between or
  1961. // non-between case
  1962. return(_idx - 1);
  1963. }
  1964. public Entry getEntry() {
  1965. return _entry;
  1966. }
  1967. public boolean isBetween() {
  1968. return _between;
  1969. }
  1970. public boolean equalsEntry(Entry entry) {
  1971. return _entry.equals(entry);
  1972. }
  1973. public int compareTo(Position other)
  1974. {
  1975. if(this == other) {
  1976. return 0;
  1977. }
  1978. if(_dataPage.equals(other._dataPage)) {
  1979. // "simple" index comparison (handle between-ness)
  1980. int idxCmp = ((_idx < other._idx) ? -1 :
  1981. ((_idx > other._idx) ? 1 :
  1982. ((_between == other._between) ? 0 :
  1983. (_between ? -1 : 1))));
  1984. if(idxCmp != 0) {
  1985. return idxCmp;
  1986. }
  1987. }
  1988. // compare the entries.
  1989. return _entry.compareTo(other._entry);
  1990. }
  1991. @Override
  1992. public int hashCode() {
  1993. return _entry.hashCode();
  1994. }
  1995. @Override
  1996. public boolean equals(Object o) {
  1997. return((this == o) ||
  1998. ((o != null) && (getClass() == o.getClass()) &&
  1999. (compareTo((Position)o) == 0)));
  2000. }
  2001. @Override
  2002. public String toString() {
  2003. return "Page = " + _dataPage.getPageNumber() + ", Idx = " + _idx +
  2004. ", Entry = " + _entry + ", Between = " + _between;
  2005. }
  2006. }
  2007. /**
  2008. * Object used to maintain state about an Index page.
  2009. */
  2010. protected static abstract class DataPage {
  2011. public abstract int getPageNumber();
  2012. public abstract boolean isLeaf();
  2013. public abstract void setLeaf(boolean isLeaf);
  2014. public abstract int getPrevPageNumber();
  2015. public abstract void setPrevPageNumber(int pageNumber);
  2016. public abstract int getNextPageNumber();
  2017. public abstract void setNextPageNumber(int pageNumber);
  2018. public abstract int getChildTailPageNumber();
  2019. public abstract void setChildTailPageNumber(int pageNumber);
  2020. public abstract int getTotalEntrySize();
  2021. public abstract void setTotalEntrySize(int totalSize);
  2022. public abstract byte[] getEntryPrefix();
  2023. public abstract void setEntryPrefix(byte[] entryPrefix);
  2024. public abstract List<Entry> getEntries();
  2025. public abstract void setEntries(List<Entry> entries);
  2026. public abstract void addEntry(int idx, Entry entry)
  2027. throws IOException;
  2028. public abstract void removeEntry(int idx)
  2029. throws IOException;
  2030. public final boolean isEmpty() {
  2031. return getEntries().isEmpty();
  2032. }
  2033. public final int getCompressedEntrySize() {
  2034. // when written to the index page, the entryPrefix bytes will only be
  2035. // written for the first entry, so we subtract the entry prefix size
  2036. // from all the other entries to determine the compressed size
  2037. return getTotalEntrySize() -
  2038. (getEntryPrefix().length * (getEntries().size() - 1));
  2039. }
  2040. public final int findEntry(Entry entry) {
  2041. return Collections.binarySearch(getEntries(), entry);
  2042. }
  2043. @Override
  2044. public final int hashCode() {
  2045. return getPageNumber();
  2046. }
  2047. @Override
  2048. public final boolean equals(Object o) {
  2049. return((this == o) ||
  2050. ((o != null) && (getClass() == o.getClass()) &&
  2051. (getPageNumber() == ((DataPage)o).getPageNumber())));
  2052. }
  2053. @Override
  2054. public final String toString() {
  2055. List<Entry> entries = getEntries();
  2056. return (isLeaf() ? "Leaf" : "Node") + "DataPage[" + getPageNumber() +
  2057. "] " + getPrevPageNumber() + ", " + getNextPageNumber() + ", (" +
  2058. getChildTailPageNumber() + "), " +
  2059. ((isLeaf() && !entries.isEmpty()) ?
  2060. ("[" + entries.get(0) + ", " +
  2061. entries.get(entries.size() - 1) + "]") :
  2062. entries);
  2063. }
  2064. }
  2065. }