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 71KB

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