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.

IndexImpl.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. /*
  2. Copyright (c) 2005 Health Market Science, Inc.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package com.healthmarketscience.jackcess.impl;
  14. import java.io.IOException;
  15. import java.nio.ByteBuffer;
  16. import java.util.Collections;
  17. import java.util.List;
  18. import java.util.Map;
  19. import com.healthmarketscience.jackcess.CursorBuilder;
  20. import com.healthmarketscience.jackcess.Index;
  21. import com.healthmarketscience.jackcess.IndexBuilder;
  22. import org.apache.commons.lang3.builder.ToStringBuilder;
  23. import org.apache.commons.logging.Log;
  24. import org.apache.commons.logging.LogFactory;
  25. /**
  26. * Access table (logical) index. Logical indexes are backed for IndexData,
  27. * where one or more logical indexes could be backed by the same data.
  28. *
  29. * @author Tim McCune
  30. */
  31. public class IndexImpl implements Index, Comparable<IndexImpl>
  32. {
  33. protected static final Log LOG = LogFactory.getLog(IndexImpl.class);
  34. /** index type for primary key indexes */
  35. public static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1;
  36. /** index type for foreign key indexes */
  37. public static final byte FOREIGN_KEY_INDEX_TYPE = (byte)2;
  38. /** flag for indicating that updates should cascade in a foreign key index */
  39. private static final byte CASCADE_UPDATES_FLAG = (byte)1;
  40. /** flag for indicating that deletes should cascade in a foreign key index */
  41. private static final byte CASCADE_DELETES_FLAG = (byte)1;
  42. /** flag for indicating that deletes should cascade a null in a foreign key
  43. index */
  44. private static final byte CASCADE_NULL_FLAG = (byte)2;
  45. /** index table type for the "primary" table in a foreign key index */
  46. static final byte FK_PRIMARY_TABLE_TYPE = (byte)1;
  47. /** index table type for the "secondary" table in a foreign key index */
  48. static final byte FK_SECONDARY_TABLE_TYPE = (byte)2;
  49. /** indicate an invalid index number for foreign key field */
  50. private static final int INVALID_INDEX_NUMBER = -1;
  51. /** the actual data backing this index (more than one index may be backed by
  52. the same data */
  53. private final IndexData _data;
  54. /** 0-based index number */
  55. private final int _indexNumber;
  56. /** the type of the index */
  57. private final byte _indexType;
  58. /** Index name */
  59. private String _name;
  60. /** foreign key reference info, if any */
  61. private final ForeignKeyReference _reference;
  62. protected IndexImpl(ByteBuffer tableBuffer, List<IndexData> indexDatas,
  63. JetFormat format)
  64. throws IOException
  65. {
  66. ByteUtil.forward(tableBuffer, format.SKIP_BEFORE_INDEX_SLOT); //Forward past Unknown
  67. _indexNumber = tableBuffer.getInt();
  68. int indexDataNumber = tableBuffer.getInt();
  69. // read foreign key reference info
  70. byte relIndexType = tableBuffer.get();
  71. int relIndexNumber = tableBuffer.getInt();
  72. int relTablePageNumber = tableBuffer.getInt();
  73. byte cascadeUpdatesFlag = tableBuffer.get();
  74. byte cascadeDeletesFlag = tableBuffer.get();
  75. _indexType = tableBuffer.get();
  76. if((_indexType == FOREIGN_KEY_INDEX_TYPE) &&
  77. (relIndexNumber != INVALID_INDEX_NUMBER)) {
  78. _reference = new ForeignKeyReference(
  79. relIndexType, relIndexNumber, relTablePageNumber,
  80. ((cascadeUpdatesFlag & CASCADE_UPDATES_FLAG) != 0),
  81. ((cascadeDeletesFlag & CASCADE_DELETES_FLAG) != 0),
  82. ((cascadeDeletesFlag & CASCADE_NULL_FLAG) != 0));
  83. } else {
  84. _reference = null;
  85. }
  86. ByteUtil.forward(tableBuffer, format.SKIP_AFTER_INDEX_SLOT); //Skip past Unknown
  87. _data = indexDatas.get(indexDataNumber);
  88. _data.addIndex(this);
  89. }
  90. public IndexData getIndexData() {
  91. return _data;
  92. }
  93. @Override
  94. public TableImpl getTable() {
  95. return getIndexData().getTable();
  96. }
  97. public JetFormat getFormat() {
  98. return getTable().getFormat();
  99. }
  100. public PageChannel getPageChannel() {
  101. return getTable().getPageChannel();
  102. }
  103. public int getIndexNumber() {
  104. return _indexNumber;
  105. }
  106. public byte getIndexFlags() {
  107. return getIndexData().getIndexFlags();
  108. }
  109. public int getUniqueEntryCount() {
  110. return getIndexData().getUniqueEntryCount();
  111. }
  112. public int getUniqueEntryCountOffset() {
  113. return getIndexData().getUniqueEntryCountOffset();
  114. }
  115. @Override
  116. public String getName() {
  117. return _name;
  118. }
  119. void setName(String name) {
  120. _name = name;
  121. }
  122. @Override
  123. public boolean isPrimaryKey() {
  124. return _indexType == PRIMARY_KEY_INDEX_TYPE;
  125. }
  126. @Override
  127. public boolean isForeignKey() {
  128. return _indexType == FOREIGN_KEY_INDEX_TYPE;
  129. }
  130. public ForeignKeyReference getReference() {
  131. return _reference;
  132. }
  133. @Override
  134. public IndexImpl getReferencedIndex() throws IOException {
  135. if(_reference == null) {
  136. return null;
  137. }
  138. TableImpl refTable = getTable().getDatabase().getTable(
  139. _reference.getOtherTablePageNumber());
  140. if(refTable == null) {
  141. throw new IOException(withErrorContext(
  142. "Reference to missing table " + _reference.getOtherTablePageNumber()));
  143. }
  144. IndexImpl refIndex = null;
  145. int idxNumber = _reference.getOtherIndexNumber();
  146. for(IndexImpl idx : refTable.getIndexes()) {
  147. if(idx.getIndexNumber() == idxNumber) {
  148. refIndex = idx;
  149. break;
  150. }
  151. }
  152. if(refIndex == null) {
  153. throw new IOException(withErrorContext(
  154. "Reference to missing index " + idxNumber +
  155. " on table " + refTable.getName()));
  156. }
  157. // finally verify that we found the expected index (should reference this
  158. // index)
  159. ForeignKeyReference otherRef = refIndex.getReference();
  160. if((otherRef == null) ||
  161. (otherRef.getOtherTablePageNumber() !=
  162. getTable().getTableDefPageNumber()) ||
  163. (otherRef.getOtherIndexNumber() != _indexNumber)) {
  164. throw new IOException(withErrorContext(
  165. "Found unexpected index " + refIndex.getName() +
  166. " on table " + refTable.getName() + " with reference " + otherRef));
  167. }
  168. return refIndex;
  169. }
  170. @Override
  171. public boolean shouldIgnoreNulls() {
  172. return getIndexData().shouldIgnoreNulls();
  173. }
  174. @Override
  175. public boolean isUnique() {
  176. return getIndexData().isUnique();
  177. }
  178. @Override
  179. public boolean isRequired() {
  180. return getIndexData().isRequired();
  181. }
  182. @Override
  183. public List<IndexData.ColumnDescriptor> getColumns() {
  184. return getIndexData().getColumns();
  185. }
  186. @Override
  187. public int getColumnCount() {
  188. return getIndexData().getColumnCount();
  189. }
  190. @Override
  191. public CursorBuilder newCursor() {
  192. return getTable().newCursor().setIndex(this);
  193. }
  194. /**
  195. * Whether or not the complete index state has been read.
  196. */
  197. public boolean isInitialized() {
  198. return getIndexData().isInitialized();
  199. }
  200. /**
  201. * Forces initialization of this index (actual parsing of index pages).
  202. * normally, the index will not be initialized until the entries are
  203. * actually needed.
  204. */
  205. public void initialize() throws IOException {
  206. getIndexData().initialize();
  207. }
  208. /**
  209. * Gets a new cursor for this index.
  210. * <p>
  211. * Forces index initialization.
  212. */
  213. public IndexData.EntryCursor cursor()
  214. throws IOException
  215. {
  216. return cursor(null, true, null, true);
  217. }
  218. /**
  219. * Gets a new cursor for this index, narrowed to the range defined by the
  220. * given startRow and endRow.
  221. * <p>
  222. * Forces index initialization.
  223. *
  224. * @param startRow the first row of data for the cursor, or {@code null} for
  225. * the first entry
  226. * @param startInclusive whether or not startRow is inclusive or exclusive
  227. * @param endRow the last row of data for the cursor, or {@code null} for
  228. * the last entry
  229. * @param endInclusive whether or not endRow is inclusive or exclusive
  230. */
  231. public IndexData.EntryCursor cursor(Object[] startRow,
  232. boolean startInclusive,
  233. Object[] endRow,
  234. boolean endInclusive)
  235. throws IOException
  236. {
  237. return getIndexData().cursor(startRow, startInclusive, endRow,
  238. endInclusive);
  239. }
  240. /**
  241. * Constructs an array of values appropriate for this index from the given
  242. * column values, expected to match the columns for this index.
  243. * @return the appropriate sparse array of data
  244. * @throws IllegalArgumentException if the wrong number of values are
  245. * provided
  246. */
  247. public Object[] constructIndexRowFromEntry(Object... values)
  248. {
  249. return getIndexData().constructIndexRowFromEntry(values);
  250. }
  251. /**
  252. * Constructs an array of values appropriate for this index from the given
  253. * column values, possibly only providing a prefix subset of the index
  254. * columns (at least one value must be provided). If a prefix entry is
  255. * provided, any missing, trailing index entry values will use the given
  256. * filler value.
  257. * @return the appropriate sparse array of data
  258. * @throws IllegalArgumentException if at least one value is not provided
  259. */
  260. public Object[] constructPartialIndexRowFromEntry(
  261. Object filler, Object... values)
  262. {
  263. return getIndexData().constructPartialIndexRowFromEntry(filler, values);
  264. }
  265. /**
  266. * Constructs an array of values appropriate for this index from the given
  267. * column value.
  268. * @return the appropriate sparse array of data or {@code null} if not all
  269. * columns for this index were provided
  270. */
  271. public Object[] constructIndexRow(String colName, Object value)
  272. {
  273. return constructIndexRow(Collections.singletonMap(colName, value));
  274. }
  275. /**
  276. * Constructs an array of values appropriate for this index from the given
  277. * column value, which must be the first column of the index. Any missing,
  278. * trailing index entry values will use the given filler value.
  279. * @return the appropriate sparse array of data or {@code null} if no prefix
  280. * list of columns for this index were provided
  281. */
  282. public Object[] constructPartialIndexRow(Object filler, String colName, Object value)
  283. {
  284. return constructPartialIndexRow(filler, Collections.singletonMap(colName, value));
  285. }
  286. /**
  287. * Constructs an array of values appropriate for this index from the given
  288. * column values.
  289. * @return the appropriate sparse array of data or {@code null} if not all
  290. * columns for this index were provided
  291. */
  292. public Object[] constructIndexRow(Map<String,?> row)
  293. {
  294. return getIndexData().constructIndexRow(row);
  295. }
  296. /**
  297. * Constructs an array of values appropriate for this index from the given
  298. * column values, possibly only using a subset of the given values. A
  299. * partial row can be created if one or more prefix column values are
  300. * provided. If a prefix can be found, any missing, trailing index entry
  301. * values will use the given filler value.
  302. * @return the appropriate sparse array of data or {@code null} if no prefix
  303. * list of columns for this index were provided
  304. */
  305. public Object[] constructPartialIndexRow(Object filler, Map<String,?> row)
  306. {
  307. return getIndexData().constructPartialIndexRow(filler, row);
  308. }
  309. @Override
  310. public String toString() {
  311. ToStringBuilder sb = CustomToStringStyle.builder(this)
  312. .append("name", "(" + getTable().getName() + ") " + _name)
  313. .append("number", _indexNumber)
  314. .append("isPrimaryKey", isPrimaryKey())
  315. .append("isForeignKey", isForeignKey());
  316. if(_reference != null) {
  317. sb.append("foreignKeyReference", _reference);
  318. }
  319. sb.append("data", _data);
  320. return sb.toString();
  321. }
  322. @Override
  323. public int compareTo(IndexImpl other) {
  324. if (_indexNumber > other.getIndexNumber()) {
  325. return 1;
  326. } else if (_indexNumber < other.getIndexNumber()) {
  327. return -1;
  328. } else {
  329. return 0;
  330. }
  331. }
  332. /**
  333. * Writes the logical index definitions into a table definition buffer.
  334. * @param creator description of the indexes to write
  335. * @param buffer Buffer to write to
  336. */
  337. protected static void writeDefinitions(
  338. TableCreator creator, ByteBuffer buffer)
  339. throws IOException
  340. {
  341. // write logical index information
  342. for(IndexBuilder idx : creator.getIndexes()) {
  343. writeDefinition(creator, idx, buffer);
  344. }
  345. // write index names
  346. for(IndexBuilder idx : creator.getIndexes()) {
  347. TableImpl.writeName(buffer, idx.getName(), creator.getCharset());
  348. }
  349. }
  350. protected static void writeDefinition(
  351. TableMutator mutator, IndexBuilder idx, ByteBuffer buffer)
  352. throws IOException
  353. {
  354. TableMutator.IndexDataState idxDataState = mutator.getIndexDataState(idx);
  355. // write logical index information
  356. buffer.putInt(TableImpl.MAGIC_TABLE_NUMBER); // seemingly constant magic value which matches the table def
  357. buffer.putInt(idx.getIndexNumber()); // index num
  358. buffer.putInt(idxDataState.getIndexDataNumber()); // index data num
  359. byte idxType = idx.getType();
  360. if(idxType != FOREIGN_KEY_INDEX_TYPE) {
  361. buffer.put((byte)0); // related table type
  362. buffer.putInt(INVALID_INDEX_NUMBER); // related index num
  363. buffer.putInt(0); // related table definition page number
  364. buffer.put((byte)0); // cascade updates flag
  365. buffer.put((byte)0); // cascade deletes flag
  366. } else {
  367. ForeignKeyReference reference = mutator.getForeignKey(idx);
  368. buffer.put(reference.getTableType()); // related table type
  369. buffer.putInt(reference.getOtherIndexNumber()); // related index num
  370. buffer.putInt(reference.getOtherTablePageNumber()); // related table definition page number
  371. byte updateFlags = 0;
  372. if(reference.isCascadeUpdates()) {
  373. updateFlags |= CASCADE_UPDATES_FLAG;
  374. }
  375. byte deleteFlags = 0;
  376. if(reference.isCascadeDeletes()) {
  377. deleteFlags |= CASCADE_DELETES_FLAG;
  378. }
  379. if(reference.isCascadeNullOnDelete()) {
  380. deleteFlags |= CASCADE_NULL_FLAG;
  381. }
  382. buffer.put(updateFlags); // cascade updates flag
  383. buffer.put(deleteFlags); // cascade deletes flag
  384. }
  385. buffer.put(idxType); // index type flags
  386. buffer.putInt(0); // unknown
  387. }
  388. private String withErrorContext(String msg) {
  389. return withErrorContext(msg, getTable().getDatabase(), getName());
  390. }
  391. private static String withErrorContext(String msg, DatabaseImpl db,
  392. String idxName) {
  393. return msg + " (Db=" + db.getName() + ";Index=" + idxName + ")";
  394. }
  395. /**
  396. * Information about a foreign key reference defined in an index (when
  397. * referential integrity should be enforced).
  398. */
  399. public static class ForeignKeyReference
  400. {
  401. private final byte _tableType;
  402. private final int _otherIndexNumber;
  403. private final int _otherTablePageNumber;
  404. private final boolean _cascadeUpdates;
  405. private final boolean _cascadeDeletes;
  406. private final boolean _cascadeNull;
  407. public ForeignKeyReference(
  408. byte tableType, int otherIndexNumber, int otherTablePageNumber,
  409. boolean cascadeUpdates, boolean cascadeDeletes, boolean cascadeNull)
  410. {
  411. _tableType = tableType;
  412. _otherIndexNumber = otherIndexNumber;
  413. _otherTablePageNumber = otherTablePageNumber;
  414. _cascadeUpdates = cascadeUpdates;
  415. _cascadeDeletes = cascadeDeletes;
  416. _cascadeNull = cascadeNull;
  417. }
  418. public byte getTableType() {
  419. return _tableType;
  420. }
  421. public boolean isPrimaryTable() {
  422. return(getTableType() == FK_PRIMARY_TABLE_TYPE);
  423. }
  424. public int getOtherIndexNumber() {
  425. return _otherIndexNumber;
  426. }
  427. public int getOtherTablePageNumber() {
  428. return _otherTablePageNumber;
  429. }
  430. public boolean isCascadeUpdates() {
  431. return _cascadeUpdates;
  432. }
  433. public boolean isCascadeDeletes() {
  434. return _cascadeDeletes;
  435. }
  436. public boolean isCascadeNullOnDelete() {
  437. return _cascadeNull;
  438. }
  439. @Override
  440. public String toString() {
  441. return CustomToStringStyle.builder(this)
  442. .append("otherIndexNumber", _otherIndexNumber)
  443. .append("otherTablePageNum", _otherTablePageNumber)
  444. .append("isPrimaryTable", isPrimaryTable())
  445. .append("isCascadeUpdates", isCascadeUpdates())
  446. .append("isCascadeDeletes", isCascadeDeletes())
  447. .append("isCascadeNullOnDelete", isCascadeNullOnDelete())
  448. .toString();
  449. }
  450. }
  451. }