1 29 30 package com.caucho.db.table; 31 32 import com.caucho.db.Database; 33 import com.caucho.db.index.BTree; 34 import com.caucho.db.index.KeyCompare; 35 import com.caucho.db.sql.CreateQuery; 36 import com.caucho.db.sql.Expr; 37 import com.caucho.db.sql.Parser; 38 import com.caucho.db.sql.QueryContext; 39 import com.caucho.db.store.Block; 40 import com.caucho.db.store.Lock; 41 import com.caucho.db.store.Store; 42 import com.caucho.db.store.Transaction; 43 import com.caucho.log.Log; 44 import com.caucho.sql.SQLExceptionWrapper; 45 import com.caucho.util.L10N; 46 import com.caucho.vfs.Path; 47 import com.caucho.vfs.ReadStream; 48 import com.caucho.vfs.TempBuffer; 49 import com.caucho.vfs.TempStream; 50 import com.caucho.vfs.WriteStream; 51 52 import java.io.IOException ; 53 import java.sql.SQLException ; 54 import java.util.ArrayList ; 55 import java.util.logging.Level ; 56 import java.util.logging.Logger ; 57 58 72 public class Table extends Store { 73 private final static Logger log = Log.open(Table.class); 74 private final static L10N L = new L10N(Table.class); 75 76 private final static int ROOT_DATA_OFFSET = STORE_CREATE_END; 77 private final static int INDEX_ROOT_OFFSET = ROOT_DATA_OFFSET + 32; 78 79 private final static int ROOT_DATA_END = ROOT_DATA_OFFSET + 1024; 80 81 public final static int INLINE_BLOB_SIZE = 120; 82 83 public final static long ROW_CLOCK_MIN = 1024; 84 85 public final static byte ROW_VALID = 0x1; 86 public final static byte ROW_ALLOC = 0x2; 87 public final static byte ROW_MASK = 0x3; 88 89 private final static String DB_VERSION = "Resin-DB 3.1.0"; 90 private final static String MIN_VERSION = "Resin-DB 3.1.0"; 91 92 private final Row _row; 93 94 private final int _rowLength; 95 private final int _rowsPerBlock; 96 private final int _rowEnd; 97 98 private final Constraint[]_constraints; 99 100 private final Column _autoIncrementColumn; 101 102 private long _entries; 103 104 private final Object _rowClockLock = new Object (); 105 private long _rowClockAddr; 106 private long _rowClockTotal; 107 private long _rowClockUsed; 108 private int _rowClockCount; 109 private int _rowAllocCount; 110 111 private long _autoIncrementValue = -1; 112 113 private Lock _allocLock; 114 private Lock _insertLock; 115 116 Table(Database database, String name, Row row, Constraint constraints[]) 117 { 118 super(database, name, null); 119 120 _row = row; 121 _constraints = constraints; 122 123 _rowLength = _row.getLength(); 124 _rowsPerBlock = BLOCK_SIZE / _rowLength; 125 _rowEnd = _rowLength * _rowsPerBlock; 126 127 _rowClockAddr = 0; 128 129 Column []columns = _row.getColumns(); 130 Column autoIncrementColumn = null; 131 for (int i = 0; i < columns.length; i++) { 132 columns[i].setTable(this); 133 134 if (columns[i].getAutoIncrement() >= 0) 135 autoIncrementColumn = columns[i]; 136 } 137 _autoIncrementColumn = autoIncrementColumn; 138 139 _insertLock = new Lock("table-insert:" + name); 140 _allocLock = new Lock("table-alloc:" + name); 141 } 142 143 Row getRow() 144 { 145 return _row; 146 } 147 148 151 int getRowLength() 152 { 153 return _rowLength; 154 } 155 156 159 int getRowEnd() 160 { 161 return _rowEnd; 162 } 163 164 public final Column []getColumns() 165 { 166 return _row.getColumns(); 167 } 168 169 172 public final Constraint []getConstraints() 173 { 174 return _constraints; 175 } 176 177 180 public Column getAutoIncrementColumn() 181 { 182 return _autoIncrementColumn; 183 } 184 185 192 public Column getColumn(String name) 193 { 194 Column []columns = getColumns(); 195 196 for (int i = 0; i < columns.length; i++) { 197 if (columns[i].getName().equals(name)) 198 return columns[i]; 199 } 200 201 return null; 202 } 203 204 211 public int getColumnIndex(String name) 212 throws SQLException 213 { 214 Column []columns = getColumns(); 215 216 for (int i = 0; i < columns.length; i++) { 217 if (columns[i].getName().equals(name)) 218 return i; 219 } 220 221 return -1; 222 } 223 224 227 public static Table loadFromFile(Database db, String name) 228 throws IOException , SQLException 229 { 230 Path path = db.getPath().lookup(name + ".db"); 231 232 if (! path.exists()) 233 throw new IOException (L.l("table {0} does not exist", name)); 234 235 String version = null; 236 237 ReadStream is = path.openRead(); 238 try { 239 is.skip(DATA_START + ROOT_DATA_OFFSET); 241 242 StringBuilder sb = new StringBuilder (); 243 int ch; 244 245 while ((ch = is.read()) > 0) { 246 sb.append((char) ch); 247 } 248 249 version = sb.toString(); 250 251 if (! version.startsWith("Resin-DB")) { 252 throw new SQLException (L.l("table {0} is not a Resin DB. Version '{1}'", 253 name, version)); 254 } 255 else if (version.compareTo(MIN_VERSION) < 0 || 256 DB_VERSION.compareTo(version) < 0) { 257 throw new SQLException (L.l("table {0} is out of date. Old version {1}.", 258 name, version)); 259 } 260 } finally { 261 is.close(); 262 } 263 264 is = path.openRead(); 265 try { 266 is.skip(DATA_START + ROOT_DATA_END); 268 269 StringBuilder cb = new StringBuilder (); 270 271 int ch; 272 while ((ch = is.read()) > 0) { 273 cb.append((char) ch); 274 } 275 276 String sql = cb.toString(); 277 278 if (log.isLoggable(Level.FINER)) 279 log.finer("Table[" + name + "] " + version + " loading\n" + sql); 280 281 try { 282 CreateQuery query = (CreateQuery) Parser.parse(db, sql); 283 284 TableFactory factory = query.getFactory(); 285 286 if (! factory.getName().equalsIgnoreCase(name)) 287 throw new IOException (L.l("factory {0} does not match", name)); 288 289 Table table = new Table(db, factory.getName(), factory.getRow(), 290 factory.getConstraints()); 291 292 table.init(); 293 294 table.clearIndexes(); 295 table.initIndexes(); 296 table.rebuildIndexes(); 297 298 return table; 299 } catch (Throwable e) { 300 log.log(Level.FINE, e.toString(), e); 301 302 log.warning(e.toString()); 303 304 throw new SQLException (L.l("can't load table {0} in {1}.\n{2}", 305 name, path.getNativePath(), e.toString())); 306 } 307 } finally { 308 is.close(); 309 } 310 } 311 312 315 public void create() 316 throws IOException , SQLException 317 { 318 super.create(); 319 320 initIndexes(); 321 322 byte []tempBuffer = new byte[BLOCK_SIZE]; 323 324 readBlock(BLOCK_SIZE, tempBuffer, 0, BLOCK_SIZE); 325 326 TempStream ts = new TempStream(); 327 328 WriteStream os = new WriteStream(ts); 329 330 try { 331 for (int i = 0; i < ROOT_DATA_OFFSET; i++) 332 os.write(tempBuffer[i]); 333 334 writeTableHeader(os); 335 } finally { 336 os.close(); 337 } 338 339 TempBuffer head = ts.getHead(); 340 int offset = 0; 341 for (; head != null; head = head.getNext()) { 342 byte []buffer = head.getBuffer(); 343 344 int length = head.getLength(); 345 346 System.arraycopy(buffer, 0, tempBuffer, offset, length); 347 348 for (; length < buffer.length; length++) { 349 tempBuffer[offset + length] = 0; 350 } 351 352 offset += buffer.length; 353 } 354 355 for (; offset < BLOCK_SIZE; offset++) 356 tempBuffer[offset] = 0; 357 358 writeBlock(BLOCK_SIZE, tempBuffer, 0, BLOCK_SIZE); 359 360 _database.addTable(this); 361 } 362 363 366 private void initIndexes() 367 throws IOException , SQLException 368 { 369 Column []columns = _row.getColumns(); 370 for (int i = 0; i < columns.length; i++) { 371 Column column = columns[i]; 372 373 if (! column.isUnique()) 374 continue; 375 376 KeyCompare keyCompare = column.getIndexKeyCompare(); 377 378 if (keyCompare == null) 379 continue; 380 381 Block rootBlock = allocateIndexBlock(); 382 long rootBlockId = rootBlock.getBlockId(); 383 rootBlock.free(); 384 385 BTree btree = new BTree(this, rootBlockId, column.getLength(), 386 keyCompare); 387 388 column.setIndex(btree); 389 } 390 } 391 392 395 private void clearIndexes() 396 throws IOException 397 { 398 Column []columns = _row.getColumns(); 399 400 for (int i = 0; i < columns.length; i++) { 401 BTree index = columns[i].getIndex(); 402 403 if (index == null) 404 continue; 405 406 long rootAddr = index.getIndexRoot(); 407 408 Block block = readBlock(addressToBlockId(rootAddr)); 409 410 try { 411 byte []blockBuffer = block.getBuffer(); 412 413 synchronized (blockBuffer) { 414 for (int j = 0; j < blockBuffer.length; j++) { 415 blockBuffer[j] = 0; 416 } 417 418 block.setDirty(0, BLOCK_SIZE); 419 } 420 } finally { 421 block.free(); 422 } 423 } 424 425 long blockAddr = 0; 426 427 while ((blockAddr = firstBlock(blockAddr + BLOCK_SIZE, ALLOC_INDEX)) > 0) { 428 freeBlock(blockAddr); 429 } 430 } 431 432 435 private void rebuildIndexes() 436 throws IOException , SQLException 437 { 438 Transaction xa = Transaction.create(); 439 xa.setAutoCommit(true); 440 441 try { 442 TableIterator iter = createTableIterator(); 443 444 iter.init(xa); 445 446 Column []columns = _row.getColumns(); 447 448 while (iter.nextBlock()) { 449 iter.initRow(); 450 451 byte []blockBuffer = iter.getBuffer(); 452 453 while (iter.nextRow()) { 454 long rowAddress = iter.getRowAddress(); 455 int rowOffset = iter.getRowOffset(); 456 457 for (int i = 0; i < columns.length; i++) { 458 Column column = columns[i]; 459 460 column.setIndex(xa, blockBuffer, rowOffset, rowAddress, null); 461 } 462 } 463 } 464 } finally { 465 xa.commit(); 466 } 467 } 468 469 private void writeTableHeader(WriteStream os) 470 throws IOException 471 { 472 os.print(DB_VERSION); 473 os.write(0); 474 475 while (os.getBufferOffset() < INDEX_ROOT_OFFSET) 476 os.write(0); 477 478 Column []columns = _row.getColumns(); 479 for (int i = 0; i < columns.length; i++) { 480 if (! columns[i].isUnique()) 481 continue; 482 483 BTree index = columns[i].getIndex(); 484 485 if (index != null) { 486 writeLong(os, index.getIndexRoot()); 487 } 488 else { 489 writeLong(os, 0); 490 } 491 } 492 493 while (os.getBufferOffset() < ROOT_DATA_END) 494 os.write(0); 495 496 os.print("CREATE TABLE " + getName() + "("); 497 for (int i = 0; i < _row.getColumns().length; i++) { 498 Column column = _row.getColumns()[i]; 499 500 if (i != 0) 501 os.print(","); 502 503 os.print(column.getName()); 504 os.print(" "); 505 506 switch (column.getTypeCode()) { 507 case Column.VARCHAR: 508 os.print("VARCHAR(" + column.getDeclarationSize() + ")"); 509 break; 510 case Column.INT: 511 os.print("INTEGER"); 512 break; 513 case Column.LONG: 514 os.print("BIGINT"); 515 break; 516 case Column.DOUBLE: 517 os.print("DOUBLE"); 518 break; 519 case Column.DATE: 520 os.print("TIMESTAMP"); 521 break; 522 case Column.BLOB: 523 os.print("BLOB"); 524 break; 525 case Column.NUMERIC: 526 { 527 NumericColumn numeric = (NumericColumn) column; 528 529 os.print("NUMERIC(" + numeric.getPrecision() + "," + numeric.getScale() + ")"); 530 break; 531 } 532 default: 533 throw new UnsupportedOperationException (); 534 } 535 536 if (column.isPrimaryKey()) 537 os.print(" PRIMARY KEY"); 538 else if (column.isUnique()) 539 os.print(" UNIQUE"); 540 541 if (column.isNotNull()) 542 os.print(" NOT NULL"); 543 544 Expr defaultExpr = column.getDefault(); 545 546 if (defaultExpr != null) { 547 os.print(" DEFAULT ("); 548 os.print(defaultExpr); 549 os.print(")"); 550 } 551 552 if (column.getAutoIncrement() >= 0) 553 os.print(" auto_increment"); 554 } 555 os.print(")"); 556 557 562 } 563 564 public TableIterator createTableIterator() 565 { 566 assertStoreActive(); 567 568 return new TableIterator(this); 569 } 570 571 574 public long nextAutoIncrement(QueryContext context) 575 throws SQLException 576 { 577 synchronized (this) { 578 if (_autoIncrementValue >= 0) 579 return ++_autoIncrementValue; 580 } 581 582 long max = 0; 583 584 try { 585 TableIterator iter = createTableIterator(); 586 iter.init(context); 587 while (iter.next()) { 588 byte []buffer = iter.getBuffer(); 589 590 long value = _autoIncrementColumn.getLong(buffer, iter.getRowOffset()); 591 592 if (max < value) 593 max = value; 594 } 595 } catch (IOException e) { 596 throw new SQLExceptionWrapper(e); 597 } 598 599 synchronized (this) { 600 if (_autoIncrementValue < max) 601 _autoIncrementValue = max; 602 603 return ++_autoIncrementValue; 604 } 605 } 606 607 610 public long insert(QueryContext queryContext, Transaction xa, 611 ArrayList <Column> columns, 612 ArrayList <Expr> values) 613 throws IOException , SQLException 614 { 615 if (log.isLoggable(Level.FINE)) 616 log.fine("db table " + getName() + " insert row xa:" + xa); 617 618 Block block = null; 619 620 try { 621 long addr; 622 int rowOffset = 0; 623 624 boolean isLoop = false; 625 boolean hasRow = false; 626 627 int rowClockCount = 0; 628 long rowClockAddr = 0; 629 long rowClockUsed = 0; 630 long rowClockTotal = 0; 631 632 do { 633 long blockId = 0; 634 635 if (block != null) { 636 block.free(); 637 block = null; 638 } 639 640 synchronized (_rowClockLock) { 641 blockId = firstRow(_rowClockAddr); 642 643 if (blockId >= 0) { 644 } 645 else if (! isLoop 646 && (ROW_CLOCK_MIN < _rowClockTotal 647 && 4 * _rowClockUsed < 3 * _rowClockTotal 648 || _rowAllocCount > 8)) { 649 isLoop = true; 653 _rowClockCount = 0; 654 _rowClockAddr = 0; 655 _rowClockUsed = 0; 656 _rowClockTotal = 0; 657 _rowAllocCount = 0; 658 continue; 659 } 660 else { 661 663 _rowAllocCount++; 664 665 block = xa.allocateRow(this); 667 669 blockId = block.getBlockId(); 670 } 671 672 rowClockCount = _rowClockCount; 673 rowClockAddr = blockIdToAddress(blockId); 674 rowClockUsed = _rowClockUsed; 675 rowClockTotal = _rowClockTotal; 676 677 _rowClockCount++; 679 _rowClockAddr = rowClockAddr + BLOCK_SIZE; 680 _rowClockUsed = rowClockUsed + _rowsPerBlock; 681 _rowClockTotal = rowClockTotal + _rowsPerBlock; 682 } 683 684 if (block == null) 685 block = xa.readBlock(this, blockId); 686 687 xa.lockWrite(block.getLock()); 688 try { 689 rowOffset = 0; 690 691 byte []buffer = block.getBuffer(); 692 693 for (; rowOffset < _rowEnd; rowOffset += _rowLength) { 694 if (buffer[rowOffset] == 0) { 695 block.setDirty(rowOffset, rowOffset + 1); 696 697 hasRow = true; 698 buffer[rowOffset] = ROW_ALLOC; 699 break; 700 } 701 } 702 } finally { 703 xa.unlockReadAndWrite(block.getLock()); 704 } 705 } while (! hasRow); 706 707 insertRow(queryContext, xa, columns, values, 708 block, rowOffset); 709 710 synchronized (_rowClockLock) { 711 if (rowClockCount < _rowClockCount) { 712 int blocks = _rowClockCount - rowClockCount; 714 715 _rowClockCount = rowClockCount; 716 _rowClockAddr = rowClockAddr; 717 _rowClockUsed -= blocks * _rowsPerBlock; 718 _rowClockTotal -= blocks * _rowsPerBlock; 719 } 720 } 721 722 return blockIdToAddress(block.getBlockId(), rowOffset); 723 } finally { 724 if (block != null) 725 block.free(); 726 } 727 } 728 729 public void insertRow(QueryContext queryContext, Transaction xa, 730 ArrayList <Column> columns, 731 ArrayList <Expr> values, 732 Block block, int rowOffset) 733 throws SQLException 734 { 735 byte []buffer = block.getBuffer(); 736 737 long rowAddr = blockIdToAddress(block.getBlockId(), rowOffset); 738 740 TableIterator iter = createTableIterator(); 741 TableIterator []iterSet = new TableIterator[] { iter }; 742 queryContext.init(xa, iterSet, true); 744 iter.init(queryContext); 745 746 boolean isOkay = false; 747 queryContext.lock(); 748 try { 749 iter.setRow(block, rowOffset); 750 751 block.setDirty(rowOffset, rowOffset + _rowLength); 752 753 for (int i = rowOffset + _rowLength - 1; rowOffset < i; i--) 754 buffer[i] = 0; 755 756 for (int i = 0; i < columns.size(); i++) { 757 Column column = columns.get(i); 758 Expr value = values.get(i); 759 760 column.setExpr(xa, buffer, rowOffset, value, queryContext); 761 } 762 763 try { 767 validate(block, rowOffset, queryContext, xa); 768 769 buffer[rowOffset] = (byte) ((buffer[rowOffset] & ~ROW_MASK) | ROW_VALID); 770 771 for (int i = 0; i < columns.size(); i++) { 772 Column column = columns.get(i); 773 Expr value = values.get(i); 774 775 column.setIndex(xa, buffer, rowOffset, rowAddr, queryContext); 776 } 777 778 xa.addUpdateBlock(block); 779 780 if (_autoIncrementColumn != null) { 781 long value = _autoIncrementColumn.getLong(buffer, rowOffset); 782 783 synchronized (this) { 784 if (_autoIncrementValue < value) 785 _autoIncrementValue = value; 786 } 787 } 788 789 _entries++; 790 791 isOkay = true; 792 } finally { 793 795 if (! isOkay) 796 delete(xa, block, buffer, rowOffset); 797 } 798 } finally { 799 queryContext.unlock(); 800 } 801 } 802 803 806 private void validate(Block block, int rowOffset, 807 QueryContext queryContext, Transaction xa) 808 throws SQLException 809 { 810 TableIterator row = createTableIterator(); 811 TableIterator []rows = new TableIterator[] { row }; 812 813 row.setRow(block, rowOffset); 814 815 for (int i = 0; i < _constraints.length; i++) { 816 _constraints[i].validate(rows, queryContext, xa); 817 } 818 } 819 820 void delete(Transaction xa, Block block, byte []buffer, int rowOffset) 821 throws SQLException 822 { 823 byte rowState = buffer[rowOffset]; 824 825 if ((rowState & ROW_MASK) != ROW_VALID) 826 return; 827 828 buffer[rowOffset] = (byte) ((rowState & ~ROW_MASK) | ROW_ALLOC); 829 830 Column []columns = _row.getColumns(); 831 832 for (int i = 0; i < columns.length; i++) { 833 columns[i].delete(xa, buffer, rowOffset); 834 } 835 836 buffer[rowOffset] = 0; 837 838 synchronized (_rowClockLock) { 839 long addr = blockIdToAddress(block.getBlockId()); 840 841 if (addr <= _rowClockAddr) { 842 _rowClockUsed--; 843 } 844 } 845 } 846 847 private void writeLong(WriteStream os, long value) 848 throws IOException 849 { 850 os.write((int) (value >> 56)); 851 os.write((int) (value >> 48)); 852 os.write((int) (value >> 40)); 853 os.write((int) (value >> 32)); 854 os.write((int) (value >> 24)); 855 os.write((int) (value >> 16)); 856 os.write((int) (value >> 8)); 857 os.write((int) value); 858 } 859 860 private void setLong(byte []buffer, int offset, long value) 861 throws IOException 862 { 863 buffer[offset + 0] = (byte) (value >> 56); 864 buffer[offset + 1] = (byte) (value >> 48); 865 buffer[offset + 2] = (byte) (value >> 40); 866 buffer[offset + 3] = (byte) (value >> 32); 867 buffer[offset + 4] = (byte) (value >> 24); 868 buffer[offset + 5] = (byte) (value >> 16); 869 buffer[offset + 6] = (byte) (value >> 8); 870 buffer[offset + 7] = (byte) (value); 871 } 872 873 private long getLong(byte []buffer, int offset) 874 throws IOException 875 { 876 long value = (((buffer[offset + 0] & 0xffL) << 56) + 877 ((buffer[offset + 1] & 0xffL) << 48) + 878 ((buffer[offset + 2] & 0xffL) << 40) + 879 ((buffer[offset + 3] & 0xffL) << 32) + 880 881 ((buffer[offset + 4] & 0xffL) << 24) + 882 ((buffer[offset + 5] & 0xffL) << 16) + 883 ((buffer[offset + 6] & 0xffL) << 8) + 884 ((buffer[offset + 7] & 0xffL))); 885 886 return value; 887 } 888 889 public String toString() 890 { 891 return "Table[" + getName() + ":" + getId() + "]"; 892 } 893 } 894 | Popular Tags |