| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | |
|
| 5 | |
|
| 6 | |
|
| 7 | |
|
| 8 | |
|
| 9 | |
|
| 10 | |
|
| 11 | |
|
| 12 | |
|
| 13 | |
|
| 14 | |
|
| 15 | |
|
| 16 | |
|
| 17 | |
|
| 18 | |
|
| 19 | |
|
| 20 | |
|
| 21 | |
|
| 22 | |
|
| 23 | |
|
| 24 | |
|
| 25 | |
|
| 26 | |
|
| 27 | |
|
| 28 | |
package com.healthmarketscience.jackcess; |
| 29 | |
|
| 30 | |
import java.io.ByteArrayOutputStream; |
| 31 | |
import java.io.IOException; |
| 32 | |
import java.nio.ByteBuffer; |
| 33 | |
import java.nio.ByteOrder; |
| 34 | |
import java.util.ArrayList; |
| 35 | |
import java.util.Arrays; |
| 36 | |
import java.util.Collections; |
| 37 | |
import java.util.Comparator; |
| 38 | |
import java.util.Iterator; |
| 39 | |
import java.util.LinkedList; |
| 40 | |
import java.util.List; |
| 41 | |
import java.util.Map; |
| 42 | |
|
| 43 | |
import org.apache.commons.logging.Log; |
| 44 | |
import org.apache.commons.logging.LogFactory; |
| 45 | |
|
| 46 | |
import static com.healthmarketscience.jackcess.IndexCodes.*; |
| 47 | |
|
| 48 | |
|
| 49 | |
|
| 50 | |
|
| 51 | |
|
| 52 | |
|
| 53 | 4050028 | public abstract class Index implements Comparable<Index> { |
| 54 | |
|
| 55 | 1 | protected static final Log LOG = LogFactory.getLog(Index.class); |
| 56 | |
|
| 57 | |
|
| 58 | 1 | public static final Entry FIRST_ENTRY = |
| 59 | |
createSpecialEntry(RowId.FIRST_ROW_ID); |
| 60 | |
|
| 61 | |
|
| 62 | 1 | public static final Entry LAST_ENTRY = |
| 63 | |
createSpecialEntry(RowId.LAST_ROW_ID); |
| 64 | |
|
| 65 | |
protected static final int INVALID_INDEX_PAGE_NUMBER = 0; |
| 66 | |
|
| 67 | |
|
| 68 | |
private static final int MAX_COLUMNS = 10; |
| 69 | |
|
| 70 | 1 | protected static final byte[] EMPTY_PREFIX = new byte[0]; |
| 71 | |
|
| 72 | |
private static final short COLUMN_UNUSED = -1; |
| 73 | |
|
| 74 | |
private static final byte ASCENDING_COLUMN_FLAG = (byte)0x01; |
| 75 | |
|
| 76 | |
private static final byte UNIQUE_INDEX_FLAG = (byte)0x01; |
| 77 | |
private static final byte IGNORE_NULLS_INDEX_FLAG = (byte)0x02; |
| 78 | |
|
| 79 | |
|
| 80 | |
private static final byte PRIMARY_KEY_INDEX_TYPE = (byte)1; |
| 81 | |
|
| 82 | |
|
| 83 | |
private static final byte FOREIGN_KEY_INDEX_TYPE = (byte)2; |
| 84 | |
|
| 85 | |
private static final int MAX_TEXT_INDEX_CHAR_LENGTH = |
| 86 | |
(JetFormat.TEXT_FIELD_MAX_LENGTH / JetFormat.TEXT_FIELD_UNIT_SIZE); |
| 87 | |
|
| 88 | |
|
| 89 | 6 | public enum EntryType { |
| 90 | |
|
| 91 | |
|
| 92 | 1 | ALWAYS_FIRST, |
| 93 | |
|
| 94 | |
|
| 95 | 1 | FIRST_VALID, |
| 96 | |
|
| 97 | |
|
| 98 | 1 | NORMAL, |
| 99 | |
|
| 100 | |
|
| 101 | 1 | LAST_VALID, |
| 102 | |
|
| 103 | |
|
| 104 | 1 | ALWAYS_LAST; |
| 105 | |
} |
| 106 | |
|
| 107 | 1 | static final Comparator<byte[]> BYTE_CODE_COMPARATOR = |
| 108 | |
new Comparator<byte[]>() { |
| 109 | 152415 | public int compare(byte[] left, byte[] right) { |
| 110 | 152414 | if(left == right) { |
| 111 | 7873 | return 0; |
| 112 | |
} |
| 113 | 144541 | if(left == null) { |
| 114 | 0 | return -1; |
| 115 | |
} |
| 116 | 144541 | if(right == null) { |
| 117 | 2 | return 1; |
| 118 | |
} |
| 119 | |
|
| 120 | 144539 | int len = Math.min(left.length, right.length); |
| 121 | 144539 | int pos = 0; |
| 122 | 681283 | while((pos < len) && (left[pos] == right[pos])) { |
| 123 | 536744 | ++pos; |
| 124 | |
} |
| 125 | 144539 | if(pos < len) { |
| 126 | 126205 | return ((ByteUtil.asUnsignedByte(left[pos]) < |
| 127 | |
ByteUtil.asUnsignedByte(right[pos])) ? -1 : 1); |
| 128 | |
} |
| 129 | 18334 | return ((left.length < right.length) ? -1 : |
| 130 | |
((left.length > right.length) ? 1 : 0)); |
| 131 | |
} |
| 132 | |
}; |
| 133 | |
|
| 134 | |
|
| 135 | |
|
| 136 | |
private final Table _table; |
| 137 | |
|
| 138 | |
private int _rootPageNumber; |
| 139 | |
|
| 140 | |
|
| 141 | |
private final int _uniqueEntryCountOffset; |
| 142 | |
|
| 143 | |
|
| 144 | |
|
| 145 | |
private int _uniqueEntryCount; |
| 146 | |
|
| 147 | 353 | private final List<ColumnDescriptor> _columns = |
| 148 | |
new ArrayList<ColumnDescriptor>(); |
| 149 | |
|
| 150 | |
private int _indexNumber; |
| 151 | |
|
| 152 | |
private byte _indexFlags; |
| 153 | |
|
| 154 | |
private byte _indexType; |
| 155 | |
|
| 156 | |
private String _name; |
| 157 | |
|
| 158 | |
|
| 159 | |
private boolean _initialized; |
| 160 | |
|
| 161 | |
private int _modCount; |
| 162 | |
|
| 163 | 353 | private final TempBufferHolder _indexBufferH = |
| 164 | |
TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true); |
| 165 | |
|
| 166 | |
private final int _maxPageEntrySize; |
| 167 | |
|
| 168 | |
boolean _readOnly; |
| 169 | |
|
| 170 | |
protected Index(Table table, int uniqueEntryCount, |
| 171 | |
int uniqueEntryCountOffset) |
| 172 | 353 | { |
| 173 | 353 | _table = table; |
| 174 | 353 | _uniqueEntryCount = uniqueEntryCount; |
| 175 | 353 | _uniqueEntryCountOffset = uniqueEntryCountOffset; |
| 176 | 353 | _maxPageEntrySize = calcMaxPageEntrySize(_table.getFormat()); |
| 177 | 353 | } |
| 178 | |
|
| 179 | |
public Table getTable() { |
| 180 | 43016 | return _table; |
| 181 | |
} |
| 182 | |
|
| 183 | |
public JetFormat getFormat() { |
| 184 | 15910 | return getTable().getFormat(); |
| 185 | |
} |
| 186 | |
|
| 187 | |
public PageChannel getPageChannel() { |
| 188 | 16590 | return getTable().getPageChannel(); |
| 189 | |
} |
| 190 | |
|
| 191 | |
public void setIndexNumber(int indexNumber) { |
| 192 | 353 | _indexNumber = indexNumber; |
| 193 | 353 | } |
| 194 | |
|
| 195 | |
public int getIndexNumber() { |
| 196 | 159 | return _indexNumber; |
| 197 | |
} |
| 198 | |
|
| 199 | |
public void setIndexType(byte indexType) { |
| 200 | 353 | _indexType = indexType; |
| 201 | 353 | } |
| 202 | |
|
| 203 | |
public byte getIndexFlags() { |
| 204 | 0 | return _indexFlags; |
| 205 | |
} |
| 206 | |
|
| 207 | |
public int getUniqueEntryCount() { |
| 208 | 4407 | return _uniqueEntryCount; |
| 209 | |
} |
| 210 | |
|
| 211 | |
public int getUniqueEntryCountOffset() { |
| 212 | 4399 | return _uniqueEntryCountOffset; |
| 213 | |
} |
| 214 | |
|
| 215 | |
public String getName() { |
| 216 | 1979 | return _name; |
| 217 | |
} |
| 218 | |
|
| 219 | |
public void setName(String name) { |
| 220 | 353 | _name = name; |
| 221 | 353 | } |
| 222 | |
|
| 223 | |
public boolean isPrimaryKey() { |
| 224 | 5028 | return _indexType == PRIMARY_KEY_INDEX_TYPE; |
| 225 | |
} |
| 226 | |
|
| 227 | |
public boolean isForeignKey() { |
| 228 | 11 | return _indexType == FOREIGN_KEY_INDEX_TYPE; |
| 229 | |
} |
| 230 | |
|
| 231 | |
|
| 232 | |
|
| 233 | |
|
| 234 | |
public boolean shouldIgnoreNulls() { |
| 235 | 4596 | return((_indexFlags & IGNORE_NULLS_INDEX_FLAG) != 0); |
| 236 | |
} |
| 237 | |
|
| 238 | |
|
| 239 | |
|
| 240 | |
|
| 241 | |
|
| 242 | |
|
| 243 | |
|
| 244 | |
|
| 245 | |
|
| 246 | |
|
| 247 | |
|
| 248 | |
|
| 249 | |
|
| 250 | |
public boolean isUnique() { |
| 251 | 2509 | return(isPrimaryKey() || ((_indexFlags & UNIQUE_INDEX_FLAG) != 0)); |
| 252 | |
} |
| 253 | |
|
| 254 | |
|
| 255 | |
|
| 256 | |
|
| 257 | |
public List<ColumnDescriptor> getColumns() { |
| 258 | 100 | return Collections.unmodifiableList(_columns); |
| 259 | |
} |
| 260 | |
|
| 261 | |
|
| 262 | |
|
| 263 | |
|
| 264 | |
public boolean isInitialized() { |
| 265 | 3 | return _initialized; |
| 266 | |
} |
| 267 | |
|
| 268 | |
protected int getRootPageNumber() { |
| 269 | 193 | return _rootPageNumber; |
| 270 | |
} |
| 271 | |
|
| 272 | |
protected void setReadOnly() { |
| 273 | 8 | _readOnly = true; |
| 274 | 8 | } |
| 275 | |
|
| 276 | |
protected int getMaxPageEntrySize() { |
| 277 | 4002 | return _maxPageEntrySize; |
| 278 | |
} |
| 279 | |
|
| 280 | |
|
| 281 | |
|
| 282 | |
|
| 283 | |
|
| 284 | |
|
| 285 | |
|
| 286 | |
protected int getEntryCount() |
| 287 | |
throws IOException |
| 288 | |
{ |
| 289 | 28 | initialize(); |
| 290 | 28 | EntryCursor cursor = cursor(); |
| 291 | 28 | Entry endEntry = cursor.getLastEntry(); |
| 292 | 28 | int count = 0; |
| 293 | 1361 | while(!endEntry.equals(cursor.getNextEntry())) { |
| 294 | 1333 | ++count; |
| 295 | |
} |
| 296 | 28 | return count; |
| 297 | |
} |
| 298 | |
|
| 299 | |
|
| 300 | |
|
| 301 | |
|
| 302 | |
|
| 303 | |
|
| 304 | |
public void initialize() throws IOException { |
| 305 | 13047 | if(!_initialized) { |
| 306 | 193 | readIndexEntries(); |
| 307 | 193 | _initialized = true; |
| 308 | |
} |
| 309 | 13047 | } |
| 310 | |
|
| 311 | |
|
| 312 | |
|
| 313 | |
|
| 314 | |
|
| 315 | |
|
| 316 | |
public void update() throws IOException |
| 317 | |
{ |
| 318 | |
|
| 319 | 4399 | initialize(); |
| 320 | |
|
| 321 | 4399 | if(_readOnly) { |
| 322 | 1 | throw new UnsupportedOperationException( |
| 323 | |
"FIXME cannot write indexes of this type yet, see Database javadoc for info on enabling large index support"); |
| 324 | |
} |
| 325 | 4398 | updateImpl(); |
| 326 | 4398 | } |
| 327 | |
|
| 328 | |
|
| 329 | |
|
| 330 | |
|
| 331 | |
|
| 332 | |
|
| 333 | |
public void read(ByteBuffer tableBuffer, List<Column> availableColumns) |
| 334 | |
throws IOException |
| 335 | |
{ |
| 336 | 3883 | for (int i = 0; i < MAX_COLUMNS; i++) { |
| 337 | 3530 | short columnNumber = tableBuffer.getShort(); |
| 338 | 3530 | byte colFlags = tableBuffer.get(); |
| 339 | 3530 | if (columnNumber != COLUMN_UNUSED) { |
| 340 | |
|
| 341 | |
|
| 342 | 427 | Column idxCol = null; |
| 343 | 427 | for(Column col : availableColumns) { |
| 344 | 794 | if(col.getColumnNumber() == columnNumber) { |
| 345 | 427 | idxCol = col; |
| 346 | 427 | break; |
| 347 | |
} |
| 348 | |
} |
| 349 | 427 | if(idxCol == null) { |
| 350 | 0 | throw new IOException("Could not find column with number " |
| 351 | |
+ columnNumber + " for index " + getName()); |
| 352 | |
} |
| 353 | 427 | _columns.add(newColumnDescriptor(idxCol, colFlags)); |
| 354 | |
} |
| 355 | |
} |
| 356 | 353 | tableBuffer.getInt(); |
| 357 | 353 | _rootPageNumber = tableBuffer.getInt(); |
| 358 | 353 | tableBuffer.getInt(); |
| 359 | 353 | _indexFlags = tableBuffer.get(); |
| 360 | 353 | tableBuffer.position(tableBuffer.position() + 5); |
| 361 | 353 | } |
| 362 | |
|
| 363 | |
|
| 364 | |
|
| 365 | |
|
| 366 | |
|
| 367 | |
|
| 368 | |
|
| 369 | |
|
| 370 | |
|
| 371 | |
public void addRow(Object[] row, RowId rowId) |
| 372 | |
throws IOException |
| 373 | |
{ |
| 374 | 2503 | int nullCount = countNullValues(row); |
| 375 | 2503 | boolean isNullEntry = (nullCount == _columns.size()); |
| 376 | 2503 | if(shouldIgnoreNulls() && isNullEntry) { |
| 377 | |
|
| 378 | 5 | return; |
| 379 | |
} |
| 380 | 2498 | if(isPrimaryKey() && (nullCount > 0)) { |
| 381 | 0 | throw new IOException("Null value found in row " + Arrays.asList(row) |
| 382 | |
+ " for primary key index " + this); |
| 383 | |
} |
| 384 | |
|
| 385 | |
|
| 386 | 2498 | initialize(); |
| 387 | |
|
| 388 | 2498 | Entry newEntry = new Entry(createEntryBytes(row), rowId); |
| 389 | 2498 | if(addEntry(newEntry, isNullEntry, row)) { |
| 390 | 2489 | ++_modCount; |
| 391 | |
} else { |
| 392 | 0 | LOG.warn("Added duplicate index entry " + newEntry + " for row: " + |
| 393 | |
Arrays.asList(row)); |
| 394 | |
} |
| 395 | 2489 | } |
| 396 | |
|
| 397 | |
|
| 398 | |
|
| 399 | |
|
| 400 | |
private boolean addEntry(Entry newEntry, boolean isNullEntry, Object[] row) |
| 401 | |
throws IOException |
| 402 | |
{ |
| 403 | 2498 | DataPage dataPage = findDataPage(newEntry); |
| 404 | 2498 | int idx = dataPage.findEntry(newEntry); |
| 405 | 2498 | if(idx < 0) { |
| 406 | |
|
| 407 | 2498 | idx = missingIndexToInsertionPoint(idx); |
| 408 | |
|
| 409 | 2498 | Position newPos = new Position(dataPage, idx, newEntry, true); |
| 410 | 2498 | Position nextPos = getNextPosition(newPos); |
| 411 | 2498 | Position prevPos = getPreviousPosition(newPos); |
| 412 | |
|
| 413 | |
|
| 414 | |
|
| 415 | |
|
| 416 | 2498 | boolean isDupeEntry = |
| 417 | |
(((nextPos != null) && |
| 418 | |
newEntry.equalsEntryBytes(nextPos.getEntry())) || |
| 419 | |
((prevPos != null) && |
| 420 | |
newEntry.equalsEntryBytes(prevPos.getEntry()))); |
| 421 | 2498 | if(isUnique() && !isNullEntry && isDupeEntry) |
| 422 | |
{ |
| 423 | 9 | throw new IOException( |
| 424 | |
"New row " + Arrays.asList(row) + |
| 425 | |
" violates uniqueness constraint for index " + this); |
| 426 | |
} |
| 427 | |
|
| 428 | 2489 | if(!isDupeEntry) { |
| 429 | 2405 | ++_uniqueEntryCount; |
| 430 | |
} |
| 431 | |
|
| 432 | 2489 | dataPage.addEntry(idx, newEntry); |
| 433 | 2489 | return true; |
| 434 | |
} |
| 435 | 0 | return false; |
| 436 | |
} |
| 437 | |
|
| 438 | |
|
| 439 | |
|
| 440 | |
|
| 441 | |
|
| 442 | |
|
| 443 | |
|
| 444 | |
|
| 445 | |
|
| 446 | |
public void deleteRow(Object[] row, RowId rowId) |
| 447 | |
throws IOException |
| 448 | |
{ |
| 449 | 2082 | int nullCount = countNullValues(row); |
| 450 | 2082 | if(shouldIgnoreNulls() && (nullCount == _columns.size())) { |
| 451 | |
|
| 452 | 0 | return; |
| 453 | |
} |
| 454 | |
|
| 455 | |
|
| 456 | 2082 | initialize(); |
| 457 | |
|
| 458 | 2082 | Entry oldEntry = new Entry(createEntryBytes(row), rowId); |
| 459 | 2082 | if(removeEntry(oldEntry)) { |
| 460 | 2082 | ++_modCount; |
| 461 | |
} else { |
| 462 | 0 | LOG.warn("Failed removing index entry " + oldEntry + " for row: " + |
| 463 | |
Arrays.asList(row)); |
| 464 | |
} |
| 465 | 2082 | } |
| 466 | |
|
| 467 | |
|
| 468 | |
|
| 469 | |
|
| 470 | |
|
| 471 | |
|
| 472 | |
private boolean removeEntry(Entry oldEntry) |
| 473 | |
throws IOException |
| 474 | |
{ |
| 475 | 2082 | DataPage dataPage = findDataPage(oldEntry); |
| 476 | 2082 | int idx = dataPage.findEntry(oldEntry); |
| 477 | 2082 | boolean doRemove = false; |
| 478 | 2082 | if(idx < 0) { |
| 479 | |
|
| 480 | |
|
| 481 | |
|
| 482 | 2063 | EntryCursor cursor = cursor(); |
| 483 | 2063 | Position tmpPos = null; |
| 484 | 2063 | Position endPos = cursor._lastPos; |
| 485 | 2000094 | while(!endPos.equals( |
| 486 | |
tmpPos = cursor.getAnotherPosition(Cursor.MOVE_FORWARD))) { |
| 487 | 2000094 | if(tmpPos.getEntry().getRowId().equals(oldEntry.getRowId())) { |
| 488 | 2063 | dataPage = tmpPos.getDataPage(); |
| 489 | 2063 | idx = tmpPos.getIndex(); |
| 490 | 2063 | doRemove = true; |
| 491 | 2063 | break; |
| 492 | |
} |
| 493 | |
} |
| 494 | 2063 | } else { |
| 495 | 19 | doRemove = true; |
| 496 | |
} |
| 497 | |
|
| 498 | 2082 | if(doRemove) { |
| 499 | |
|
| 500 | 2082 | dataPage.removeEntry(idx); |
| 501 | |
} |
| 502 | |
|
| 503 | 2082 | return doRemove; |
| 504 | |
} |
| 505 | |
|
| 506 | |
|
| 507 | |
|
| 508 | |
|
| 509 | |
|
| 510 | |
|
| 511 | |
public EntryCursor cursor() |
| 512 | |
throws IOException |
| 513 | |
{ |
| 514 | 2091 | return cursor(null, true, null, true); |
| 515 | |
} |
| 516 | |
|
| 517 | |
|
| 518 | |
|
| 519 | |
|
| 520 | |
|
| 521 | |
|
| 522 | |
|
| 523 | |
|
| 524 | |
|
| 525 | |
|
| 526 | |
|
| 527 | |
|
| 528 | |
|
| 529 | |
|
| 530 | |
public EntryCursor cursor(Object[] startRow, |
| 531 | |
boolean startInclusive, |
| 532 | |
Object[] endRow, |
| 533 | |
boolean endInclusive) |
| 534 | |
throws IOException |
| 535 | |
{ |
| 536 | 4032 | initialize(); |
| 537 | 4032 | Entry startEntry = FIRST_ENTRY; |
| 538 | 4032 | byte[] startEntryBytes = null; |
| 539 | 4032 | if(startRow != null) { |
| 540 | 1849 | startEntryBytes = createEntryBytes(startRow); |
| 541 | 1849 | startEntry = new Entry(startEntryBytes, |
| 542 | |
(startInclusive ? |
| 543 | |
RowId.FIRST_ROW_ID : RowId.LAST_ROW_ID)); |
| 544 | |
} |
| 545 | 4032 | Entry endEntry = LAST_ENTRY; |
| 546 | 4032 | if(endRow != null) { |
| 547 | |
|
| 548 | |
|
| 549 | 1847 | byte[] endEntryBytes = ((startRow == endRow) ? |
| 550 | |
startEntryBytes : |
| 551 | |
createEntryBytes(endRow)); |
| 552 | 1847 | endEntry = new Entry(endEntryBytes, |
| 553 | |
(endInclusive ? |
| 554 | |
RowId.LAST_ROW_ID : RowId.FIRST_ROW_ID)); |
| 555 | |
} |
| 556 | 4032 | return new EntryCursor(findEntryPosition(startEntry), |
| 557 | |
findEntryPosition(endEntry)); |
| 558 | |
} |
| 559 | |
|
| 560 | |
private Position findEntryPosition(Entry entry) |
| 561 | |
throws IOException |
| 562 | |
{ |
| 563 | 16517 | DataPage dataPage = findDataPage(entry); |
| 564 | 16517 | int idx = dataPage.findEntry(entry); |
| 565 | 16517 | boolean between = false; |
<