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.

Index.java 50KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714
  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.ByteArrayOutputStream;
  24. import java.io.IOException;
  25. import java.nio.ByteBuffer;
  26. import java.nio.ByteOrder;
  27. import java.util.ArrayList;
  28. import java.util.Arrays;
  29. import java.util.Collection;
  30. import java.util.Collections;
  31. import java.util.Comparator;
  32. import java.util.HashMap;
  33. import java.util.Iterator;
  34. import java.util.LinkedHashMap;
  35. import java.util.List;
  36. import java.util.Map;
  37. import java.util.SortedSet;
  38. import java.util.TreeSet;
  39. import org.apache.commons.lang.builder.CompareToBuilder;
  40. import org.apache.commons.logging.Log;
  41. import org.apache.commons.logging.LogFactory;
  42. /**
  43. * Access table index
  44. * @author Tim McCune
  45. */
  46. public class Index implements Comparable<Index> {
  47. private static final Log LOG = LogFactory.getLog(Index.class);
  48. /** special entry which is less than any other entry */
  49. public static final Entry FIRST_ENTRY =
  50. createSpecialEntry(RowId.FIRST_ROW_ID);
  51. /** special entry which is greater than any other entry */
  52. public static final Entry LAST_ENTRY =
  53. createSpecialEntry(RowId.LAST_ROW_ID);
  54. /** index of the first (exclusive) index entry */
  55. private static final int FIRST_ENTRY_IDX = -1;
  56. /** index of the last (exclusive) index entry */
  57. private static final int LAST_ENTRY_IDX = -2;
  58. /** the first position for a cursor */
  59. private static final Position FIRST_POSITION =
  60. new Position(FIRST_ENTRY_IDX, FIRST_ENTRY);
  61. /** the last position for a cursor */
  62. private static final Position LAST_POSITION =
  63. new Position(LAST_ENTRY_IDX, LAST_ENTRY);
  64. /** Max number of columns in an index */
  65. private static final int MAX_COLUMNS = 10;
  66. private static final short COLUMN_UNUSED = -1;
  67. private static final byte INDEX_NODE_PAGE_TYPE = (byte)0x03;
  68. private static final byte INDEX_LEAF_PAGE_TYPE = (byte)0x04;
  69. static final Comparator<byte[]> BYTE_CODE_COMPARATOR =
  70. new Comparator<byte[]>() {
  71. public int compare(byte[] left, byte[] right) {
  72. if(left == right) {
  73. return 0;
  74. }
  75. if(left == null) {
  76. return -1;
  77. }
  78. if(right == null) {
  79. return 1;
  80. }
  81. int len = Math.min(left.length, right.length);
  82. int pos = 0;
  83. while((pos < len) && (left[pos] == right[pos])) {
  84. ++pos;
  85. }
  86. if(pos < len) {
  87. return ((ByteUtil.toUnsignedInt(left[pos]) <
  88. ByteUtil.toUnsignedInt(right[pos])) ? -1 : 1);
  89. }
  90. return ((left.length < right.length) ? -1 :
  91. ((left.length > right.length) ? 1 : 0));
  92. }
  93. };
  94. /**
  95. * Map of character to byte[] that Access uses in indexes (not ASCII)
  96. * (Character -> byte[]) as codes to order text
  97. */
  98. private static final Map<Character, byte[]> CODES =
  99. new HashMap<Character, byte[]>();
  100. /**
  101. * Map of character to byte[] that Access uses in indexes (not ASCII)
  102. * (Character -> byte[]), in the extended portion
  103. */
  104. private static final Map<Character, byte[]> CODES_EXT =
  105. new HashMap<Character, byte[]>();
  106. static {
  107. CODES.put('^', new byte[]{(byte)43, (byte)2});
  108. CODES.put('_', new byte[]{(byte)43, (byte)3});
  109. CODES.put('`', new byte[]{(byte)43, (byte)7});
  110. CODES.put('{', new byte[]{(byte)43, (byte)9});
  111. CODES.put('|', new byte[]{(byte)43, (byte)11});
  112. CODES.put('}', new byte[]{(byte)43, (byte)13});
  113. CODES.put('~', new byte[]{(byte)43, (byte)15});
  114. CODES.put('\t', new byte[]{(byte)8, (byte)3});
  115. CODES.put('\r', new byte[]{(byte)8, (byte)4});
  116. CODES.put('\n', new byte[]{(byte)8, (byte)7});
  117. CODES.put(' ', new byte[]{(byte)7});
  118. CODES.put('!', new byte[]{(byte)9});
  119. CODES.put('"', new byte[]{(byte)10});
  120. CODES.put('#', new byte[]{(byte)12});
  121. CODES.put('$', new byte[]{(byte)14});
  122. CODES.put('%', new byte[]{(byte)16});
  123. CODES.put('&', new byte[]{(byte)18});
  124. CODES.put('(', new byte[]{(byte)20});
  125. CODES.put(')', new byte[]{(byte)22});
  126. CODES.put('*', new byte[]{(byte)24});
  127. CODES.put(',', new byte[]{(byte)26});
  128. CODES.put('.', new byte[]{(byte)28});
  129. CODES.put('/', new byte[]{(byte)30});
  130. CODES.put(':', new byte[]{(byte)32});
  131. CODES.put(';', new byte[]{(byte)34});
  132. CODES.put('?', new byte[]{(byte)36});
  133. CODES.put('@', new byte[]{(byte)38});
  134. CODES.put('[', new byte[]{(byte)39});
  135. CODES.put('\\', new byte[]{(byte)41});
  136. CODES.put(']', new byte[]{(byte)42});
  137. CODES.put('+', new byte[]{(byte)44});
  138. CODES.put('<', new byte[]{(byte)46});
  139. CODES.put('=', new byte[]{(byte)48});
  140. CODES.put('>', new byte[]{(byte)50});
  141. CODES.put('0', new byte[]{(byte)54});
  142. CODES.put('1', new byte[]{(byte)56});
  143. CODES.put('2', new byte[]{(byte)58});
  144. CODES.put('3', new byte[]{(byte)60});
  145. CODES.put('4', new byte[]{(byte)62});
  146. CODES.put('5', new byte[]{(byte)64});
  147. CODES.put('6', new byte[]{(byte)66});
  148. CODES.put('7', new byte[]{(byte)68});
  149. CODES.put('8', new byte[]{(byte)70});
  150. CODES.put('9', new byte[]{(byte)72});
  151. CODES.put('A', new byte[]{(byte)74});
  152. CODES.put('B', new byte[]{(byte)76});
  153. CODES.put('C', new byte[]{(byte)77});
  154. CODES.put('D', new byte[]{(byte)79});
  155. CODES.put('E', new byte[]{(byte)81});
  156. CODES.put('F', new byte[]{(byte)83});
  157. CODES.put('G', new byte[]{(byte)85});
  158. CODES.put('H', new byte[]{(byte)87});
  159. CODES.put('I', new byte[]{(byte)89});
  160. CODES.put('J', new byte[]{(byte)91});
  161. CODES.put('K', new byte[]{(byte)92});
  162. CODES.put('L', new byte[]{(byte)94});
  163. CODES.put('M', new byte[]{(byte)96});
  164. CODES.put('N', new byte[]{(byte)98});
  165. CODES.put('O', new byte[]{(byte)100});
  166. CODES.put('P', new byte[]{(byte)102});
  167. CODES.put('Q', new byte[]{(byte)104});
  168. CODES.put('R', new byte[]{(byte)105});
  169. CODES.put('S', new byte[]{(byte)107});
  170. CODES.put('T', new byte[]{(byte)109});
  171. CODES.put('U', new byte[]{(byte)111});
  172. CODES.put('V', new byte[]{(byte)113});
  173. CODES.put('W', new byte[]{(byte)115});
  174. CODES.put('X', new byte[]{(byte)117});
  175. CODES.put('Y', new byte[]{(byte)118});
  176. CODES.put('Z', new byte[]{(byte)120});
  177. CODES_EXT.put('\'', new byte[]{(byte)6, (byte)128});
  178. CODES_EXT.put('-', new byte[]{(byte)6, (byte)130});
  179. }
  180. /** owning table */
  181. private final Table _table;
  182. /** Page number of the index data */
  183. private int _pageNumber;
  184. /** Number of rows in the index
  185. NOTE: this does not actually seem to be the row count, unclear what the
  186. value means*/
  187. private int _rowCount;
  188. /** sorted collection of index entries. this is kept in a list instead of a
  189. SortedSet because the SortedSet has lame traversal utilities */
  190. private final List<Entry> _entries = new ArrayList<Entry>();
  191. /** Map of columns to flags */
  192. private final Map<Column, Byte> _columns = new LinkedHashMap<Column, Byte>();
  193. /** 0-based index number */
  194. private int _indexNumber;
  195. /** Index name */
  196. private String _name;
  197. /** is this index a primary key */
  198. private boolean _primaryKey;
  199. /** <code>true</code> if the index entries have been initialized,
  200. <code>false</code> otherwise */
  201. private boolean _initialized;
  202. /** modification count for the table, keeps cursors up-to-date */
  203. private int _modCount;
  204. /** FIXME, for now, we can't write multi-page indexes or indexes using the funky primary key compression scheme */
  205. boolean _readOnly;
  206. public Index(Table table) {
  207. _table = table;
  208. }
  209. public Table getTable() {
  210. return _table;
  211. }
  212. public JetFormat getFormat() {
  213. return getTable().getFormat();
  214. }
  215. public PageChannel getPageChannel() {
  216. return getTable().getPageChannel();
  217. }
  218. public void setIndexNumber(int indexNumber) {
  219. _indexNumber = indexNumber;
  220. }
  221. public int getIndexNumber() {
  222. return _indexNumber;
  223. }
  224. public void setRowCount(int rowCount) {
  225. _rowCount = rowCount;
  226. }
  227. public int getRowCount() {
  228. return _rowCount;
  229. }
  230. /**
  231. * Note, there may still be some issues around the name of an index, this
  232. * information may not be correct. I've done a variety of testing comparing
  233. * the index name to what ms access shows, and i think the data is being
  234. * parsed correctly, but sometimes access comes up with a completely
  235. * different index name, hence my lack of confidence in this method. (of
  236. * course, access could also just be doing some monkeying under the
  237. * hood...).
  238. */
  239. public String getName() {
  240. return _name;
  241. }
  242. public void setName(String name) {
  243. _name = name;
  244. }
  245. public boolean isPrimaryKey() {
  246. return _primaryKey;
  247. }
  248. public void setPrimaryKey(boolean newPrimaryKey) {
  249. _primaryKey = newPrimaryKey;
  250. }
  251. /**
  252. * Returns the Columns for this index (unmodifiable)
  253. */
  254. public Collection<Column> getColumns() {
  255. return Collections.unmodifiableCollection(_columns.keySet());
  256. }
  257. /**
  258. * Returns the number of index entries in the index. Only called by unit
  259. * tests.
  260. * <p>
  261. * Forces index initialization.
  262. */
  263. int getEntryCount()
  264. throws IOException
  265. {
  266. initialize();
  267. return _entries.size();
  268. }
  269. public boolean isInitialized() {
  270. return _initialized;
  271. }
  272. /**
  273. * Forces initialization of this index (actual parsing of index pages).
  274. * normally, the index will not be initialized until the entries are
  275. * actually needed.
  276. */
  277. public void initialize() throws IOException {
  278. if(!_initialized) {
  279. readIndexEntries();
  280. _initialized = true;
  281. }
  282. }
  283. /**
  284. * Writes the current index state to the database.
  285. * <p>
  286. * Forces index initialization.
  287. */
  288. public void update() throws IOException {
  289. // make sure we've parsed the entries
  290. initialize();
  291. if(_readOnly) {
  292. throw new UnsupportedOperationException(
  293. "FIXME cannot write indexes of this type yet");
  294. }
  295. getPageChannel().writePage(write(), _pageNumber);
  296. }
  297. /**
  298. * Write this index out to a buffer
  299. */
  300. private ByteBuffer write() throws IOException {
  301. ByteBuffer buffer = getPageChannel().createPageBuffer();
  302. buffer.put((byte) 0x04); //Page type
  303. buffer.put((byte) 0x01); //Unknown
  304. buffer.putShort((short) 0); //Free space
  305. buffer.putInt(getTable().getTableDefPageNumber());
  306. buffer.putInt(0); //Prev page
  307. buffer.putInt(0); //Next page
  308. buffer.putInt(0); //Leaf page
  309. buffer.putInt(0); //Unknown
  310. buffer.put((byte) 0); // compressed byte count
  311. buffer.put((byte) 0); //Unknown
  312. buffer.put((byte) 0); //Unknown
  313. byte[] entryMask = new byte[getFormat().SIZE_INDEX_ENTRY_MASK];
  314. int totalSize = 0;
  315. for(Entry entry : _entries) {
  316. int size = entry.size();
  317. totalSize += size;
  318. int idx = totalSize / 8;
  319. if(idx >= entryMask.length) {
  320. throw new UnsupportedOperationException(
  321. "FIXME cannot write large index yet");
  322. }
  323. entryMask[idx] |= (1 << (totalSize % 8));
  324. }
  325. buffer.put(entryMask);
  326. for(Entry entry : _entries) {
  327. entry.write(buffer);
  328. }
  329. buffer.putShort(2, (short) (getFormat().PAGE_SIZE - buffer.position()));
  330. return buffer;
  331. }
  332. /**
  333. * Read 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. for (int i = 0; i < MAX_COLUMNS; i++) {
  341. short columnNumber = tableBuffer.getShort();
  342. Byte flags = Byte.valueOf(tableBuffer.get());
  343. if (columnNumber != COLUMN_UNUSED) {
  344. _columns.put(availableColumns.get(columnNumber), flags);
  345. }
  346. }
  347. tableBuffer.getInt(); //Forward past Unknown
  348. _pageNumber = tableBuffer.getInt();
  349. tableBuffer.position(tableBuffer.position() + 10); //Forward past other stuff
  350. }
  351. /**
  352. * Reads the actual index entries.
  353. */
  354. private void readIndexEntries()
  355. throws IOException
  356. {
  357. // use sorted set initially to do the bulk of the sorting
  358. SortedSet<Entry> tmpEntries = new TreeSet<Entry>();
  359. ByteBuffer indexPage = getPageChannel().createPageBuffer();
  360. // find first leaf page
  361. int leafPageNumber = _pageNumber;
  362. while(true) {
  363. getPageChannel().readPage(indexPage, leafPageNumber);
  364. if(indexPage.get(0) == INDEX_NODE_PAGE_TYPE) {
  365. // FIXME we can't modify this index at this point in time
  366. _readOnly = true;
  367. // found another node page
  368. leafPageNumber = readNodePage(indexPage);
  369. } else {
  370. // found first leaf
  371. indexPage.rewind();
  372. break;
  373. }
  374. }
  375. // read all leaf pages
  376. while(true) {
  377. leafPageNumber = readLeafPage(indexPage, tmpEntries);
  378. if(leafPageNumber != 0) {
  379. // FIXME we can't modify this index at this point in time
  380. _readOnly = true;
  381. // found another one
  382. getPageChannel().readPage(indexPage, leafPageNumber);
  383. } else {
  384. // all done
  385. break;
  386. }
  387. }
  388. // dump all the entries (sorted) into the actual _entries list
  389. _entries.addAll(tmpEntries);
  390. }
  391. /**
  392. * Reads the first entry off of an index node page and returns the next page
  393. * number.
  394. */
  395. private int readNodePage(ByteBuffer nodePage)
  396. throws IOException
  397. {
  398. if(nodePage.get(0) != INDEX_NODE_PAGE_TYPE) {
  399. throw new IOException("expected index node page, found " +
  400. nodePage.get(0));
  401. }
  402. List<NodeEntry> nodeEntries = new ArrayList<NodeEntry>();
  403. readIndexPage(nodePage, false, null, nodeEntries);
  404. // grab the first entry
  405. // FIXME, need to parse all...?
  406. return nodeEntries.get(0).getSubPageNumber();
  407. }
  408. /**
  409. * Reads an index leaf page.
  410. * @return the next leaf page number, 0 if none
  411. */
  412. private int readLeafPage(ByteBuffer leafPage, Collection<Entry> entries)
  413. throws IOException
  414. {
  415. if(leafPage.get(0) != INDEX_LEAF_PAGE_TYPE) {
  416. throw new IOException("expected index leaf page, found " +
  417. leafPage.get(0));
  418. }
  419. // note, "header" data is in LITTLE_ENDIAN format, entry data is in
  420. // BIG_ENDIAN format
  421. int nextLeafPage = leafPage.getInt(getFormat().OFFSET_NEXT_INDEX_LEAF_PAGE);
  422. readIndexPage(leafPage, true, entries, null);
  423. return nextLeafPage;
  424. }
  425. /**
  426. * Reads an index page, populating the correct collection based on the page
  427. * type (node or leaf).
  428. */
  429. private void readIndexPage(ByteBuffer indexPage, boolean isLeaf,
  430. Collection<Entry> entries,
  431. Collection<NodeEntry> nodeEntries)
  432. throws IOException
  433. {
  434. // note, "header" data is in LITTLE_ENDIAN format, entry data is in
  435. // BIG_ENDIAN format
  436. int numCompressedBytes = indexPage.get(
  437. getFormat().OFFSET_INDEX_COMPRESSED_BYTE_COUNT);
  438. int entryMaskLength = getFormat().SIZE_INDEX_ENTRY_MASK;
  439. int entryMaskPos = getFormat().OFFSET_INDEX_ENTRY_MASK;
  440. int entryPos = entryMaskPos + getFormat().SIZE_INDEX_ENTRY_MASK;
  441. int lastStart = 0;
  442. byte[] valuePrefix = null;
  443. boolean firstEntry = true;
  444. for (int i = 0; i < entryMaskLength; i++) {
  445. byte entryMask = indexPage.get(entryMaskPos + i);
  446. for (int j = 0; j < 8; j++) {
  447. if ((entryMask & (1 << j)) != 0) {
  448. int length = i * 8 + j - lastStart;
  449. indexPage.position(entryPos + lastStart);
  450. if(isLeaf) {
  451. entries.add(new Entry(indexPage, valuePrefix, _columns));
  452. } else {
  453. nodeEntries.add(new NodeEntry(indexPage, valuePrefix, _columns));
  454. }
  455. // read any shared "compressed" bytes
  456. if(firstEntry) {
  457. firstEntry = false;
  458. if(numCompressedBytes > 0) {
  459. // FIXME we can't modify this index at this point in time
  460. _readOnly = true;
  461. valuePrefix = new byte[numCompressedBytes];
  462. indexPage.position(entryPos + lastStart);
  463. indexPage.get(valuePrefix);
  464. }
  465. }
  466. lastStart += length;
  467. }
  468. }
  469. }
  470. }
  471. /**
  472. * Adds a row to this index
  473. * <p>
  474. * Forces index initialization.
  475. *
  476. * @param row Row to add
  477. * @param pageNumber Page number on which the row is stored
  478. * @param rowNumber Row number at which the row is stored
  479. */
  480. public void addRow(Object[] row, RowId rowId)
  481. throws IOException
  482. {
  483. // make sure we've parsed the entries
  484. initialize();
  485. Entry newEntry = new Entry(row, rowId, _columns);
  486. if(addEntry(newEntry)) {
  487. ++_rowCount;
  488. ++_modCount;
  489. } else {
  490. LOG.warn("Added duplicate index entry " + newEntry + " for row: " +
  491. Arrays.asList(row));
  492. }
  493. }
  494. /**
  495. * Removes a row from this index
  496. * <p>
  497. * Forces index initialization.
  498. *
  499. * @param row Row to remove
  500. * @param pageNumber Page number on which the row is removed
  501. * @param rowNumber Row number at which the row is removed
  502. */
  503. public void deleteRow(Object[] row, RowId rowId)
  504. throws IOException
  505. {
  506. // make sure we've parsed the entries
  507. initialize();
  508. Entry oldEntry = new Entry(row, rowId, _columns);
  509. if(removeEntry(oldEntry)) {
  510. --_rowCount;
  511. ++_modCount;
  512. } else {
  513. LOG.warn("Failed removing index entry " + oldEntry + " for row: " +
  514. Arrays.asList(row));
  515. }
  516. }
  517. /**
  518. * Gets a new cursor for this index.
  519. * <p>
  520. * Forces index initialization.
  521. */
  522. public EntryCursor cursor()
  523. throws IOException
  524. {
  525. initialize();
  526. return new EntryCursor();
  527. }
  528. /**
  529. * Finds the index of given entry in the _entries list.
  530. * @return the index if found, (-<insertion_point> - 1) if not found
  531. */
  532. private int findEntry(Entry entry) {
  533. return Collections.binarySearch(_entries, entry);
  534. }
  535. /**
  536. * Returns the valid insertion point for an index indicating a missing
  537. * entry.
  538. */
  539. private static int missingIndexToInsertionPoint(int idx) {
  540. return -(idx + 1);
  541. }
  542. /**
  543. * Adds an entry to the _entries list, maintaining the order.
  544. */
  545. private boolean addEntry(Entry newEntry) {
  546. int idx = findEntry(newEntry);
  547. if(idx < 0) {
  548. // this is a new entry
  549. idx = missingIndexToInsertionPoint(idx);
  550. _entries.add(idx, newEntry);
  551. return true;
  552. }
  553. return false;
  554. }
  555. /**
  556. * Removes an entry from the _entries list, maintaining the order. Will
  557. * search by RowId if entry is not found in case a partial entry was
  558. * provided.
  559. */
  560. private boolean removeEntry(Entry oldEntry)
  561. {
  562. int idx = findEntry(oldEntry);
  563. boolean removed = false;
  564. if(idx < 0) {
  565. // the caller may have only read some of the row data, if this is the
  566. // case, just search for the page/row numbers
  567. for(Iterator<Entry> iter = _entries.iterator(); iter.hasNext(); ) {
  568. Entry entry = iter.next();
  569. if(entry.getRowId().equals(oldEntry.getRowId())) {
  570. iter.remove();
  571. removed = true;
  572. break;
  573. }
  574. }
  575. } else {
  576. // found it!
  577. _entries.remove(idx);
  578. removed = true;
  579. }
  580. return removed;
  581. }
  582. /**
  583. * Constructs an array of values appropriate for this index from the given
  584. * column values, expected to match the columns for this index.
  585. * @return the appropriate sparse array of data
  586. * @throws IllegalArgumentException if the wrong number of values are
  587. * provided
  588. */
  589. public Object[] constructIndexRow(Object... values)
  590. {
  591. if(values.length != _columns.size()) {
  592. throw new IllegalArgumentException(
  593. "Wrong number of column values given " + values.length +
  594. ", expected " + _columns.size());
  595. }
  596. int valIdx = 0;
  597. Object[] idxRow = new Object[getTable().getMaxColumnCount()];
  598. for(Column col : _columns.keySet()) {
  599. idxRow[col.getColumnNumber()] = values[valIdx++];
  600. }
  601. return idxRow;
  602. }
  603. /**
  604. * Constructs an array of values appropriate for this index from the given
  605. * column value.
  606. * @return the appropriate sparse array of data or {@code null} if not all
  607. * columns for this index were provided
  608. */
  609. public Object[] constructIndexRow(String colName, Object value)
  610. {
  611. return constructIndexRow(Collections.singletonMap(colName, value));
  612. }
  613. /**
  614. * Constructs an array of values appropriate for this index from the given
  615. * column values.
  616. * @return the appropriate sparse array of data or {@code null} if not all
  617. * columns for this index were provided
  618. */
  619. public Object[] constructIndexRow(Map<String,Object> row)
  620. {
  621. for(Column col : _columns.keySet()) {
  622. if(!row.containsKey(col.getName())) {
  623. return null;
  624. }
  625. }
  626. Object[] idxRow = new Object[getTable().getMaxColumnCount()];
  627. for(Column col : _columns.keySet()) {
  628. idxRow[col.getColumnNumber()] = row.get(col.getName());
  629. }
  630. return idxRow;
  631. }
  632. @Override
  633. public String toString() {
  634. StringBuilder rtn = new StringBuilder();
  635. rtn.append("\tName: " + _name);
  636. rtn.append("\n\tNumber: " + _indexNumber);
  637. rtn.append("\n\tPage number: " + _pageNumber);
  638. rtn.append("\n\tIs Primary Key: " + _primaryKey);
  639. rtn.append("\n\tColumns: " + _columns);
  640. rtn.append("\n\tInitialized: " + _initialized);
  641. rtn.append("\n\tEntries: " + _entries);
  642. rtn.append("\n\n");
  643. return rtn.toString();
  644. }
  645. public int compareTo(Index other) {
  646. if (_indexNumber > other.getIndexNumber()) {
  647. return 1;
  648. } else if (_indexNumber < other.getIndexNumber()) {
  649. return -1;
  650. } else {
  651. return 0;
  652. }
  653. }
  654. private static void checkColumnType(Column col)
  655. throws IOException
  656. {
  657. if(col.isVariableLength() && !isTextualColumn(col)) {
  658. throw new IOException("unsupported index column type: " +
  659. col.getType());
  660. }
  661. }
  662. private static boolean isTextualColumn(Column col) {
  663. return((col.getType() == DataType.TEXT) ||
  664. (col.getType() == DataType.MEMO));
  665. }
  666. // FIXME
  667. // private static boolean isFloatingPointColumn(Column col) {
  668. // return((col.getType() == DataType.FLOAT) ||
  669. // (col.getType() == DataType.DOUBLE));
  670. // }
  671. /**
  672. * Converts an index value for a fixed column into the index bytes
  673. */
  674. // FIXME
  675. // private static void toIndexFixedValue(
  676. // Entry.FixedEntryColumn entryCol,
  677. // Object value,
  678. // byte flags)
  679. // throws IOException
  680. // {
  681. // if(value == null) {
  682. // // nothing more to do
  683. // return;
  684. // }
  685. // Column column = entryCol._column;
  686. // // if (value instanceof Integer) {
  687. // // value = Integer.valueOf((int) (((Integer) value).longValue() -
  688. // // ((long) Integer.MAX_VALUE + 1L)));
  689. // // } else if (value instanceof Short) {
  690. // // value = Short.valueOf((short) (((Short) value).longValue() -
  691. // // ((long) Integer.MAX_VALUE + 1L)));
  692. // // }
  693. // byte[] value = column.write(value, 0, ByteOrder.BIG_ENDIAN);
  694. // if(isFloatingPointColumn(column)) {
  695. // if(((Number)value).doubleValue() < 0) {
  696. // // invert all the bits
  697. // for(int i = 0; i < value.length; ++i) {
  698. // value[i] = (byte)~value[i];
  699. // }
  700. // }
  701. // } else {
  702. // // invert the highest bit
  703. // value[0] = (byte)((value[0] ^ 0x80) & 0xFF);
  704. // }
  705. // }
  706. /**
  707. * Converts an index value for a text column into the value which
  708. * is based on a variety of nifty codes.
  709. */
  710. private static void toIndexTextValue(
  711. Entry.TextEntryColumn entryCol,
  712. Object value,
  713. byte flags)
  714. throws IOException
  715. {
  716. if(value == null) {
  717. // nothing more to do
  718. return;
  719. }
  720. // first, convert to uppercase string (all text characters are uppercase)
  721. String str = Column.toCharSequence(value).toString().toUpperCase();
  722. // now, convert each character to a "code" of one or more bytes
  723. ByteArrayOutputStream bout = new ByteArrayOutputStream(str.length());
  724. ByteArrayOutputStream boutExt = null;
  725. for(int i = 0; i < str.length(); ++i) {
  726. char c = str.charAt(i);
  727. byte[] bytes = CODES.get(c);
  728. if(bytes != null) {
  729. bout.write(bytes);
  730. } else {
  731. bytes = CODES_EXT.get(c);
  732. if(bytes != null) {
  733. // add extra chars
  734. if(boutExt == null) {
  735. boutExt = new ByteArrayOutputStream(7);
  736. // setup funky extra bytes
  737. boutExt.write(1);
  738. boutExt.write(1);
  739. boutExt.write(1);
  740. }
  741. // FIXME, complete me..
  742. // no clue where this comes from...
  743. int offset = 7 + (i * 4);
  744. boutExt.write((byte)0x80);
  745. boutExt.write((byte)offset);
  746. boutExt.write(bytes);
  747. } else {
  748. throw new IOException("unmapped string index value");
  749. }
  750. }
  751. }
  752. entryCol._valueBytes = bout.toByteArray();
  753. if(boutExt != null) {
  754. entryCol._extraBytes = boutExt.toByteArray();
  755. }
  756. }
  757. /**
  758. * Creates one of the special index entries.
  759. */
  760. private static Entry createSpecialEntry(RowId rowId) {
  761. try {
  762. return new Entry(null, rowId, null);
  763. } catch(IOException e) {
  764. // should never happen
  765. throw new IllegalStateException(e);
  766. }
  767. }
  768. /**
  769. * A single leaf entry in an index (points to a single row)
  770. */
  771. public static class Entry implements Comparable<Entry>
  772. {
  773. /** page/row on which this row is stored */
  774. private final RowId _rowId;
  775. /** Columns that are indexed */
  776. private final List<EntryColumn> _entryColumns;
  777. /**
  778. * Create a new entry
  779. * @param values Indexed row values
  780. * @param page Page number on which the row is stored
  781. * @param rowNumber Row number at which the row is stored
  782. */
  783. private Entry(Object[] values, RowId rowId,
  784. Map<Column, Byte> columns)
  785. throws IOException
  786. {
  787. _rowId = rowId;
  788. if(values != null) {
  789. _entryColumns = new ArrayList<EntryColumn>();
  790. for(Map.Entry<Column, Byte> entry : columns.entrySet()) {
  791. Column col = entry.getKey();
  792. Byte flags = entry.getValue();
  793. Object value = values[col.getColumnNumber()];
  794. _entryColumns.add(newEntryColumn(col).initFromValue(value, flags));
  795. }
  796. } else {
  797. if(!_rowId.isValid()) {
  798. // this is a "special" entry (first/last)
  799. _entryColumns = null;
  800. } else {
  801. throw new IllegalArgumentException("Values was null");
  802. }
  803. }
  804. }
  805. /**
  806. * Read an existing entry in from a buffer
  807. */
  808. private Entry(ByteBuffer buffer, byte[] valuePrefix,
  809. Map<Column, Byte> columns)
  810. throws IOException
  811. {
  812. _entryColumns = new ArrayList<EntryColumn>();
  813. for(Map.Entry<Column, Byte> entry : columns.entrySet()) {
  814. Column col = entry.getKey();
  815. Byte flags = entry.getValue();
  816. _entryColumns.add(newEntryColumn(col)
  817. .initFromBuffer(buffer, flags, valuePrefix));
  818. }
  819. int page = ByteUtil.get3ByteInt(buffer, ByteOrder.BIG_ENDIAN);
  820. int row = buffer.get();
  821. _rowId = new RowId(page, row);
  822. }
  823. /**
  824. * Instantiate the correct EntryColumn for the given column type
  825. */
  826. private EntryColumn newEntryColumn(Column col) throws IOException
  827. {
  828. if(isTextualColumn(col)) {
  829. return new TextEntryColumn(col);
  830. }
  831. return new FixedEntryColumn(col);
  832. }
  833. protected List<EntryColumn> getEntryColumns() {
  834. return _entryColumns;
  835. }
  836. public RowId getRowId() {
  837. return _rowId;
  838. }
  839. public boolean isValid() {
  840. return(_entryColumns != null);
  841. }
  842. /**
  843. * Size of this entry in the db.
  844. */
  845. protected int size() {
  846. int rtn = 4;
  847. for(EntryColumn entryCol : _entryColumns) {
  848. rtn += entryCol.size();
  849. }
  850. return rtn;
  851. }
  852. /**
  853. * Write this entry into a buffer
  854. */
  855. protected void write(ByteBuffer buffer) throws IOException {
  856. for(EntryColumn entryCol : _entryColumns) {
  857. entryCol.write(buffer);
  858. }
  859. int page = getRowId().getPageNumber();
  860. buffer.put((byte) (page >>> 16));
  861. buffer.put((byte) (page >>> 8));
  862. buffer.put((byte) page);
  863. buffer.put((byte)getRowId().getRowNumber());
  864. }
  865. @Override
  866. public String toString() {
  867. return ("RowId = " + _rowId + ", Columns = " + _entryColumns + "\n");
  868. }
  869. @Override
  870. public int hashCode() {
  871. return _rowId.hashCode();
  872. }
  873. @Override
  874. public boolean equals(Object o) {
  875. return((this == o) ||
  876. ((o != null) && (getClass() == o.getClass()) &&
  877. (compareTo((Entry)o) == 0)));
  878. }
  879. public int compareTo(Entry other) {
  880. if (this == other) {
  881. return 0;
  882. }
  883. // note, if the one or both of the entries are not valid, they are
  884. // "special" entries, which are handled below
  885. if(isValid() && other.isValid()) {
  886. // comparing two normal entries
  887. Iterator<EntryColumn> myIter = _entryColumns.iterator();
  888. Iterator<EntryColumn> otherIter = other.getEntryColumns().iterator();
  889. while (myIter.hasNext()) {
  890. if (!otherIter.hasNext()) {
  891. throw new IllegalArgumentException(
  892. "Trying to compare index entries with a different number of entry columns");
  893. }
  894. EntryColumn myCol = myIter.next();
  895. EntryColumn otherCol = otherIter.next();
  896. int i = myCol.compareTo(otherCol);
  897. if (i != 0) {
  898. return i;
  899. }
  900. }
  901. // if entry columns are equal, sort by rowIds
  902. return _rowId.compareTo(other.getRowId());
  903. }
  904. // this is the odd case where mixed entries are being compared. if both
  905. // entries are invalid or the rowIds are not equal, then use the rowId
  906. // comparison.
  907. int rowCmp = _rowId.compareTo(other.getRowId());
  908. if((isValid() == other.isValid()) || (rowCmp != 0)) {
  909. return rowCmp;
  910. }
  911. // at this point, the rowId's are equal, but the validity is not. this
  912. // will happen when a "special" entry is compared to something created
  913. // by EntryCursor.afterEntry or EntryCursor.beforeEntry. in this case,
  914. // the FIRST_ENTRY is always least and the LAST_ENTRY is always
  915. // greatest.
  916. int cmp = 0;
  917. Entry invalid = null;
  918. if(!isValid()) {
  919. cmp = -1;
  920. invalid = this;
  921. } else {
  922. cmp = 1;
  923. invalid = other;
  924. }
  925. return (cmp * (invalid.equals(FIRST_ENTRY) ? 1 : -1));
  926. }
  927. /**
  928. * A single column value within an index Entry; encapsulates column
  929. * definition and column value.
  930. */
  931. private abstract class EntryColumn implements Comparable<EntryColumn>
  932. {
  933. /** Column definition */
  934. protected Column _column;
  935. protected EntryColumn(Column col) throws IOException {
  936. checkColumnType(col);
  937. _column = col;
  938. }
  939. public int size() {
  940. int size = 1;
  941. if (!isNullValue()) {
  942. size += nonNullSize();
  943. }
  944. return size;
  945. }
  946. /**
  947. * Initialize using a new value
  948. */
  949. protected abstract EntryColumn initFromValue(Object value,
  950. byte flags)
  951. throws IOException;
  952. /**
  953. * Initialize from a buffer
  954. */
  955. protected abstract EntryColumn initFromBuffer(ByteBuffer buffer,
  956. byte flags,
  957. byte[] valuePrefix)
  958. throws IOException;
  959. protected abstract boolean isNullValue();
  960. /**
  961. * Write this entry column to a buffer
  962. */
  963. public void write(ByteBuffer buffer) throws IOException
  964. {
  965. if(isNullValue()) {
  966. buffer.put((byte)0);
  967. } else {
  968. writeNonNullValue(buffer);
  969. }
  970. }
  971. /**
  972. * Write this non-null entry column to a buffer
  973. */
  974. protected abstract void writeNonNullValue(ByteBuffer buffer)
  975. throws IOException;
  976. protected abstract int nonNullSize();
  977. public abstract int compareTo(EntryColumn other);
  978. }
  979. /**
  980. * A single fixed column value within an index Entry; encapsulates column
  981. * definition and column value.
  982. */
  983. private final class FixedEntryColumn extends EntryColumn
  984. {
  985. /** Column value */
  986. private Comparable _value;
  987. private FixedEntryColumn(Column col) throws IOException {
  988. super(col);
  989. if(isTextualColumn(col)) {
  990. throw new IOException("must be fixed column");
  991. }
  992. }
  993. /**
  994. * Initialize using a new value
  995. */
  996. @Override
  997. protected EntryColumn initFromValue(Object value, byte flags)
  998. throws IOException
  999. {
  1000. _value = (Comparable)value;
  1001. return this;
  1002. }
  1003. /**
  1004. * Initialize from a buffer
  1005. */
  1006. @Override
  1007. protected EntryColumn initFromBuffer(ByteBuffer buffer,
  1008. byte flags,
  1009. byte[] valuePrefix)
  1010. throws IOException
  1011. {
  1012. byte flag = ((valuePrefix == null) ? buffer.get() : valuePrefix[0]);
  1013. // FIXME, reverse is 0x80, reverse null is 0xFF
  1014. if (flag != (byte) 0) {
  1015. byte[] data = new byte[_column.getType().getFixedSize()];
  1016. int dataOffset = 0;
  1017. if((valuePrefix != null) && (valuePrefix.length > 1)) {
  1018. System.arraycopy(valuePrefix, 1, data, 0,
  1019. (valuePrefix.length - 1));
  1020. dataOffset += (valuePrefix.length - 1);
  1021. }
  1022. buffer.get(data, dataOffset, (data.length - dataOffset));
  1023. _value = (Comparable) _column.read(data, ByteOrder.BIG_ENDIAN);
  1024. //ints and shorts are stored in index as value + 2147483648
  1025. if (_value instanceof Integer) {
  1026. _value = Integer.valueOf((int) (((Integer) _value).longValue() +
  1027. (long) Integer.MAX_VALUE + 1L));
  1028. } else if (_value instanceof Short) {
  1029. _value = Short.valueOf((short) (((Short) _value).longValue() +
  1030. (long) Integer.MAX_VALUE + 1L));
  1031. }
  1032. }
  1033. return this;
  1034. }
  1035. @Override
  1036. protected boolean isNullValue() {
  1037. return(_value == null);
  1038. }
  1039. /**
  1040. * Write this entry column to a buffer
  1041. */
  1042. @Override
  1043. protected void writeNonNullValue(ByteBuffer buffer) throws IOException {
  1044. buffer.put((byte) 0x7F);
  1045. Comparable value = _value;
  1046. if (value instanceof Integer) {
  1047. value = Integer.valueOf((int) (((Integer) value).longValue() -
  1048. ((long) Integer.MAX_VALUE + 1L)));
  1049. } else if (value instanceof Short) {
  1050. value = Short.valueOf((short) (((Short) value).longValue() -
  1051. ((long) Integer.MAX_VALUE + 1L)));
  1052. }
  1053. buffer.put(_column.write(value, 0, ByteOrder.BIG_ENDIAN));
  1054. }
  1055. @Override
  1056. protected int nonNullSize() {
  1057. return _column.getType().getFixedSize();
  1058. }
  1059. @Override
  1060. public String toString() {
  1061. return String.valueOf(_value);
  1062. }
  1063. @Override
  1064. public int compareTo(EntryColumn other) {
  1065. return new CompareToBuilder()
  1066. .append(_value, ((FixedEntryColumn)other)._value)
  1067. .toComparison();
  1068. }
  1069. }
  1070. /**
  1071. * A single textual column value within an index Entry; encapsulates
  1072. * column definition and column value.
  1073. */
  1074. private final class TextEntryColumn extends EntryColumn
  1075. {
  1076. /** the string byte codes */
  1077. private byte[] _valueBytes;
  1078. /** extra column bytes */
  1079. private byte[] _extraBytes;
  1080. private TextEntryColumn(Column col) throws IOException {
  1081. super(col);
  1082. if(!isTextualColumn(col)) {
  1083. throw new IOException("must be textual column");
  1084. }
  1085. }
  1086. /**
  1087. * Initialize using a new value
  1088. */
  1089. @Override
  1090. protected EntryColumn initFromValue(Object value,
  1091. byte flags)
  1092. throws IOException
  1093. {
  1094. // convert string to byte array
  1095. toIndexTextValue(this, value, flags);
  1096. return this;
  1097. }
  1098. /**
  1099. * Initialize from a buffer
  1100. */
  1101. @Override
  1102. protected EntryColumn initFromBuffer(ByteBuffer buffer,
  1103. byte flags,
  1104. byte[] valuePrefix)
  1105. throws IOException
  1106. {
  1107. byte flag = ((valuePrefix == null) ? buffer.get() : valuePrefix[0]);
  1108. // FIXME, reverse is 0x80, reverse null is 0xFF
  1109. // end flag is FE, post extra bytes is FF 00
  1110. // extra bytes are inverted, so are normal bytes
  1111. if (flag != (byte) 0) {
  1112. int endPos = buffer.position();
  1113. while(buffer.get(endPos) != (byte) 1) {
  1114. ++endPos;
  1115. }
  1116. // FIXME, prefix could probably include extraBytes...
  1117. // read index bytes
  1118. int numPrefixBytes = ((valuePrefix == null) ? 0 :
  1119. (valuePrefix.length - 1));
  1120. int dataOffset = 0;
  1121. _valueBytes = new byte[(endPos - buffer.position()) +
  1122. numPrefixBytes];
  1123. if(numPrefixBytes > 0) {
  1124. System.arraycopy(valuePrefix, 1, _valueBytes, 0, numPrefixBytes);
  1125. dataOffset += numPrefixBytes;
  1126. }
  1127. buffer.get(_valueBytes, dataOffset,
  1128. (_valueBytes.length - dataOffset));
  1129. // read end codes byte
  1130. buffer.get();
  1131. //Forward past 0x00 (in some cases, there is more data here, which
  1132. //we don't currently understand)
  1133. byte endByte = buffer.get();
  1134. if(endByte != (byte)0x00) {
  1135. endPos = buffer.position() - 1;
  1136. buffer.position(endPos);
  1137. while(buffer.get(endPos) != (byte)0x00) {
  1138. ++endPos;
  1139. }
  1140. _extraBytes = new byte[endPos - buffer.position()];
  1141. buffer.get(_extraBytes);
  1142. // re-get endByte
  1143. buffer.get();
  1144. }
  1145. }
  1146. return this;
  1147. }
  1148. @Override
  1149. protected boolean isNullValue() {
  1150. return(_valueBytes == null);
  1151. }
  1152. /**
  1153. * Write this entry column to a buffer
  1154. */
  1155. @Override
  1156. protected void writeNonNullValue(ByteBuffer buffer) throws IOException {
  1157. buffer.put((byte) 0x7F);
  1158. buffer.put(_valueBytes);
  1159. buffer.put((byte) 1);
  1160. if(_extraBytes != null) {
  1161. buffer.put(_extraBytes);
  1162. }
  1163. buffer.put((byte) 0);
  1164. }
  1165. @Override
  1166. protected int nonNullSize() {
  1167. int rtn = _valueBytes.length + 2;
  1168. if(_extraBytes != null) {
  1169. rtn += _extraBytes.length;
  1170. }
  1171. return rtn;
  1172. }
  1173. @Override
  1174. public String toString() {
  1175. if(_valueBytes == null) {
  1176. return String.valueOf(_valueBytes);
  1177. }
  1178. String rtn = ByteUtil.toHexString(ByteBuffer.wrap(_valueBytes),
  1179. _valueBytes.length);
  1180. if(_extraBytes != null) {
  1181. rtn += " (" + ByteUtil.toHexString(ByteBuffer.wrap(_extraBytes),
  1182. _extraBytes.length) + ")";
  1183. }
  1184. return rtn;
  1185. }
  1186. @Override
  1187. public int compareTo(EntryColumn other) {
  1188. TextEntryColumn textOther = (TextEntryColumn)other;
  1189. int rtn = BYTE_CODE_COMPARATOR.compare(
  1190. _valueBytes, textOther._valueBytes);
  1191. if(rtn != 0) {
  1192. return rtn;
  1193. }
  1194. return BYTE_CODE_COMPARATOR.compare(
  1195. _extraBytes, textOther._extraBytes);
  1196. }
  1197. }
  1198. }
  1199. /**
  1200. * A single node entry in an index (points to a sub-page in the index)
  1201. */
  1202. private final class NodeEntry extends Entry {
  1203. /** index page number of the page to which this node entry refers */
  1204. private final int _subPageNumber;
  1205. /**
  1206. * Read an existing node entry in from a buffer
  1207. */
  1208. private NodeEntry(ByteBuffer buffer, byte[] valuePrefix,
  1209. Map<Column, Byte> columns)
  1210. throws IOException
  1211. {
  1212. super(buffer, valuePrefix, columns);
  1213. _subPageNumber = ByteUtil.getInt(buffer, ByteOrder.BIG_ENDIAN);
  1214. }
  1215. public int getSubPageNumber() {
  1216. return _subPageNumber;
  1217. }
  1218. @Override
  1219. public String toString() {
  1220. return ("Node RowId = " + getRowId() +
  1221. ", SubPage = " + _subPageNumber +
  1222. ", Columns = " + getEntryColumns() + "\n");
  1223. }
  1224. }
  1225. /**
  1226. * Utility class to traverse the entries in the Index. Remains valid in the
  1227. * face of index entry modifications.
  1228. */
  1229. public final class EntryCursor
  1230. {
  1231. /** handler for moving the page cursor forward */
  1232. private final DirHandler _forwardDirHandler = new ForwardDirHandler();
  1233. /** handler for moving the page cursor backward */
  1234. private final DirHandler _reverseDirHandler = new ReverseDirHandler();
  1235. private Position _curPos;
  1236. private Position _prevPos;
  1237. private int _lastModCount;
  1238. private EntryCursor() {
  1239. reset();
  1240. }
  1241. public Index getIndex() {
  1242. return Index.this;
  1243. }
  1244. /**
  1245. * Returns the DirHandler for the given direction
  1246. */
  1247. private DirHandler getDirHandler(boolean moveForward) {
  1248. return (moveForward ? _forwardDirHandler : _reverseDirHandler);
  1249. }
  1250. /**
  1251. * Returns {@code true} if this cursor is up-to-date with respect to its
  1252. * index.
  1253. */
  1254. public boolean isUpToDate() {
  1255. return(Index.this._modCount == _lastModCount);
  1256. }
  1257. public void reset() {
  1258. beforeFirst();
  1259. }
  1260. public void beforeFirst() {
  1261. reset(true);
  1262. }
  1263. public void afterLast() {
  1264. reset(false);
  1265. }
  1266. protected void reset(boolean moveForward) {
  1267. _curPos = getDirHandler(moveForward).getBeginningPosition();
  1268. _prevPos = _curPos;
  1269. _lastModCount = Index.this._modCount;
  1270. }
  1271. /**
  1272. * Repositions the cursor so that the next row will be the first entry
  1273. * >= the given row.
  1274. */
  1275. public void beforeEntry(Object[] row)
  1276. throws IOException
  1277. {
  1278. restorePosition(new Entry(row, RowId.FIRST_ROW_ID, _columns));
  1279. }
  1280. /**
  1281. * Repositions the cursor so that the previous row will be the first
  1282. * entry <= the given row.
  1283. */
  1284. public void afterEntry(Object[] row)
  1285. throws IOException
  1286. {
  1287. restorePosition(new Entry(row, RowId.LAST_ROW_ID, _columns));
  1288. }
  1289. /**
  1290. * @return valid entry if there was entry, {@link Index#LAST_ENTRY}
  1291. * otherwise
  1292. */
  1293. public Entry getNextEntry() {
  1294. return getAnotherEntry(true);
  1295. }
  1296. /**
  1297. * @return valid entry if there was entry, {@link Index#FIRST_ENTRY}
  1298. * otherwise
  1299. */
  1300. public Entry getPreviousEntry() {
  1301. return getAnotherEntry(false);
  1302. }
  1303. /**
  1304. * Restores a current position for the cursor (current position becomes
  1305. * previous position).
  1306. */
  1307. private void restorePosition(Entry curEntry) {
  1308. restorePosition(curEntry, _curPos.getEntry());
  1309. }
  1310. /**
  1311. * Restores a current and previous position for the cursor.
  1312. */
  1313. protected void restorePosition(Entry curEntry, Entry prevEntry)
  1314. {
  1315. if(!curEntry.equals(_curPos.getEntry()) ||
  1316. !prevEntry.equals(_prevPos.getEntry()))
  1317. {
  1318. _prevPos = updatePosition(prevEntry);
  1319. _curPos = updatePosition(curEntry);
  1320. _lastModCount = Index.this._modCount;
  1321. } else {
  1322. checkForModification();
  1323. }
  1324. }
  1325. /**
  1326. * Checks the index for modifications an updates state accordingly.
  1327. */
  1328. private void checkForModification() {
  1329. if(!isUpToDate()) {
  1330. _prevPos = updatePosition(_prevPos.getEntry());
  1331. _curPos = updatePosition(_curPos.getEntry());
  1332. _lastModCount = Index.this._modCount;
  1333. }
  1334. }
  1335. /**
  1336. * Gets an up-to-date position for the given entry.
  1337. */
  1338. private Position updatePosition(Entry entry) {
  1339. int curIdx = FIRST_ENTRY_IDX;
  1340. boolean between = false;
  1341. RowId rowId = entry.getRowId();
  1342. if(entry.isValid()) {
  1343. // find the new position for this entry
  1344. int idx = findEntry(entry);
  1345. if(idx >= 0) {
  1346. curIdx = idx;
  1347. } else {
  1348. // given entry was not found exactly. our current position is now
  1349. // really between two indexes, but we cannot support that as an
  1350. // integer value so we set a flag instead
  1351. curIdx = missingIndexToInsertionPoint(idx);
  1352. between = true;
  1353. }
  1354. } else if(entry.equals(FIRST_ENTRY)) {
  1355. curIdx = FIRST_ENTRY_IDX;
  1356. } else if(entry.equals(LAST_ENTRY)) {
  1357. curIdx = LAST_ENTRY_IDX;
  1358. } else {
  1359. throw new IllegalArgumentException("Invalid entry given: " + entry);
  1360. }
  1361. return new Position(curIdx, entry, between);
  1362. }
  1363. /**
  1364. * Gets another entry in the given direction, returning the new entry.
  1365. */
  1366. private Entry getAnotherEntry(boolean moveForward) {
  1367. DirHandler handler = getDirHandler(moveForward);
  1368. if(_curPos.equals(handler.getEndPosition())) {
  1369. if(!isUpToDate()) {
  1370. restorePosition(_prevPos.getEntry());
  1371. // drop through and retry moving to another entry
  1372. } else {
  1373. // at end, no more
  1374. return _curPos.getEntry();
  1375. }
  1376. }
  1377. checkForModification();
  1378. _prevPos = _curPos;
  1379. _curPos = handler.getAnotherPosition(_curPos.getIndex(),
  1380. _curPos.isBetween());
  1381. return _curPos.getEntry();
  1382. }
  1383. @Override
  1384. public String toString() {
  1385. return getClass().getSimpleName() + " CurPosition " + _curPos +
  1386. ", PrevPosition " + _prevPos;
  1387. }
  1388. /**
  1389. * Handles moving the cursor in a given direction. Separates cursor
  1390. * logic from value storage.
  1391. */
  1392. private abstract class DirHandler {
  1393. public abstract Position getAnotherPosition(int curIdx, boolean between);
  1394. public abstract Position getBeginningPosition();
  1395. public abstract Position getEndPosition();
  1396. protected final Position newPosition(int curIdx) {
  1397. return new Position(curIdx, _entries.get(curIdx));
  1398. }
  1399. protected final Position newForwardPosition(int curIdx) {
  1400. return((curIdx < _entries.size()) ?
  1401. newPosition(curIdx) : LAST_POSITION);
  1402. }
  1403. protected final Position newReversePosition(int curIdx) {
  1404. return ((curIdx >= 0) ?
  1405. newPosition(curIdx) : FIRST_POSITION);
  1406. }
  1407. }
  1408. /**
  1409. * Handles moving the cursor forward.
  1410. */
  1411. private final class ForwardDirHandler extends DirHandler {
  1412. @Override
  1413. public Position getAnotherPosition(int curIdx, boolean between) {
  1414. // note, curIdx does not need to be advanced if it was pointing at a
  1415. // between position
  1416. if(!between) {
  1417. curIdx = ((curIdx == getBeginningPosition().getIndex()) ?
  1418. 0 : (curIdx + 1));
  1419. }
  1420. return newForwardPosition(curIdx);
  1421. }
  1422. @Override
  1423. public Position getBeginningPosition() {
  1424. return FIRST_POSITION;
  1425. }
  1426. @Override
  1427. public Position getEndPosition() {
  1428. return LAST_POSITION;
  1429. }
  1430. }
  1431. /**
  1432. * Handles moving the cursor backward.
  1433. */
  1434. private final class ReverseDirHandler extends DirHandler {
  1435. @Override
  1436. public Position getAnotherPosition(int curIdx, boolean between) {
  1437. // note, we ignore the between flag here because the index will be
  1438. // pointing at the correct next index in either the between or
  1439. // non-between case
  1440. curIdx = ((curIdx == getBeginningPosition().getIndex()) ?
  1441. (_entries.size() - 1) : (curIdx - 1));
  1442. return newReversePosition(curIdx);
  1443. }
  1444. @Override
  1445. public Position getBeginningPosition() {
  1446. return LAST_POSITION;
  1447. }
  1448. @Override
  1449. public Position getEndPosition() {
  1450. return FIRST_POSITION;
  1451. }
  1452. }
  1453. }
  1454. /**
  1455. * Simple value object for maintaining some cursor state.
  1456. */
  1457. private static class Position {
  1458. /** the last known index of the given entry */
  1459. private final int _idx;
  1460. /** the entry at the given index */
  1461. private final Entry _entry;
  1462. /** {@code true} if this entry does not currently exist in the entry list,
  1463. {@code false} otherwise */
  1464. private final boolean _between;
  1465. private Position(int idx, Entry entry) {
  1466. this(idx, entry, false);
  1467. }
  1468. private Position(int idx, Entry entry, boolean between) {
  1469. _idx = idx;
  1470. _entry = entry;
  1471. _between = between;
  1472. }
  1473. public int getIndex() {
  1474. return _idx;
  1475. }
  1476. public Entry getEntry() {
  1477. return _entry;
  1478. }
  1479. public boolean isBetween() {
  1480. return _between;
  1481. }
  1482. @Override
  1483. public int hashCode() {
  1484. return _entry.hashCode();
  1485. }
  1486. @Override
  1487. public boolean equals(Object o) {
  1488. return((this == o) ||
  1489. ((o != null) && (getClass() == o.getClass()) &&
  1490. (_idx == ((Position)o)._idx) &&
  1491. _entry.equals(((Position)o)._entry) &&
  1492. (_between == ((Position)o)._between)));
  1493. }
  1494. @Override
  1495. public String toString() {
  1496. return "Idx = " + _idx + ", Entry = " + _entry + ", Between = " +
  1497. _between;
  1498. }
  1499. }
  1500. }