Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

Index.java 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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.util.Collections;
  26. import java.util.List;
  27. import java.util.Map;
  28. import org.apache.commons.logging.Log;
  29. import org.apache.commons.logging.LogFactory;
  30. /**
  31. * Access table (logical) index. Logical indexes are backed for IndexData,
  32. * where one or more logical indexes could be backed by the same data.
  33. *
  34. * @author Tim McCune
  35. */
  36. public class Index implements Comparable<Index> {
  37. protected static final Log LOG = LogFactory.getLog(Index.class);
  38. /** index type for primary key indexes */
  39. static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1;
  40. /** index type for foreign key indexes */
  41. static final byte FOREIGN_KEY_INDEX_TYPE = (byte)2;
  42. /** flag for indicating that updates should cascade in a foreign key index */
  43. private static final byte CASCADE_UPDATES_FLAG = (byte)1;
  44. /** flag for indicating that deletes should cascade in a foreign key index */
  45. private static final byte CASCADE_DELETES_FLAG = (byte)1;
  46. /** index table type for the "primary" table in a foreign key index */
  47. private static final byte PRIMARY_TABLE_TYPE = (byte)1;
  48. /** indicate an invalid index number for foreign key field */
  49. private static final int INVALID_INDEX_NUMBER = -1;
  50. /** the actual data backing this index (more than one index may be backed by
  51. the same data */
  52. private final IndexData _data;
  53. /** 0-based index number */
  54. private final int _indexNumber;
  55. /** the type of the index */
  56. private final byte _indexType;
  57. /** Index name */
  58. private String _name;
  59. /** foreign key reference info, if any */
  60. private final ForeignKeyReference _reference;
  61. protected Index(ByteBuffer tableBuffer, List<IndexData> indexDatas,
  62. JetFormat format)
  63. throws IOException
  64. {
  65. ByteUtil.forward(tableBuffer, format.SKIP_BEFORE_INDEX_SLOT); //Forward past Unknown
  66. _indexNumber = tableBuffer.getInt();
  67. int indexDataNumber = tableBuffer.getInt();
  68. // read foreign key reference info
  69. byte relIndexType = tableBuffer.get();
  70. int relIndexNumber = tableBuffer.getInt();
  71. int relTablePageNumber = tableBuffer.getInt();
  72. byte cascadeUpdatesFlag = tableBuffer.get();
  73. byte cascadeDeletesFlag = tableBuffer.get();
  74. _indexType = tableBuffer.get();
  75. if((_indexType == FOREIGN_KEY_INDEX_TYPE) &&
  76. (relIndexNumber != INVALID_INDEX_NUMBER)) {
  77. _reference = new ForeignKeyReference(
  78. relIndexType, relIndexNumber, relTablePageNumber,
  79. (cascadeUpdatesFlag == CASCADE_UPDATES_FLAG),
  80. (cascadeDeletesFlag == CASCADE_DELETES_FLAG));
  81. } else {
  82. _reference = null;
  83. }
  84. ByteUtil.forward(tableBuffer, format.SKIP_AFTER_INDEX_SLOT); //Skip past Unknown
  85. _data = indexDatas.get(indexDataNumber);
  86. _data.addIndex(this);
  87. }
  88. public IndexData getIndexData() {
  89. return _data;
  90. }
  91. public Table getTable() {
  92. return getIndexData().getTable();
  93. }
  94. public JetFormat getFormat() {
  95. return getTable().getFormat();
  96. }
  97. public PageChannel getPageChannel() {
  98. return getTable().getPageChannel();
  99. }
  100. public int getIndexNumber() {
  101. return _indexNumber;
  102. }
  103. public byte getIndexFlags() {
  104. return getIndexData().getIndexFlags();
  105. }
  106. public int getUniqueEntryCount() {
  107. return getIndexData().getUniqueEntryCount();
  108. }
  109. public int getUniqueEntryCountOffset() {
  110. return getIndexData().getUniqueEntryCountOffset();
  111. }
  112. public String getName() {
  113. return _name;
  114. }
  115. public void setName(String name) {
  116. _name = name;
  117. }
  118. public boolean isPrimaryKey() {
  119. return _indexType == PRIMARY_KEY_INDEX_TYPE;
  120. }
  121. public boolean isForeignKey() {
  122. return _indexType == FOREIGN_KEY_INDEX_TYPE;
  123. }
  124. public ForeignKeyReference getReference() {
  125. return _reference;
  126. }
  127. /**
  128. * @return the Index referenced by this Index's ForeignKeyReference (if it
  129. * has one), otherwise {@code null}.
  130. */
  131. public Index getReferencedIndex() throws IOException {
  132. if(_reference == null) {
  133. return null;
  134. }
  135. Table refTable = getTable().getDatabase().getTable(
  136. _reference.getOtherTablePageNumber());
  137. if(refTable == null) {
  138. throw new IOException("Reference to missing table " +
  139. _reference.getOtherTablePageNumber());
  140. }
  141. Index refIndex = null;
  142. int idxNumber = _reference.getOtherIndexNumber();
  143. for(Index idx : refTable.getIndexes()) {
  144. if(idx.getIndexNumber() == idxNumber) {
  145. refIndex = idx;
  146. break;
  147. }
  148. }
  149. if(refIndex == null) {
  150. throw new IOException("Reference to missing index " + idxNumber +
  151. " on table " + refTable.getName());
  152. }
  153. // finally verify that we found the expected index (should reference this
  154. // index)
  155. ForeignKeyReference otherRef = refIndex.getReference();
  156. if((otherRef == null) ||
  157. (otherRef.getOtherTablePageNumber() !=
  158. getTable().getTableDefPageNumber()) ||
  159. (otherRef.getOtherIndexNumber() != _indexNumber)) {
  160. throw new IOException("Found unexpected index " + refIndex.getName() +
  161. " on table " + refTable.getName() +
  162. " with reference " + otherRef);
  163. }
  164. return refIndex;
  165. }
  166. /**
  167. * Whether or not {@code null} values are actually recorded in the index.
  168. */
  169. public boolean shouldIgnoreNulls() {
  170. return getIndexData().shouldIgnoreNulls();
  171. }
  172. /**
  173. * Whether or not index entries must be unique.
  174. * <p>
  175. * Some notes about uniqueness:
  176. * <ul>
  177. * <li>Access does not seem to consider multiple {@code null} entries
  178. * invalid for a unique index</li>
  179. * <li>text indexes collapse case, and Access seems to compare <b>only</b>
  180. * the index entry bytes, therefore two strings which differ only in
  181. * case <i>will violate</i> the unique constraint</li>
  182. * </ul>
  183. */
  184. public boolean isUnique() {
  185. return getIndexData().isUnique();
  186. }
  187. /**
  188. * Returns the Columns for this index (unmodifiable)
  189. */
  190. public List<IndexData.ColumnDescriptor> getColumns() {
  191. return getIndexData().getColumns();
  192. }
  193. /**
  194. * Whether or not the complete index state has been read.
  195. */
  196. public boolean isInitialized() {
  197. return getIndexData().isInitialized();
  198. }
  199. /**
  200. * Forces initialization of this index (actual parsing of index pages).
  201. * normally, the index will not be initialized until the entries are
  202. * actually needed.
  203. */
  204. public void initialize() throws IOException {
  205. getIndexData().initialize();
  206. }
  207. /**
  208. * Writes the current index state to the database.
  209. * <p>
  210. * Forces index initialization.
  211. */
  212. public void update() throws IOException {
  213. getIndexData().update();
  214. }
  215. /**
  216. * Adds a row to this index
  217. * <p>
  218. * Forces index initialization.
  219. *
  220. * @param row Row to add
  221. * @param rowId rowId of the row to be added
  222. */
  223. public void addRow(Object[] row, RowId rowId)
  224. throws IOException
  225. {
  226. getIndexData().addRow(row, rowId);
  227. }
  228. /**
  229. * Removes a row from this index
  230. * <p>
  231. * Forces index initialization.
  232. *
  233. * @param row Row to remove
  234. * @param rowId rowId of the row to be removed
  235. */
  236. public void deleteRow(Object[] row, RowId rowId)
  237. throws IOException
  238. {
  239. getIndexData().deleteRow(row, rowId);
  240. }
  241. /**
  242. * Gets a new cursor for this index.
  243. * <p>
  244. * Forces index initialization.
  245. */
  246. public IndexData.EntryCursor cursor()
  247. throws IOException
  248. {
  249. return cursor(null, true, null, true);
  250. }
  251. /**
  252. * Gets a new cursor for this index, narrowed to the range defined by the
  253. * given startRow and endRow.
  254. * <p>
  255. * Forces index initialization.
  256. *
  257. * @param startRow the first row of data for the cursor, or {@code null} for
  258. * the first entry
  259. * @param startInclusive whether or not startRow is inclusive or exclusive
  260. * @param endRow the last row of data for the cursor, or {@code null} for
  261. * the last entry
  262. * @param endInclusive whether or not endRow is inclusive or exclusive
  263. */
  264. public IndexData.EntryCursor cursor(Object[] startRow,
  265. boolean startInclusive,
  266. Object[] endRow,
  267. boolean endInclusive)
  268. throws IOException
  269. {
  270. return getIndexData().cursor(startRow, startInclusive, endRow,
  271. endInclusive);
  272. }
  273. /**
  274. * Constructs an array of values appropriate for this index from the given
  275. * column values, expected to match the columns for this index.
  276. * @return the appropriate sparse array of data
  277. * @throws IllegalArgumentException if the wrong number of values are
  278. * provided
  279. */
  280. public Object[] constructIndexRowFromEntry(Object... values)
  281. {
  282. return getIndexData().constructIndexRowFromEntry(values);
  283. }
  284. /**
  285. * Constructs an array of values appropriate for this index from the given
  286. * column value.
  287. * @return the appropriate sparse array of data or {@code null} if not all
  288. * columns for this index were provided
  289. */
  290. public Object[] constructIndexRow(String colName, Object value)
  291. {
  292. return constructIndexRow(Collections.singletonMap(colName, value));
  293. }
  294. /**
  295. * Constructs an array of values appropriate for this index from the given
  296. * column values.
  297. * @return the appropriate sparse array of data or {@code null} if not all
  298. * columns for this index were provided
  299. */
  300. public Object[] constructIndexRow(Map<String,?> row)
  301. {
  302. return getIndexData().constructIndexRow(row);
  303. }
  304. @Override
  305. public String toString() {
  306. StringBuilder rtn = new StringBuilder();
  307. rtn.append("\tName: (").append(getTable().getName()).append(") ")
  308. .append(_name);
  309. rtn.append("\n\tNumber: ").append(_indexNumber);
  310. rtn.append("\n\tIs Primary Key: ").append(isPrimaryKey());
  311. rtn.append("\n\tIs Foreign Key: ").append(isForeignKey());
  312. if(_reference != null) {
  313. rtn.append("\n\tForeignKeyReference: ").append(_reference);
  314. }
  315. rtn.append(_data.toString());
  316. rtn.append("\n\n");
  317. return rtn.toString();
  318. }
  319. public int compareTo(Index other) {
  320. if (_indexNumber > other.getIndexNumber()) {
  321. return 1;
  322. } else if (_indexNumber < other.getIndexNumber()) {
  323. return -1;
  324. } else {
  325. return 0;
  326. }
  327. }
  328. /**
  329. * Writes the logical index definitions into a table definition buffer.
  330. * @param buffer Buffer to write to
  331. * @param indexes List of IndexBuilders to write definitions for
  332. */
  333. protected static void writeDefinitions(
  334. TableCreator creator, ByteBuffer buffer)
  335. throws IOException
  336. {
  337. // write logical index information
  338. for(IndexBuilder idx : creator.getIndexes()) {
  339. TableCreator.IndexState idxState = creator.getIndexState(idx);
  340. buffer.putInt(Table.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def
  341. buffer.putInt(idxState.getIndexNumber()); // index num
  342. buffer.putInt(idxState.getIndexDataNumber()); // index data num
  343. buffer.put((byte)0); // related table type
  344. buffer.putInt(INVALID_INDEX_NUMBER); // related index num
  345. buffer.putInt(0); // related table definition page number
  346. buffer.put((byte)0); // cascade updates flag
  347. buffer.put((byte)0); // cascade deletes flag
  348. buffer.put(idx.getType()); // index type flags
  349. buffer.putInt(0); // unknown
  350. }
  351. // write index names
  352. for(IndexBuilder idx : creator.getIndexes()) {
  353. Table.writeName(buffer, idx.getName(), creator.getCharset());
  354. }
  355. }
  356. /**
  357. * Information about a foreign key reference defined in an index (when
  358. * referential integrity should be enforced).
  359. */
  360. public static class ForeignKeyReference
  361. {
  362. private final byte _tableType;
  363. private final int _otherIndexNumber;
  364. private final int _otherTablePageNumber;
  365. private final boolean _cascadeUpdates;
  366. private final boolean _cascadeDeletes;
  367. public ForeignKeyReference(
  368. byte tableType, int otherIndexNumber, int otherTablePageNumber,
  369. boolean cascadeUpdates, boolean cascadeDeletes)
  370. {
  371. _tableType = tableType;
  372. _otherIndexNumber = otherIndexNumber;
  373. _otherTablePageNumber = otherTablePageNumber;
  374. _cascadeUpdates = cascadeUpdates;
  375. _cascadeDeletes = cascadeDeletes;
  376. }
  377. public byte getTableType() {
  378. return _tableType;
  379. }
  380. public boolean isPrimaryTable() {
  381. return(getTableType() == PRIMARY_TABLE_TYPE);
  382. }
  383. public int getOtherIndexNumber() {
  384. return _otherIndexNumber;
  385. }
  386. public int getOtherTablePageNumber() {
  387. return _otherTablePageNumber;
  388. }
  389. public boolean isCascadeUpdates() {
  390. return _cascadeUpdates;
  391. }
  392. public boolean isCascadeDeletes() {
  393. return _cascadeDeletes;
  394. }
  395. @Override
  396. public String toString() {
  397. return new StringBuilder()
  398. .append("\n\t\tOther Index Number: ").append(_otherIndexNumber)
  399. .append("\n\t\tOther Table Page Num: ").append(_otherTablePageNumber)
  400. .append("\n\t\tIs Primary Table: ").append(isPrimaryTable())
  401. .append("\n\t\tIs Cascade Updates: ").append(isCascadeUpdates())
  402. .append("\n\t\tIs Cascade Deletes: ").append(isCascadeDeletes())
  403. .toString();
  404. }
  405. }
  406. }