├── .gitignore ├── README.md ├── courses.csv ├── enrollments.csv ├── hw0-README.md ├── images ├── hw0-docker-mounts.png ├── intellij-empty-configuration.png ├── intellij-filledin-configuration.png ├── proj2-dockerstart.png ├── proj2-testoutput.png ├── proj3-volcano-model.png ├── proj4-escalate1.png ├── proj4-escalate2.png ├── proj4-layers.png ├── proj5-db-happy.png └── proj5-db-off-the-cliff.png ├── intellij-test-setup.md ├── pom.xml ├── proj2-README.md ├── proj3-README.md ├── proj3-part1-README.md ├── proj3-part2-README.md ├── proj4-README.md ├── proj4-part1-README.md ├── proj4-part2-README.md ├── proj4-part3-README.md ├── proj5-README.md ├── public.key ├── src ├── main │ └── java │ │ └── edu │ │ └── berkeley │ │ └── cs186 │ │ └── database │ │ ├── AbstractTransaction.java │ │ ├── AbstractTransactionContext.java │ │ ├── Database.java │ │ ├── DatabaseException.java │ │ ├── ThreadPool.java │ │ ├── Transaction.java │ │ ├── TransactionContext.java │ │ ├── common │ │ ├── AbstractBuffer.java │ │ ├── Bits.java │ │ ├── Buffer.java │ │ ├── ByteBuffer.java │ │ ├── HashFunc.java │ │ ├── Pair.java │ │ ├── PredicateOperator.java │ │ └── iterator │ │ │ ├── ArrayBacktrackingIterator.java │ │ │ ├── BacktrackingIterable.java │ │ │ ├── BacktrackingIterator.java │ │ │ ├── ConcatBacktrackingIterator.java │ │ │ ├── EmptyBacktrackingIterator.java │ │ │ └── IndexBacktrackingIterator.java │ │ ├── concurrency │ │ ├── DummyLockContext.java │ │ ├── DummyLockManager.java │ │ ├── DuplicateLockRequestException.java │ │ ├── InvalidLockException.java │ │ ├── Lock.java │ │ ├── LockContext.java │ │ ├── LockManager.java │ │ ├── LockRequest.java │ │ ├── LockType.java │ │ ├── LockUtil.java │ │ ├── NoLockHeldException.java │ │ └── ResourceName.java │ │ ├── databox │ │ ├── BoolDataBox.java │ │ ├── DataBox.java │ │ ├── DataBoxException.java │ │ ├── FloatDataBox.java │ │ ├── IntDataBox.java │ │ ├── LongDataBox.java │ │ ├── StringDataBox.java │ │ ├── Type.java │ │ └── TypeId.java │ │ ├── index │ │ ├── BPlusNode.java │ │ ├── BPlusTree.java │ │ ├── BPlusTreeException.java │ │ ├── BPlusTreeMetadata.java │ │ ├── InnerNode.java │ │ └── LeafNode.java │ │ ├── io │ │ ├── DiskSpaceManager.java │ │ ├── DiskSpaceManagerImpl.java │ │ └── PageException.java │ │ ├── memory │ │ ├── BufferFrame.java │ │ ├── BufferManager.java │ │ ├── BufferManagerImpl.java │ │ ├── ClockEvictionPolicy.java │ │ ├── EvictionPolicy.java │ │ ├── HashPartition.java │ │ ├── LRUEvictionPolicy.java │ │ ├── NaiveHashPartition.java │ │ └── Page.java │ │ ├── query │ │ ├── BNLJOperator.java │ │ ├── GraceHashJoin.java │ │ ├── GroupByOperator.java │ │ ├── IndexScanOperator.java │ │ ├── JoinOperator.java │ │ ├── MaterializeOperator.java │ │ ├── NaiveHashJoin.java │ │ ├── PNLJOperator.java │ │ ├── ProjectOperator.java │ │ ├── QueryOperator.java │ │ ├── QueryPlan.java │ │ ├── QueryPlanException.java │ │ ├── SNLJOperator.java │ │ ├── SelectOperator.java │ │ ├── SequentialScanOperator.java │ │ ├── SortMergeOperator.java │ │ └── SortOperator.java │ │ ├── recovery │ │ ├── ARIESRecoveryManager.java │ │ ├── AbortTransactionLogRecord.java │ │ ├── AllocPageLogRecord.java │ │ ├── AllocPartLogRecord.java │ │ ├── BeginCheckpointLogRecord.java │ │ ├── CommitTransactionLogRecord.java │ │ ├── DummyRecoveryManager.java │ │ ├── EndCheckpointLogRecord.java │ │ ├── EndTransactionLogRecord.java │ │ ├── FreePageLogRecord.java │ │ ├── FreePartLogRecord.java │ │ ├── LogManager.java │ │ ├── LogManagerImpl.java │ │ ├── LogRecord.java │ │ ├── LogType.java │ │ ├── MasterLogRecord.java │ │ ├── RecoveryManager.java │ │ ├── TransactionTableEntry.java │ │ ├── UndoAllocPageLogRecord.java │ │ ├── UndoAllocPartLogRecord.java │ │ ├── UndoFreePageLogRecord.java │ │ ├── UndoFreePartLogRecord.java │ │ ├── UndoUpdatePageLogRecord.java │ │ └── UpdatePageLogRecord.java │ │ └── table │ │ ├── HeapFile.java │ │ ├── MarkerRecord.java │ │ ├── PageDirectory.java │ │ ├── Record.java │ │ ├── RecordId.java │ │ ├── RecordIterator.java │ │ ├── Schema.java │ │ ├── Table.java │ │ └── stats │ │ ├── Bucket.java │ │ ├── Histogram.java │ │ └── TableStats.java └── test │ ├── java │ └── edu │ │ └── berkeley │ │ └── cs186 │ │ └── database │ │ ├── TestDatabase.java │ │ ├── TestDatabaseDeadlockPrecheck.java │ │ ├── TestDatabaseLocking.java │ │ ├── TestDatabaseQueries.java │ │ ├── TestUtils.java │ │ ├── TimeoutScaling.java │ │ ├── categories │ │ ├── HiddenTests.java │ │ ├── Proj0Tests.java │ │ ├── Proj2Tests.java │ │ ├── Proj3Part1Tests.java │ │ ├── Proj3Part2Tests.java │ │ ├── Proj3Tests.java │ │ ├── Proj4Part1Tests.java │ │ ├── Proj4Part2Tests.java │ │ ├── Proj4Part3Tests.java │ │ ├── Proj4Tests.java │ │ ├── Proj5Tests.java │ │ ├── Proj99Tests.java │ │ ├── ProjTests.java │ │ ├── PublicTests.java │ │ ├── StudentTestRunner.java │ │ ├── StudentTests.java │ │ └── SystemTests.java │ │ ├── common │ │ └── TestBits.java │ │ ├── concurrency │ │ ├── DeterministicRunner.java │ │ ├── DummyTransactionContext.java │ │ ├── LoggingLockContext.java │ │ ├── LoggingLockManager.java │ │ ├── TestLockContext.java │ │ ├── TestLockManager.java │ │ ├── TestLockType.java │ │ └── TestLockUtil.java │ │ ├── databox │ │ ├── TestBoolDataBox.java │ │ ├── TestFloatDataBox.java │ │ ├── TestIntDataBox.java │ │ ├── TestLongDataBox.java │ │ ├── TestStringDataBox.java │ │ ├── TestType.java │ │ └── TestWelcome.java │ │ ├── index │ │ ├── TestBPlusNode.java │ │ ├── TestBPlusTree.java │ │ ├── TestInnerNode.java │ │ └── TestLeafNode.java │ │ ├── io │ │ ├── MemoryDiskSpaceManager.java │ │ └── TestDiskSpaceManager.java │ │ ├── memory │ │ ├── TestBufferManager.java │ │ └── TestEvictionPolicy.java │ │ ├── query │ │ ├── TestBasicQuery.java │ │ ├── TestGraceHashJoin.java │ │ ├── TestJoinOperator.java │ │ ├── TestOptimization2.java │ │ ├── TestOptimizationJoins.java │ │ ├── TestSingleAccess.java │ │ ├── TestSortOperator.java │ │ └── TestSourceOperator.java │ │ ├── recovery │ │ ├── ARIESRecoveryManagerNoLocking.java │ │ ├── DummyTransaction.java │ │ ├── TestARIESStudent.java │ │ ├── TestARIESStudentRunner.java │ │ ├── TestARIESStudentRunnerBase.java │ │ ├── TestLogManager.java │ │ ├── TestLogRecord.java │ │ └── TestRecoveryManager.java │ │ └── table │ │ ├── MemoryHeapFile.java │ │ ├── TestPageDirectory.java │ │ ├── TestRecord.java │ │ ├── TestRecordId.java │ │ ├── TestSchema.java │ │ └── TestTable.java │ └── students.csv ├── students.csv └── turn_in.py /.gitignore: -------------------------------------------------------------------------------- 1 | # vim files 2 | .*~ 3 | .*.s?? 4 | 5 | # Ignore all maven build and test files 6 | target/* 7 | *.class 8 | 9 | # Eclipse 10 | .classpath 11 | .project 12 | .settings/ 13 | 14 | # Ignore all IntelliJ IDEA files 15 | .idea/* 16 | *.iml 17 | *.iws 18 | .attach_* 19 | 20 | # Ignore all OSX hidden files 21 | *.DS_Store 22 | /bin/ 23 | 24 | # ignore submission files 25 | hw*.zip 26 | 27 | -------------------------------------------------------------------------------- /courses.csv: -------------------------------------------------------------------------------- 1 | 1,CS 186,Computer Science 2 | 2,CS 61A,Computer Science 3 | 3,BIO 1A,Biology 4 | 4,BIO 1B,Biology 5 | 5,CHEM 1A,Chemistry 6 | 6,CHEM 4A,Chemistry 7 | 7,UGBA 10,Business 8 | 8,UGBA 196,Business 9 | 9,ART 10,Art History 10 | 10,ART 100,Art History 11 | 11,ZOO 15,Zoology 12 | 12,ZOO 137B,Zoology 13 | 13,CS 162,Computer Science 14 | 14,BIO 100,Biology 15 | 15,CS 61C,Computer Science 16 | -------------------------------------------------------------------------------- /images/hw0-docker-mounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/hw0-docker-mounts.png -------------------------------------------------------------------------------- /images/intellij-empty-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/intellij-empty-configuration.png -------------------------------------------------------------------------------- /images/intellij-filledin-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/intellij-filledin-configuration.png -------------------------------------------------------------------------------- /images/proj2-dockerstart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/proj2-dockerstart.png -------------------------------------------------------------------------------- /images/proj2-testoutput.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/proj2-testoutput.png -------------------------------------------------------------------------------- /images/proj3-volcano-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/proj3-volcano-model.png -------------------------------------------------------------------------------- /images/proj4-escalate1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/proj4-escalate1.png -------------------------------------------------------------------------------- /images/proj4-escalate2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/proj4-escalate2.png -------------------------------------------------------------------------------- /images/proj4-layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/proj4-layers.png -------------------------------------------------------------------------------- /images/proj5-db-happy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/proj5-db-happy.png -------------------------------------------------------------------------------- /images/proj5-db-off-the-cliff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cabbage-Cat/Database-Implementation-CS186-Project/909caea5f47f9175c3056607ce5973ee447fc41b/images/proj5-db-off-the-cliff.png -------------------------------------------------------------------------------- /intellij-test-setup.md: -------------------------------------------------------------------------------- 1 | # IntelliJ setup for running tests 2 | 3 | This document will help you set up IntelliJ for running an assignment's tests 4 | (making it run all the tests that `mvn test -Dproj=X` would run). 5 | 6 | 1. Open up Run/Debug Configurations with Run > Edit Configurations. 7 | 2. Click the + button in the top left to create a new configuration, and choose JUnit from 8 | the dropdown. This should get you the following unnamed configuration: 9 | ![unnamed configuration menu](images/intellij-empty-configuration.png) 10 | 3. Fill in the fields as listed below, then press OK. 11 | ![filled in menu](images/intellij-filledin-configuration.png) 12 | - Name: Proj2 tests (or whichever assignment you're setting up) 13 | - Test kind: Category 14 | - Category: edu.berkeley.cs186.database.categories.Proj2Tests (or the category corresponding to the assignment you're setting up) 15 | - Search for tests: In whole project 16 | 4. You should now see Project 2 tests in the dropdown in the top right. You can run/debug this configuration to run all the Project 2 tests. 17 | -------------------------------------------------------------------------------- /public.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP PUBLIC KEY BLOCK----- 2 | 3 | mQINBF4mo5kBEAC+ZHsiOQ0cfW96+eN18sxYzEIAFqfCXzrBl45IWK2Z2ObPFSLZ 4 | OcccQ7md1KcXlWTWgprZy4zExwT8+1+2dpc4pGlOcda/vuTQjgxYW0MMUbaMv817 5 | 3Jrq6MaKLq9FxLI9VCz+f9un8KW8WkW8lxDmL+wXJY/ziWh7EN2ubNzutX1xLei6 6 | ou28uxgMsgNE5wRhk768gycY77Lhy1y3nxNZtQlINfCqE05rFyuAfqIvXTNHRTVu 7 | gH+gn+Xtk/H4FSg08JJkq2SAj50DqoB9CgwouC8aB1BRL7rZlrw3287R0I4AVDvH 8 | 9217jgOo5SbKmHbbf0zjW3azSX0/bYZSDMVXNLLi1hXKXFCWMlqoejllDHTkjNHY 9 | 4G5PDKh8lLspif46ir0SBsO1IP1cDYUScL/h1qyygaot5DEFsJ+91qsH8Yqw0K+y 10 | +gI9E9xFEqMurijawFPuGVqeb3tgoxEbkp9UwMIEkddlL6hg/SrDeFTgqEewkgNd 11 | jCquOdLF1zANK14LLMJ1GUs/6RvB+GUnqD/humJ2O+/2aYrkOVRKGU0+VhU7jalc 12 | 2vgOx4lQbfQG284sayDdOiqF3f2rFTzcuPECJNWdoVD5WCJMWbs1YiS9nfH5qeeM 13 | d0Hrul3/aCjgrkn4VFHmU2Pw6I9UwIOWoPGb8qepHonpyU3mfuoyL8n+3wARAQAB 14 | tD9DUzE4NiBTdGFmZiAoQ1MxODYgU3ByaW5nIDIwMjAgR1BHIGtleXMpIDxka2lt 15 | OTA4QGJlcmtlbGV5LmVkdT6JAlQEEwEIAD4WIQSkmJK9nX1On7ooNjZ7Q6fP+piP 16 | iAUCXiajmQIbAwUJAO1OAAULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRB7Q6fP 17 | +piPiJHrD/9Gvet1SVfYkCl8t7r96smKguI6ZLdMCylmv6UumgfJnIJ4xIOR8Cff 18 | U4dWjexbRhTlbntBquQGsu9UiC8oM5ExTqbZ1Z6Hri4CYoys++1X7RDY42pEic95 19 | XnnNA2rZUhqaTjalnvCdEtQFQr6RD3z9Jhm0HSvta1AlxXtmu0HuoPd4MVLbj6Zq 20 | //mI7VVV1TP5jvIG7HCG4nsPYhpxwLqv6eoqiSsoslx1OES12BSr9e6JX+a8Vfkd 21 | EJbu7axYoh7euuLNPcRkMdxElEUr7jtPiK256dr0/XZJ1AAN8h8KpB/NDPTrmcyM 22 | L60skvb0UhnGuhVVzbLSR4lMHpH9W82prcMFf/hlLQAQQpzjcCSrWRFwyPPXMOtE 23 | 4sXhC3nBSO782JejzlSjRrvYpn6dVADEhj6ul9aGCSwki+YiSUg4Fa2T91qk0Qbu 24 | mEZrNyePn9hzQAy7ZY4yZqM97GduQ1TesOnfqilMsiUUg7R18b3yNEemErAdrHj+ 25 | +SbxceQD9b+qb9hRjs/Qx+xPWqwaOcWvYNNVJJtDhW0N+s31L6czbxv+6COETRW4 26 | DhmpwphNZuWwbLXp2nJWPebcjt4wfTIwfq75f1Sa373WoOiBnNA75AXjBUPVioWS 27 | WXXwyghYH3NFBhzTV2nMKgjUlaYqtMpPcKcqWfFM9iuFFkqfrQAz87kCDQReJqOZ 28 | ARAAyyJpxfL4clNhsUnhLhdJRaNQdeVDW5FtQYtq1vtLf81yvIbY7vQmGyDc1sdO 29 | 63c4oiNRl66h+zRnrLSYfsZN4HRfaIZ/cPROV60JvnGxHj3cSopQnhJdpJ/A5OUF 30 | eQEcv6YhT7iwjWB254wIE4KhE4+S2aRd3J526WWY8nIyCtcpLN73liKK2RU824Fu 31 | gNiElXOhxhqMWkBkCR7zkG3ZbZPaB6cFihCqZznjRMoQu8iEst74isxMfLlmHAZH 32 | F4B9ji0jGJtdbabuxDSIx7lYgBqSB5zrDiHaiawCbFRAFhi08cbvRs6qeWfQ3jBY 33 | KR3P78XBnCBdNbACbnB0hrfGWmKSSpQDMPvnf+dXcZAQQTH+SazssjXnqsIUCl0O 34 | LHlUY9EbCGIz5TUtSIL/x/1f4mdfiv7seuaiLIKe5L4/3Q+GSmI9eWwrBgVfngxY 35 | eh9ZRMQJwGYPXnq5qc9yponFTVpY7L3/EH+gahZSPYm7TOsPaH4jmjITmiWiPYSn 36 | kGFarnb7bQaA7IW5VYw5qIY6+JQgqz6X+yfRn61oMM15vYkaPhBKk1Na7rR2K9Kd 37 | jhMwZrnSKjbdZoBIbQGyZajWqPIXbgRtnDIWQ2xbj/nQME9dc4+vChMkp3lNEw+1 38 | bpdhShlPyKa8hSKXudbhQvL/xPEBNGV+q9dE7CYMnSnLvQUAEQEAAYkCPAQYAQgA 39 | JhYhBKSYkr2dfU6fuig2NntDp8/6mI+IBQJeJqOZAhsMBQkA7U4AAAoJEHtDp8/6 40 | mI+IJzQP/i5HTweMUai58cg2yoPcS6Pk1UUNLjKGuWS2eliwhHfbGm2Ezv56xF3S 41 | 0CQVynpILs2DZl5h/6RvOPHtX9wh9OHkWHrnm8Te9faA7yW06laun6oM1aBvn/yS 42 | v71h0FYMpofUCL6+TuQRUbovtqZa8rDWJeOXsD3ee1EiiehAzX4jWpSFHNTJTT9k 43 | uHtUea5S77nOv6cVMHhYqJ5aq5FC360ZQjKxtH+gNajake7u43XHOm+cnIvcAO3Z 44 | lfkntC7pwuAUXz8uITuRYOSof+cxdV/zmUvc02FY8PhSgbedbEINhystQpzCGsOh 45 | LZzk0mi3jgKyYcayEl07tB1X1xWXKtvredHeE3dhHy4IddSqoAM2Cj5zHC09fwXr 46 | uP6XtsenoihZuk2/1AmHO4VrfOXDCDwWwyUN9AUXmc+cU4O4z6rrytxqL7zPdwK3 47 | wKxXeFVIHMMy8PopcCdb3fU3vWJTW7fkfv7LbEIEf2EjRqVIZo1Fet00yb9v1ci3 48 | WFA7z0DlIcBTIrZcE1iMKKQnWwKhoPrbU+38M+Pq2xiguBmk/te0jIRbIlUX4vYX 49 | +p6UV2b4J3/miHi4cjhGVJBnJSuBoWalU0VHq/zfBRKga+10VV0vgzI9flN4DLcP 50 | lyAZIs62VaV/4IPgF/BMC6Poxokme8e4VSGlfeO4O89r1e2xs+wY 51 | =GCpj 52 | -----END PGP PUBLIC KEY BLOCK----- 53 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/AbstractTransaction.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database; 2 | 3 | public abstract class AbstractTransaction implements Transaction { 4 | private Status status = Status.RUNNING; 5 | 6 | /** 7 | * Called when commit() is called. Any exception thrown in this method will cause 8 | * the transaction to abort. 9 | */ 10 | protected abstract void startCommit(); 11 | 12 | /** 13 | * Called when rollback() is called. No exception should be thrown, and any exception 14 | * thrown will be interpreted the same as if the method had returned normally. 15 | */ 16 | protected abstract void startRollback(); 17 | 18 | /** 19 | * Commit the transaction. 20 | */ 21 | @Override 22 | public final void commit() { 23 | if (status != Status.RUNNING) { 24 | throw new IllegalStateException("transaction not in running state, cannot commit"); 25 | } 26 | startCommit(); 27 | } 28 | 29 | /** 30 | * Rollback the transaction. 31 | */ 32 | @Override 33 | public final void rollback() { 34 | if (status != Status.RUNNING) { 35 | throw new IllegalStateException("transaction not in running state, cannot rollback"); 36 | } 37 | startRollback(); 38 | } 39 | 40 | @Override 41 | public final Status getStatus() { 42 | return status; 43 | } 44 | 45 | @Override 46 | public void setStatus(Status status) { 47 | this.status = status; 48 | } 49 | 50 | /** 51 | * Implements close() as commit() when abort/commit not called - so that we can write: 52 | * 53 | * try (Transaction t = ...) { 54 | * ... 55 | * } 56 | * 57 | * and have the transaction commit. 58 | */ 59 | @Override 60 | public final void close() { 61 | if (status == Status.RUNNING) { 62 | commit(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/AbstractTransactionContext.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database; 2 | 3 | import java.util.concurrent.locks.Condition; 4 | import java.util.concurrent.locks.ReentrantLock; 5 | 6 | /** 7 | * This transaction context implementation assumes that exactly one transaction runs 8 | * on a thread at a time, and that, aside from the unblock() method, no methods 9 | * of the transaction are called from a different thread than the thread that the 10 | * transaction is associated with. This implementation blocks the thread when 11 | * block() is called. 12 | */ 13 | public abstract class AbstractTransactionContext implements TransactionContext { 14 | private boolean blocked = false; 15 | private boolean startBlock = false; 16 | private final ReentrantLock transactionLock = new ReentrantLock(); 17 | private final Condition unblocked = transactionLock.newCondition(); 18 | 19 | /** 20 | * prepareBlock acquires the lock backing the condition variable that the transaction 21 | * waits on. Must be called before block(), and is used to ensure that the unblock() call 22 | * corresponding to the following block() call cannot be run before the transaction blocks. 23 | */ 24 | @Override 25 | public void prepareBlock() { 26 | if (this.startBlock) { 27 | throw new IllegalStateException("already preparing to block"); 28 | } 29 | this.transactionLock.lock(); 30 | this.startBlock = true; 31 | } 32 | 33 | /** 34 | * Blocks the transaction (and thread). prepareBlock() must be called first. 35 | */ 36 | @Override 37 | public void block() { 38 | if (!this.startBlock) { 39 | throw new IllegalStateException("prepareBlock() must be called before block()"); 40 | } 41 | try { 42 | this.blocked = true; 43 | while (this.blocked) { 44 | this.unblocked.awaitUninterruptibly(); 45 | } 46 | } finally { 47 | this.startBlock = false; 48 | this.transactionLock.unlock(); 49 | } 50 | } 51 | 52 | /** 53 | * Unblocks the transaction (and thread running the transaction). 54 | */ 55 | @Override 56 | public void unblock() { 57 | this.transactionLock.lock(); 58 | try { 59 | this.blocked = false; 60 | this.unblocked.signal(); 61 | } finally { 62 | this.transactionLock.unlock(); 63 | } 64 | } 65 | 66 | @Override 67 | public boolean getBlocked() { 68 | return this.blocked; 69 | } 70 | 71 | @SuppressWarnings("unchecked") 72 | private static void rethrow(Throwable t) throws T { 73 | // rethrows checked exceptions as unchecked 74 | throw (T) t; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/DatabaseException.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database; 2 | 3 | public class DatabaseException extends RuntimeException { 4 | public DatabaseException(String message) { 5 | super(message); 6 | } 7 | 8 | public DatabaseException(Exception e) { 9 | super(e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/ThreadPool.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database; 2 | 3 | import java.util.concurrent.*; 4 | 5 | class ThreadPool extends ThreadPoolExecutor { 6 | ThreadPool() { 7 | super(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>()); 8 | } 9 | 10 | @Override 11 | protected void afterExecute(Runnable r, Throwable t) { 12 | super.afterExecute(r, t); 13 | if (t == null && r instanceof Future) { 14 | try { 15 | ((Future) r).get(); 16 | } catch (CancellationException ce) { 17 | t = ce; 18 | } catch (ExecutionException ee) { 19 | t = ee.getCause(); 20 | } catch (InterruptedException ie) { 21 | Thread.currentThread().interrupt(); // ignore/reset 22 | } 23 | } 24 | if (t != null) { 25 | rethrow(t); 26 | } 27 | } 28 | 29 | @SuppressWarnings("unchecked") 30 | private static void rethrow(Throwable t) throws T { 31 | // rethrows checked exceptions as unchecked 32 | throw (T) t; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/Bits.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common; 2 | 3 | import java.util.Arrays; 4 | 5 | public class Bits { 6 | public enum Bit { ZERO, ONE } 7 | 8 | /** 9 | * Get the ith bit of a byte where the 0th bit is the most significant bit 10 | * and the 7th bit is the least significant bit. Some examples: 11 | * 12 | * - getBit(0b10000000, 7) == ZERO 13 | * - getBit(0b10000000, 0) == ONE 14 | * - getBit(0b01000000, 1) == ONE 15 | * - getBit(0b00100000, 1) == ZERO 16 | */ 17 | static Bit getBit(byte b, int i) { 18 | if (i < 0 || i >= 8) { 19 | throw new IllegalArgumentException(String.format("index %d out of bounds", i)); 20 | } 21 | return ((b >> (7 - i)) & 1) == 0 ? Bit.ZERO : Bit.ONE; 22 | } 23 | 24 | /** 25 | * Get the ith bit of a byte array where the 0th bit is the most significat 26 | * bit of the first byte. Some examples: 27 | * 28 | * - getBit(new byte[]{0b10000000, 0b00000000}, 0) == ONE 29 | * - getBit(new byte[]{0b01000000, 0b00000000}, 1) == ONE 30 | * - getBit(new byte[]{0b00000000, 0b00000001}, 15) == ONE 31 | */ 32 | public static Bit getBit(byte[] bytes, int i) { 33 | if (bytes.length == 0 || i < 0 || i >= bytes.length * 8) { 34 | String err = String.format("bytes.length = %d; i = %d.", bytes.length, i); 35 | throw new IllegalArgumentException(err); 36 | } 37 | return getBit(bytes[i / 8], i % 8); 38 | } 39 | 40 | /** 41 | * Set the ith bit of a byte where the 0th bit is the most significant bit 42 | * and the 7th bit is the least significant bit. Some examples: 43 | * 44 | * - setBit(0b00000000, 0, ONE) == 0b10000000 45 | * - setBit(0b00000000, 1, ONE) == 0b01000000 46 | * - setBit(0b00000000, 2, ONE) == 0b00100000 47 | */ 48 | static byte setBit(byte b, int i, Bit bit) { 49 | if (i < 0 || i >= 8) { 50 | throw new IllegalArgumentException(String.format("index %d out of bounds", i)); 51 | } 52 | byte mask = (byte) (1 << (7 - i)); 53 | switch (bit) { 54 | case ZERO: { return (byte) (b & ~mask); } 55 | case ONE: { return (byte) (b | mask); } 56 | default: { throw new IllegalArgumentException("Unreachable code."); } 57 | } 58 | } 59 | 60 | /** 61 | * Set the ith bit of a byte array where the 0th bit is the most significant 62 | * bit of the first byte (arr[0]). An example: 63 | * 64 | * byte[] buf = new bytes[2]; // [0b00000000, 0b00000000] 65 | * setBit(buf, 0, ONE); // [0b10000000, 0b00000000] 66 | * setBit(buf, 1, ONE); // [0b11000000, 0b00000000] 67 | * setBit(buf, 2, ONE); // [0b11100000, 0b00000000] 68 | * setBit(buf, 15, ONE); // [0b11100000, 0b00000001] 69 | */ 70 | public static void setBit(byte[] bytes, int i, Bit bit) { 71 | bytes[i / 8] = setBit(bytes[i / 8], i % 8, bit); 72 | } 73 | 74 | /** 75 | * Counts the number of set bits. For example: 76 | * 77 | * - countBits(0b00001010) == 2 78 | * - countBits(0b11111101) == 7 79 | */ 80 | public static int countBits(byte b) { 81 | return Integer.bitCount(b); 82 | } 83 | 84 | /** 85 | * Counts the number of set bits. 86 | */ 87 | public static int countBits(byte[] bytes) { 88 | int count = 0; 89 | for (byte b : bytes) { 90 | count += countBits(b); 91 | } 92 | return count; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/Buffer.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common; 2 | 3 | public interface Buffer { 4 | Buffer get(byte[] dst, int offset, int length); 5 | byte get(int index); 6 | byte get(); 7 | Buffer get(byte[] dst); 8 | char getChar(); 9 | char getChar(int index); 10 | double getDouble(); 11 | double getDouble(int index); 12 | float getFloat(); 13 | float getFloat(int index); 14 | int getInt(); 15 | int getInt(int index); 16 | long getLong(); 17 | long getLong(int index); 18 | short getShort(); 19 | short getShort(int index); 20 | Buffer put(byte[] src, int offset, int length); 21 | Buffer put(byte[] src); 22 | Buffer put(byte b); 23 | Buffer put(int index, byte b); 24 | Buffer putChar(char value); 25 | Buffer putChar(int index, char value); 26 | Buffer putDouble(double value); 27 | Buffer putDouble(int index, double value); 28 | Buffer putFloat(float value); 29 | Buffer putFloat(int index, float value); 30 | Buffer putInt(int value); 31 | Buffer putInt(int index, int value); 32 | Buffer putLong(long value); 33 | Buffer putLong(int index, long value); 34 | Buffer putShort(short value); 35 | Buffer putShort(int index, short value); 36 | Buffer slice(); 37 | Buffer duplicate(); 38 | int position(); 39 | Buffer position(int pos); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/HashFunc.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common; 2 | 3 | import java.util.function.Function; 4 | import edu.berkeley.cs186.database.databox.DataBox; 5 | 6 | import java.math.BigDecimal; 7 | import java.math.BigInteger; 8 | import java.util.Random;; 9 | 10 | public class HashFunc { 11 | private static BigInteger p = BigInteger.valueOf(18618618661L); // Arbitrary prime > 32 bits 12 | private static BigInteger m = BigInteger.valueOf(1L << 32L); // 1 + max size of unsigned 32-bit 13 | private static Random generator = new Random(); 14 | 15 | public static Function getHashFunction(int pass) { 16 | assert(pass >= 1); 17 | if (pass == 1) { 18 | // First pass just uses regular hash function 19 | return (DataBox d) -> { return d.hashCode();}; 20 | } 21 | // Future passes select a random fine-grain hash function 22 | generator.setSeed(pass); 23 | final BigInteger a = generateBigA(p); 24 | final BigInteger b = generateBoundedBigInteger(p); 25 | 26 | // Take 170 to learn more about what's going on here! 27 | // https://en.wikipedia.org/wiki/Universal_hashing#Hashing_integers 28 | return (DataBox d) -> { 29 | BigInteger bhash = BigInteger.valueOf(d.hashCode()); 30 | BigInteger result = a.multiply(bhash).add(b).mod(p).mod(m); 31 | // Don't tell Nick Weaver, but we do an implicit 32 | // unsigned -> signed conversion here. Shhh... 33 | return result.intValue(); 34 | }; 35 | } 36 | 37 | /** 38 | * Generate a BigInteger between 0 and bound right exclusive. 39 | */ 40 | private static BigInteger generateBoundedBigInteger(BigInteger bound) { 41 | BigDecimal bbound = new BigDecimal(bound); 42 | BigDecimal scalar = BigDecimal.valueOf(generator.nextFloat()); 43 | BigDecimal scaled = bbound.multiply(scalar); 44 | return scaled.toBigInteger(); 45 | } 46 | 47 | /** 48 | * Same as above but in the highly unfortunate case a == 0 we reroll it 49 | */ 50 | private static BigInteger generateBigA(BigInteger bound) { 51 | BigInteger a; 52 | do { 53 | a = generateBoundedBigInteger(p); 54 | } while (a.equals(BigInteger.ZERO)); 55 | return a; // We can't let a be zero! Think about why... 56 | } 57 | } -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/Pair.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common; 2 | 3 | /** A simple, immutable, generic pair. */ 4 | public class Pair { 5 | private final A first; 6 | private final B second; 7 | 8 | public Pair(A first, B second) { 9 | this.first = first; 10 | this.second = second; 11 | } 12 | 13 | public A getFirst() { 14 | return first; 15 | } 16 | 17 | public B getSecond() { 18 | return second; 19 | } 20 | 21 | @Override 22 | public int hashCode() { 23 | int hashFirst = first != null ? first.hashCode() : 0; 24 | int hashSecond = second != null ? second.hashCode() : 0; 25 | return (hashFirst + hashSecond) * hashSecond + hashFirst; 26 | } 27 | 28 | @Override 29 | public boolean equals(Object other) { 30 | if (!(other instanceof Pair)) { 31 | return false; 32 | } 33 | 34 | Pair p = (Pair) other; 35 | boolean firstEquals = getFirst() == null 36 | ? p.getFirst() == null 37 | : getFirst().equals(p.getFirst()); 38 | boolean secondEquals = getSecond() == null 39 | ? p.getSecond() == null 40 | : getSecond().equals(p.getSecond()); 41 | return firstEquals && secondEquals; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "(" + first + ", " + second + ")"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/PredicateOperator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common; 2 | 3 | public enum PredicateOperator { 4 | EQUALS, 5 | NOT_EQUALS, 6 | LESS_THAN, 7 | LESS_THAN_EQUALS, 8 | GREATER_THAN, 9 | GREATER_THAN_EQUALS; 10 | 11 | public > boolean evaluate(T a, T b) { 12 | switch (this) { 13 | case EQUALS: 14 | return a.compareTo(b) == 0; 15 | case NOT_EQUALS: 16 | return a.compareTo(b) != 0; 17 | case LESS_THAN: 18 | return a.compareTo(b) < 0; 19 | case LESS_THAN_EQUALS: 20 | return a.compareTo(b) <= 0; 21 | case GREATER_THAN: 22 | return a.compareTo(b) > 0; 23 | case GREATER_THAN_EQUALS: 24 | return a.compareTo(b) >= 0; 25 | } 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/iterator/ArrayBacktrackingIterator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common.iterator; 2 | 3 | /** 4 | * Backtracking iterator over an array. 5 | */ 6 | public class ArrayBacktrackingIterator extends IndexBacktrackingIterator { 7 | protected T[] array; 8 | 9 | public ArrayBacktrackingIterator(T[] array) { 10 | super(array.length); 11 | this.array = array; 12 | } 13 | 14 | @Override 15 | protected int getNextNonempty(int currentIndex) { 16 | return currentIndex + 1; 17 | } 18 | 19 | @Override 20 | protected T getValue(int index) { 21 | return this.array[index]; 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/iterator/BacktrackingIterable.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common.iterator; 2 | 3 | public interface BacktrackingIterable extends Iterable { 4 | @Override 5 | BacktrackingIterator iterator(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/iterator/BacktrackingIterator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common.iterator; 2 | 3 | import java.util.Iterator; 4 | 5 | public interface BacktrackingIterator extends Iterator { 6 | /** 7 | * markPrev() marks the last returned value of the iterator, which is the last 8 | * returned value of next(). 9 | * 10 | * Calling markPrev() on an iterator that has not yielded a record yet, 11 | * or that has not yielded a record since the last reset() call does nothing. 12 | */ 13 | void markPrev(); 14 | 15 | /** 16 | * markNext() marks the next returned value of the iterator, which is the 17 | * value returned by the next call of next(). 18 | * 19 | * Calling markNext() on an iterator that has no records left, 20 | * or that has not yielded a record since the last reset() call does nothing. 21 | */ 22 | void markNext(); 23 | 24 | /** 25 | * reset() resets the iterator to the last marked location. 26 | * 27 | * The next next() call should return the value that was marked - if markPrev() 28 | * was used, this is the value returned by the next() call before markPrev(), and if 29 | * markNext() was used, this is the value returned by the next() call after markNext(). 30 | * If neither mark methods were called, reset() does nothing. You may reset() to the same 31 | * point as many times as desired, as long as neither mark method is called again. 32 | */ 33 | void reset(); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/iterator/ConcatBacktrackingIterator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common.iterator; 2 | 3 | /** 4 | * Iterator that concatenates a bunch of backtracking iterables together. 5 | */ 6 | public class ConcatBacktrackingIterator implements BacktrackingIterator { 7 | private BacktrackingIterator> outerIterator; 8 | 9 | private BacktrackingIterator prevItemIterator; 10 | private BacktrackingIterator nextItemIterator; 11 | private BacktrackingIterator markItemIterator; 12 | 13 | private boolean markMidIterator; 14 | 15 | public ConcatBacktrackingIterator(BacktrackingIterable> outerIterable) { 16 | this(outerIterable.iterator()); 17 | } 18 | 19 | public ConcatBacktrackingIterator(BacktrackingIterator> outerIterator) { 20 | this.outerIterator = outerIterator; 21 | this.prevItemIterator = null; 22 | this.nextItemIterator = new EmptyBacktrackingIterator<>(); 23 | this.markItemIterator = null; 24 | this.markMidIterator = false; 25 | 26 | this.moveNextToNonempty(); 27 | } 28 | 29 | private void moveNextToNonempty() { 30 | while (!this.nextItemIterator.hasNext() && this.outerIterator.hasNext()) { 31 | this.nextItemIterator = this.outerIterator.next().iterator(); 32 | } 33 | } 34 | 35 | @Override 36 | public boolean hasNext() { 37 | return this.nextItemIterator.hasNext(); 38 | } 39 | 40 | @Override 41 | public T next() { 42 | T item = this.nextItemIterator.next(); 43 | this.prevItemIterator = this.nextItemIterator; 44 | this.moveNextToNonempty(); 45 | return item; 46 | } 47 | 48 | @Override 49 | public void markPrev() { 50 | if (this.prevItemIterator == null) { 51 | return; 52 | } 53 | this.markItemIterator = this.prevItemIterator; 54 | this.markItemIterator.markPrev(); 55 | this.outerIterator.markPrev(); 56 | this.markMidIterator = (this.prevItemIterator == this.nextItemIterator); 57 | } 58 | 59 | @Override 60 | public void markNext() { 61 | this.markItemIterator = this.nextItemIterator; 62 | this.markItemIterator.markNext(); 63 | this.outerIterator.markNext(); 64 | this.markMidIterator = false; 65 | } 66 | 67 | @Override 68 | public void reset() { 69 | if (this.markItemIterator == null) { 70 | return; 71 | } 72 | this.prevItemIterator = null; 73 | this.nextItemIterator = this.markItemIterator; 74 | this.nextItemIterator.reset(); 75 | 76 | this.outerIterator.reset(); 77 | // either next = prev at time of markPrev, or next = nonempty after prev 78 | // we want outerIterator.next() to return iterable after prev; it returns next 79 | // skipping the empty iterables after prev is also ok -- since they would be skipped anyways 80 | if (this.markMidIterator) { 81 | this.outerIterator.next(); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/iterator/EmptyBacktrackingIterator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common.iterator; 2 | 3 | import java.util.NoSuchElementException; 4 | 5 | /** 6 | * Empty backtracking iterator. 7 | */ 8 | public class EmptyBacktrackingIterator implements BacktrackingIterator { 9 | @Override public boolean hasNext() { return false; } 10 | @Override public T next() { throw new NoSuchElementException(); } 11 | @Override public void markPrev() {} 12 | @Override public void markNext() {} 13 | @Override public void reset() {} 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/common/iterator/IndexBacktrackingIterator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.common.iterator; 2 | 3 | import java.util.NoSuchElementException; 4 | 5 | /** 6 | * Partial implementation of a backtracking iterator over an indexable collection 7 | * with some indices possibly not matching to a value. 8 | */ 9 | public abstract class IndexBacktrackingIterator implements BacktrackingIterator { 10 | private int maxIndex; 11 | private int prevIndex = -1; 12 | private int nextIndex = -1; 13 | private int markedIndex = -1; 14 | private int firstIndex = -1; 15 | 16 | public IndexBacktrackingIterator(int maxIndex) { 17 | this.maxIndex = maxIndex; 18 | } 19 | 20 | /** 21 | * Get the next nonempty index. Initial call uses -1. 22 | * @return next nonempty index or the max index if no more values. 23 | */ 24 | protected abstract int getNextNonempty(int currentIndex); 25 | 26 | /** 27 | * Get the value at the given index. Index will always be a value returned 28 | * by getNextNonempty. 29 | * @param index index to get value at 30 | * @return value at index 31 | */ 32 | protected abstract T getValue(int index); 33 | 34 | @Override 35 | public boolean hasNext() { 36 | if (this.nextIndex == -1) { 37 | this.nextIndex = this.firstIndex = this.getNextNonempty(-1); 38 | } 39 | return this.nextIndex < this.maxIndex; 40 | } 41 | 42 | @Override 43 | public T next() { 44 | if (!this.hasNext()) { 45 | throw new NoSuchElementException(); 46 | } 47 | T value = getValue(this.nextIndex); 48 | this.prevIndex = this.nextIndex; 49 | this.nextIndex = this.getNextNonempty(this.nextIndex); 50 | return value; 51 | } 52 | 53 | @Override 54 | public void markPrev() { 55 | // The second condition prevents using mark/reset/mark/reset/.. to 56 | // move the iterator backwards. 57 | if (this.nextIndex <= this.firstIndex || this.prevIndex < this.markedIndex) { 58 | return; 59 | } 60 | this.markedIndex = this.prevIndex; 61 | } 62 | 63 | @Override 64 | public void markNext() { 65 | if (this.nextIndex >= this.maxIndex) { 66 | return; 67 | } 68 | this.markedIndex = this.nextIndex; 69 | } 70 | 71 | @Override 72 | public void reset() { 73 | if (this.markedIndex == -1) { 74 | return; 75 | } 76 | this.nextIndex = this.markedIndex; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/concurrency/DummyLockContext.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | import edu.berkeley.cs186.database.TransactionContext; 4 | import edu.berkeley.cs186.database.common.Pair; 5 | 6 | /** 7 | * A lock context that doesn't do anything at all. Used where a lock context 8 | * is expected, but no locking should be done. 9 | */ 10 | public class DummyLockContext extends LockContext { 11 | public DummyLockContext() { 12 | this((LockContext) null); 13 | } 14 | 15 | public DummyLockContext(LockContext parent) { 16 | super(new DummyLockManager(), parent, new Pair<>("Unnamed", -1L)); 17 | } 18 | 19 | public DummyLockContext(Pair name) { 20 | this(null, name); 21 | } 22 | 23 | public DummyLockContext(LockContext parent, Pair name) { 24 | super(new DummyLockManager(), parent, name); 25 | } 26 | 27 | @Override 28 | public void acquire(TransactionContext transaction, LockType lockType) { } 29 | 30 | @Override 31 | public void release(TransactionContext transaction) { } 32 | 33 | @Override 34 | public void promote(TransactionContext transaction, LockType newLockType) { } 35 | 36 | @Override 37 | public void escalate(TransactionContext transaction) { } 38 | 39 | @Override 40 | public void disableChildLocks() { } 41 | 42 | @Override 43 | public LockContext childContext(String readable, long name) { 44 | return new DummyLockContext(this, new Pair<>(readable, name)); 45 | } 46 | 47 | @Override 48 | public int capacity() { 49 | return 0; 50 | } 51 | 52 | @Override 53 | public void capacity(int capacity) { 54 | } 55 | 56 | @Override 57 | public double saturation(TransactionContext transaction) { 58 | return 0.0; 59 | } 60 | 61 | @Override 62 | public LockType getExplicitLockType(TransactionContext transaction) { 63 | return LockType.NL; 64 | } 65 | 66 | @Override 67 | public LockType getEffectiveLockType(TransactionContext transaction) { 68 | return LockType.NL; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return "Dummy Lock Context(\" + name.toString() + \")"; 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/concurrency/DummyLockManager.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import edu.berkeley.cs186.database.TransactionContext; 7 | import edu.berkeley.cs186.database.common.Pair; 8 | 9 | /** 10 | * Dummy lock manager that does no locking or error checking. 11 | * 12 | * Used for non-locking-related tests to disable locking. 13 | */ 14 | public class DummyLockManager extends LockManager { 15 | public DummyLockManager() { } 16 | 17 | @Override 18 | public LockContext context(String readable, long name) { 19 | return new DummyLockContext(new Pair<>(readable, name)); 20 | } 21 | 22 | @Override 23 | public LockContext databaseContext() { 24 | return new DummyLockContext(new Pair<>("database", 0L)); 25 | } 26 | 27 | @Override 28 | public void acquireAndRelease(TransactionContext transaction, ResourceName name, 29 | LockType lockType, List releaseLocks) 30 | throws DuplicateLockRequestException, NoLockHeldException { } 31 | 32 | @Override 33 | public void acquire(TransactionContext transaction, ResourceName name, 34 | LockType lockType) throws DuplicateLockRequestException { } 35 | 36 | @Override 37 | public void release(TransactionContext transaction, ResourceName name) 38 | throws NoLockHeldException { } 39 | 40 | @Override 41 | public void promote(TransactionContext transaction, ResourceName name, 42 | LockType newLockType) 43 | throws DuplicateLockRequestException, NoLockHeldException, InvalidLockException { } 44 | 45 | @Override 46 | public LockType getLockType(TransactionContext transaction, ResourceName name) { 47 | return LockType.NL; 48 | } 49 | 50 | @Override 51 | public List getLocks(ResourceName name) { 52 | return Collections.emptyList(); 53 | } 54 | 55 | @Override 56 | public List getLocks(TransactionContext transaction) { 57 | return Collections.emptyList(); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/concurrency/DuplicateLockRequestException.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | public class DuplicateLockRequestException extends RuntimeException { 4 | DuplicateLockRequestException(String message) { 5 | super(message); 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/concurrency/InvalidLockException.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | public class InvalidLockException extends RuntimeException { 4 | InvalidLockException(String message) { 5 | super(message); 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/concurrency/Lock.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | /** 4 | * Represents a lock held by a transaction on a resource. 5 | */ 6 | public class Lock { 7 | public ResourceName name; 8 | public LockType lockType; 9 | public Long transactionNum; 10 | 11 | public Lock(ResourceName name, LockType lockType, long transactionNum) { 12 | this.name = name; 13 | this.lockType = lockType; 14 | this.transactionNum = transactionNum; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object other) { 19 | if (other instanceof Lock) { 20 | Lock l = (Lock) other; 21 | return l.name.equals(name) && lockType == l.lockType && transactionNum.equals(l.transactionNum); 22 | } else { 23 | return false; 24 | } 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | return 37 * (37 * name.hashCode() + lockType.hashCode()) + transactionNum.hashCode(); 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "T" + transactionNum.toString() + ": " + lockType.toString() + "(" + name.toString() + ")"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/concurrency/LockRequest.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | import edu.berkeley.cs186.database.TransactionContext; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | /** 9 | * Represents a lock request on the queue, for 10 | * TRANSACTION requesting LOCK and releasing everything in RELEASEDLOCKS. 11 | * LOCK should be granted and everything in RELEASEDLOCKS should be released 12 | * *before* the transaction is unblocked. 13 | */ 14 | class LockRequest { 15 | TransactionContext transaction; 16 | Lock lock; 17 | List releasedLocks; 18 | 19 | // Lock request for LOCK, that is not releasing anything. 20 | LockRequest(TransactionContext transaction, Lock lock) { 21 | this.transaction = transaction; 22 | this.lock = lock; 23 | this.releasedLocks = Collections.emptyList(); 24 | } 25 | 26 | // Lock request for LOCK, in exchange for all the locks in RELEASEDLOCKS. 27 | LockRequest(TransactionContext transaction, Lock lock, List releasedLocks) { 28 | this.transaction = transaction; 29 | this.lock = lock; 30 | this.releasedLocks = releasedLocks; 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "Request for " + lock.toString() + " (releasing " + releasedLocks.toString() + ")"; 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/concurrency/NoLockHeldException.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | public class NoLockHeldException extends RuntimeException { 4 | NoLockHeldException(String message) { 5 | super(message); 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/concurrency/ResourceName.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | import edu.berkeley.cs186.database.common.Pair; 4 | 5 | import java.util.*; 6 | 7 | import static java.util.stream.Collectors.toList; 8 | 9 | /** 10 | * This class represents the full name of a resource. The name 11 | * of a resource is an ordered tuple of integers, and any 12 | * subsequence of the tuple starting with the first element 13 | * is the name of a resource higher up on the hierarchy. For debugging 14 | * aid, we attach a string to each integer (which is only used in toString()). 15 | * 16 | * For example, a page may have the name (0, 3, 10), where 3 is the table's 17 | * partition number and 10 is the page number. We store this as the list 18 | * [("database, 0), ("Students", 3), ("10", 10)], and its ancestors on the 19 | * hierarchy would be [("database", 0)] (which represents the entire database), 20 | * and [("database", 0), ("Students", 3)] (which 21 | * represents the Students table, of which this is a page of). 22 | */ 23 | public class ResourceName { 24 | private final List> names; 25 | private final int hash; 26 | 27 | public ResourceName(Pair name) { 28 | this(Collections.singletonList(name)); 29 | } 30 | 31 | private ResourceName(List> names) { 32 | this.names = new ArrayList<>(names); 33 | this.hash = names.stream().map(x -> x == null ? null : x.getSecond()).collect(toList()).hashCode(); 34 | } 35 | 36 | ResourceName(ResourceName parent, Pair name) { 37 | names = new ArrayList<>(parent.names); 38 | names.add(name); 39 | this.hash = names.stream().map(x -> x == null ? null : x.getSecond()).collect(toList()).hashCode(); 40 | } 41 | 42 | ResourceName parent() { 43 | if (names.size() > 1) { 44 | return new ResourceName(names.subList(0, names.size() - 1)); 45 | } else { 46 | return null; 47 | } 48 | } 49 | 50 | boolean isDescendantOf(ResourceName other) { 51 | if (other.names.size() >= names.size()) { 52 | return false; 53 | } 54 | Iterator> mine = names.iterator(); 55 | Iterator> others = other.names.iterator(); 56 | while (others.hasNext()) { 57 | if (!mine.next().getSecond().equals(others.next().getSecond())) { 58 | return false; 59 | } 60 | } 61 | return true; 62 | } 63 | 64 | Pair getCurrentName() { 65 | return names.get(names.size() - 1); 66 | } 67 | 68 | List> getNames() { 69 | return names; 70 | } 71 | 72 | @Override 73 | public boolean equals(Object other) { 74 | if (!(other instanceof ResourceName)) { 75 | return false; 76 | } 77 | ResourceName n = (ResourceName) other; 78 | return n.hash == hash; 79 | } 80 | 81 | @Override 82 | public int hashCode() { 83 | return hash; 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | StringBuilder rn = new StringBuilder(names.get(0).getFirst()); 89 | for (int i = 1; i < names.size(); ++i) { 90 | rn.append('/').append(names.get(i).getFirst()); 91 | } 92 | return rn.toString(); 93 | } 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/BoolDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import java.nio.ByteBuffer; 4 | 5 | public class BoolDataBox extends DataBox { 6 | private boolean b; 7 | 8 | public BoolDataBox(boolean b) { 9 | this.b = b; 10 | } 11 | 12 | @Override 13 | public Type type() { 14 | return Type.boolType(); 15 | } 16 | 17 | @Override 18 | public boolean getBool() { 19 | return this.b; 20 | } 21 | 22 | @Override 23 | public byte[] toBytes() { 24 | byte val = b ? (byte) 1 : (byte) 0; 25 | return ByteBuffer.allocate(1).put(val).array(); 26 | } 27 | 28 | @Override 29 | public String toString() { 30 | return Boolean.toString(b); 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) { 36 | return true; 37 | } 38 | if (!(o instanceof BoolDataBox)) { 39 | return false; 40 | } 41 | BoolDataBox b = (BoolDataBox) o; 42 | return this.b == b.b; 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | return Boolean.valueOf(b).hashCode(); 48 | } 49 | 50 | @Override 51 | public int compareTo(DataBox d) { 52 | if (!(d instanceof BoolDataBox)) { 53 | String err = String.format("Invalid comparison between %s and %s.", 54 | toString(), d.toString()); 55 | throw new DataBoxException(err); 56 | } 57 | BoolDataBox b = (BoolDataBox) d; 58 | return Boolean.compare(this.b, b.b); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/DataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | 5 | import java.nio.charset.Charset; 6 | 7 | /** 8 | * A DataBox is an element of one of the primitive types specified in 9 | * Type.java. You can create 10 | * 11 | * - booleans with new BoolDataBox(b), 12 | * - integers with new IntDataBox(i), 13 | * - floats with new FloatDataBox(f), 14 | * - strings with new StringDataBox(s, n), and 15 | * - longs with new LongDataBox(l). 16 | * 17 | * You can unwrap a databox by first pattern matching on its type and then 18 | * using one of getBool, getInt, getFloat, getString, and getLong: 19 | * 20 | * Databox d = DataBox.fromBytes(bytes); 21 | * switch (d.type().getTypeId()) { 22 | * case BOOL: { System.out.println(d.getBool()); } 23 | * case INT: { System.out.println(d.getInt()); } 24 | * case FLOAT: { System.out.println(d.getFloat()); } 25 | * case STRING: { System.out.println(d.getString()); } 26 | * case LONG: { System.out.println(d.getLong()); } 27 | * } 28 | */ 29 | public abstract class DataBox implements Comparable { 30 | public abstract Type type(); 31 | 32 | public boolean getBool() { 33 | throw new DataBoxException("not boolean type"); 34 | } 35 | 36 | public int getInt() { 37 | throw new DataBoxException("not int type"); 38 | } 39 | 40 | public float getFloat() { 41 | throw new DataBoxException("not float type"); 42 | } 43 | 44 | public String getString() { 45 | throw new DataBoxException("not String type"); 46 | } 47 | 48 | public long getLong() { 49 | throw new DataBoxException("not Long type"); 50 | } 51 | 52 | // Databoxes are serialized as follows: 53 | // 54 | // - BoolDataBoxes are serialized to a single byte that is 0 if the 55 | // BoolDataBox is false and 1 if the Databox is true. 56 | // - An IntDataBox and a FloatDataBox are serialized to their 4-byte 57 | // values (e.g. using ByteBuffer::putInt or ByteBuffer::putFloat). 58 | // - The first byte of a serialized m-byte StringDataBox is the 4-byte 59 | // number m. Then come the m bytes of the string. 60 | // 61 | // Note that when DataBoxes are serialized, they do not serialize their type. 62 | // That is, serialized DataBoxes are not self-descriptive; you need the type 63 | // of a Databox in order to parse it. 64 | public abstract byte[] toBytes(); 65 | 66 | public static DataBox fromBytes(Buffer buf, Type type) { 67 | switch (type.getTypeId()) { 68 | case BOOL: { 69 | byte b = buf.get(); 70 | assert (b == 0 || b == 1); 71 | return new BoolDataBox(b == 1); 72 | } 73 | case INT: { 74 | return new IntDataBox(buf.getInt()); 75 | } 76 | case FLOAT: { 77 | return new FloatDataBox(buf.getFloat()); 78 | } 79 | case STRING: { 80 | byte[] bytes = new byte[type.getSizeInBytes()]; 81 | buf.get(bytes); 82 | String s = new String(bytes, Charset.forName("UTF-8")); 83 | return new StringDataBox(s, type.getSizeInBytes()); 84 | } 85 | case LONG: { 86 | return new LongDataBox(buf.getLong()); 87 | } 88 | default: { 89 | String err = String.format("Unhandled TypeId %s.", 90 | type.getTypeId().toString()); 91 | throw new IllegalArgumentException(err); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/DataBoxException.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | public class DataBoxException extends RuntimeException { 4 | public DataBoxException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/FloatDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | import java.nio.ByteBuffer; 3 | 4 | public class FloatDataBox extends DataBox { 5 | private float f; 6 | 7 | public FloatDataBox(float f) { 8 | this.f = f; 9 | } 10 | 11 | @Override 12 | public Type type() { 13 | return Type.floatType(); 14 | } 15 | 16 | @Override 17 | public float getFloat() { 18 | return this.f; 19 | } 20 | 21 | @Override 22 | public byte[] toBytes() { 23 | return ByteBuffer.allocate(Float.BYTES).putFloat(f).array(); 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return Float.toString(f); 29 | } 30 | 31 | @Override 32 | public boolean equals(Object o) { 33 | if (this == o) { 34 | return true; 35 | } 36 | if (!(o instanceof FloatDataBox)) { 37 | return false; 38 | } 39 | FloatDataBox f = (FloatDataBox) o; 40 | return this.f == f.f; 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return new Float(f).hashCode(); 46 | } 47 | 48 | @Override 49 | public int compareTo(DataBox d) { 50 | if (!(d instanceof FloatDataBox)) { 51 | String err = String.format("Invalid comparison between %s and %s.", 52 | toString(), d.toString()); 53 | throw new DataBoxException(err); 54 | } 55 | FloatDataBox f = (FloatDataBox) d; 56 | return Float.compare(this.f, f.f); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/IntDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | import java.nio.ByteBuffer; 3 | 4 | public class IntDataBox extends DataBox { 5 | private int i; 6 | 7 | public IntDataBox(int i) { 8 | this.i = i; 9 | } 10 | 11 | @Override 12 | public Type type() { 13 | return Type.intType(); 14 | } 15 | 16 | @Override 17 | public int getInt() { 18 | return this.i; 19 | } 20 | 21 | @Override 22 | public byte[] toBytes() { 23 | return ByteBuffer.allocate(Integer.BYTES).putInt(i).array(); 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return Integer.toString(i); 29 | } 30 | 31 | @Override 32 | public boolean equals(Object o) { 33 | if (o == this) { 34 | return true; 35 | } 36 | if (!(o instanceof IntDataBox)) { 37 | return false; 38 | } 39 | IntDataBox i = (IntDataBox) o; 40 | return this.i == i.i; 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return new Integer(i).hashCode(); 46 | } 47 | 48 | @Override 49 | public int compareTo(DataBox d) { 50 | if (!(d instanceof IntDataBox)) { 51 | String err = String.format("Invalid comparison between %s and %s.", 52 | toString(), d.toString()); 53 | throw new DataBoxException(err); 54 | } 55 | IntDataBox i = (IntDataBox) d; 56 | return Integer.compare(this.i, i.i); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/LongDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | import java.nio.ByteBuffer; 3 | 4 | public class LongDataBox extends DataBox { 5 | private long l; 6 | 7 | public LongDataBox(long l) { 8 | this.l = l; 9 | } 10 | 11 | @Override 12 | public Type type() { 13 | return Type.longType(); 14 | } 15 | 16 | @Override 17 | public long getLong() { 18 | return this.l; 19 | } 20 | 21 | @Override 22 | public byte[] toBytes() { 23 | return ByteBuffer.allocate(Long.BYTES).putLong(l).array(); 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return Long.toString(l); 29 | } 30 | 31 | @Override 32 | public boolean equals(Object o) { 33 | if (o == this) { 34 | return true; 35 | } 36 | if (!(o instanceof LongDataBox)) { 37 | return false; 38 | } 39 | LongDataBox l = (LongDataBox) o; 40 | return this.l == l.l; 41 | } 42 | 43 | @Override 44 | public int hashCode() { 45 | return new Long(l).hashCode(); 46 | } 47 | 48 | @Override 49 | public int compareTo(DataBox d) { 50 | if (!(d instanceof LongDataBox)) { 51 | String err = String.format("Invalid comparison between %s and %s.", 52 | toString(), d.toString()); 53 | throw new DataBoxException(err); 54 | } 55 | LongDataBox l = (LongDataBox) d; 56 | return Long.compare(this.l, l.l); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/StringDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | public class StringDataBox extends DataBox { 6 | private String s; 7 | 8 | // Construct an m-byte string. If s has more than m-bytes, it is truncated to 9 | // its first m bytes. If s has fewer than m bytes, it is padded with null bytes 10 | // until it is exactly m bytes long. 11 | // 12 | // - new StringDataBox("123", 5).getString() == "123\x0\x0" 13 | // - new StringDataBox("12345", 5).getString() == "12345" 14 | // - new StringDataBox("1234567", 5).getString() == "12345" 15 | public StringDataBox(String s, int m) { 16 | if (m <= 0) { 17 | String msg = String.format("Cannot construct a %d-byte string. " + 18 | "Strings must be at least one byte.", m); 19 | throw new DataBoxException(msg); 20 | } 21 | 22 | if (m < s.length()) { 23 | this.s = s.substring(0, m); 24 | } else { 25 | this.s = s + new String(new char[m - s.length()]); 26 | } 27 | assert(this.s.length() == m); 28 | } 29 | 30 | @Override 31 | public Type type() { 32 | return Type.stringType(s.length()); 33 | } 34 | 35 | @Override 36 | public String getString() { 37 | return s.indexOf(0) < 0 ? s : s.substring(0, s.indexOf(0)); 38 | } 39 | 40 | @Override 41 | public byte[] toBytes() { 42 | return s.getBytes(Charset.forName("ascii")); 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | // TODO(hw0): replace with return s; 48 | // return "Welcome to CS186 (original string: " + s + ")"; 49 | return s; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) { 55 | return true; 56 | } 57 | if (!(o instanceof StringDataBox)) { 58 | return false; 59 | } 60 | StringDataBox s = (StringDataBox) o; 61 | return this.s.equals(s.s); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return s.hashCode(); 67 | } 68 | 69 | @Override 70 | public int compareTo(DataBox d) { 71 | if (!(d instanceof StringDataBox)) { 72 | String err = String.format("Invalid comparison between %s and %s.", 73 | toString(), d.toString()); 74 | throw new DataBoxException(err); 75 | } 76 | StringDataBox s = (StringDataBox) d; 77 | return this.s.compareTo(s.s); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/Type.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.util.Objects; 7 | 8 | /** 9 | * There are five primitive types: 10 | * 11 | * 1. 1-byte booleans (Type.boolType()), 12 | * 2. 4-byte integers (Type.intType()), 13 | * 3. 4-byte floats (Type.floatType()), and 14 | * 4. n-byte strings (Type.stringType(n)) where n > 0. 15 | * 5. 8-byte integers (Type.longType()) 16 | * 17 | * Note that n-byte strings and m-byte strings are considered different types 18 | * when n != m. 19 | */ 20 | public class Type { 21 | // The type of this type. 22 | private TypeId typeId; 23 | 24 | // The size (in bytes) of an element of this type. 25 | private int sizeInBytes; 26 | 27 | public Type(TypeId typeId, int sizeInBytes) { 28 | this.typeId = typeId; 29 | this.sizeInBytes = sizeInBytes; 30 | } 31 | 32 | public static Type boolType() { 33 | // Unlike all the other primitive type boxes (e.g. Integer, Float), Boolean 34 | // does not have a BYTES field, so we hand code the fact that Java booleans 35 | // are 1 byte. 36 | return new Type(TypeId.BOOL, 1); 37 | } 38 | 39 | public static Type intType() { 40 | return new Type(TypeId.INT, Integer.BYTES); 41 | } 42 | 43 | public static Type floatType() { 44 | return new Type(TypeId.FLOAT, Float.BYTES); 45 | } 46 | 47 | public static Type stringType(int n) { 48 | if (n < 0) { 49 | String msg = String.format("The provided string length %d is negative.", n); 50 | throw new DataBoxException(msg); 51 | } 52 | if (n == 0) { 53 | String msg = "Empty strings are not supported."; 54 | throw new DataBoxException(msg); 55 | } 56 | return new Type(TypeId.STRING, n); 57 | } 58 | 59 | public static Type longType() { 60 | return new Type(TypeId.LONG, Long.BYTES); 61 | } 62 | 63 | public TypeId getTypeId() { 64 | return typeId; 65 | } 66 | 67 | public int getSizeInBytes() { 68 | return sizeInBytes; 69 | } 70 | 71 | public byte[] toBytes() { 72 | // A Type is uniquely identified by its typeId `t` and the size (in bytes) 73 | // of an element of the type `s`. A Type is serialized as two integers. The 74 | // first is the ordinal corresponding to `t`. The second is `s`. 75 | // 76 | // For example, the type "42-byte string" would serialized as the bytes [3, 77 | // 42] because 3 is the ordinal of the STRING TypeId and 42 is the number 78 | // of bytes in a 42-byte string (duh). 79 | ByteBuffer buf = ByteBuffer.allocate(Integer.BYTES * 2); 80 | buf.putInt(typeId.ordinal()); 81 | buf.putInt(sizeInBytes); 82 | return buf.array(); 83 | } 84 | 85 | public static Type fromBytes(Buffer buf) { 86 | int ordinal = buf.getInt(); 87 | int sizeInBytes = buf.getInt(); 88 | switch (TypeId.fromInt(ordinal)) { 89 | case BOOL: 90 | assert(sizeInBytes == 1); 91 | return Type.boolType(); 92 | case INT: 93 | assert(sizeInBytes == Integer.BYTES); 94 | return Type.intType(); 95 | case FLOAT: 96 | assert(sizeInBytes == Float.BYTES); 97 | return Type.floatType(); 98 | case STRING: 99 | return Type.stringType(sizeInBytes); 100 | case LONG: 101 | assert(sizeInBytes == Long.BYTES); 102 | return Type.longType(); 103 | default: 104 | throw new RuntimeException("unreachable"); 105 | } 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return String.format("(%s, %d)", typeId.toString(), sizeInBytes); 111 | } 112 | 113 | @Override 114 | public boolean equals(Object o) { 115 | if (o == this) { 116 | return true; 117 | } 118 | if (!(o instanceof Type)) { 119 | return false; 120 | } 121 | Type t = (Type) o; 122 | return typeId.equals(t.typeId) && sizeInBytes == t.sizeInBytes; 123 | } 124 | 125 | @Override 126 | public int hashCode() { 127 | return Objects.hash(typeId, sizeInBytes); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/databox/TypeId.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | public enum TypeId { 4 | BOOL, 5 | INT, 6 | FLOAT, 7 | STRING, 8 | LONG; 9 | 10 | private static final TypeId[] values = TypeId.values(); 11 | 12 | public static TypeId fromInt(int x) { 13 | if (x < 0 || x >= values.length) { 14 | String err = String.format("Unknown TypeId ordinal %d.", x); 15 | throw new IllegalArgumentException(err); 16 | } 17 | return values[x]; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/index/BPlusTreeException.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.index; 2 | 3 | public class BPlusTreeException extends RuntimeException { 4 | public BPlusTreeException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/index/BPlusTreeMetadata.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.index; 2 | 3 | import edu.berkeley.cs186.database.databox.Type; 4 | 5 | /** Metadata about a B+ tree. */ 6 | public class BPlusTreeMetadata { 7 | // Table for which this B+ tree is for 8 | private final String tableName; 9 | 10 | // Column that this B+ tree uses as a search key 11 | private final String colName; 12 | 13 | // B+ trees map keys (of some type) to record ids. This is the type of the 14 | // keys. 15 | private final Type keySchema; 16 | 17 | // The order of the tree. Given a tree of order d, its inner nodes store 18 | // between d and 2d keys and between d+1 and 2d+1 children pointers. Leaf 19 | // nodes store between d and 2d (key, record id) pairs. Notable exceptions 20 | // include the root node and leaf nodes that have been deleted from; these 21 | // may contain fewer than d entries. 22 | private final int order; 23 | 24 | // The partition that the B+ tree allocates pages from. Every node of the B+ tree 25 | // is stored on a different page on this partition. 26 | private final int partNum; 27 | 28 | // The page number of the root node. 29 | private long rootPageNum; 30 | 31 | // The height of this tree. 32 | private int height; 33 | 34 | public BPlusTreeMetadata(String tableName, String colName, Type keySchema, int order, int partNum, 35 | long rootPageNum, int height) { 36 | this.tableName = tableName; 37 | this.colName = colName; 38 | this.keySchema = keySchema; 39 | this.order = order; 40 | this.partNum = partNum; 41 | this.rootPageNum = rootPageNum; 42 | this.height = height; 43 | } 44 | 45 | public BPlusTreeMetadata(String tableName, String colName) { 46 | this(tableName, colName, Type.intType(), -1, -1, -1, -1); 47 | } 48 | 49 | public String getTableName() { 50 | return tableName; 51 | } 52 | 53 | public String getColName() { 54 | return colName; 55 | } 56 | 57 | public String getName() { 58 | return tableName + "," + colName; 59 | } 60 | 61 | public Type getKeySchema() { 62 | return keySchema; 63 | } 64 | 65 | public int getOrder() { 66 | return order; 67 | } 68 | 69 | public int getPartNum() { 70 | return partNum; 71 | } 72 | 73 | public long getRootPageNum() { 74 | return rootPageNum; 75 | } 76 | 77 | void setRootPageNum(long rootPageNum) { 78 | this.rootPageNum = rootPageNum; 79 | } 80 | 81 | public int getHeight() { 82 | return height; 83 | } 84 | 85 | void incrementHeight() { 86 | ++height; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/io/DiskSpaceManager.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.io; 2 | 3 | public interface DiskSpaceManager extends AutoCloseable { 4 | short PAGE_SIZE = 4096; // size of a page in bytes 5 | long INVALID_PAGE_NUM = -1L; // a page number that is always invalid 6 | 7 | @Override 8 | void close(); 9 | 10 | /** 11 | * Allocates a new partition. 12 | * 13 | * @return partition number of new partition 14 | */ 15 | int allocPart(); 16 | 17 | /** 18 | * Allocates a new partition with a specific partition number. 19 | * 20 | * @param partNum partition number of new partition 21 | * @return partition number of new partition 22 | */ 23 | int allocPart(int partNum); 24 | 25 | /** 26 | * Releases a partition from use. 27 | 28 | * @param partNum partition number to be released 29 | */ 30 | void freePart(int partNum); 31 | 32 | /** 33 | * Allocates a new page. 34 | * @param partNum partition to allocate new page under 35 | * @return virtual page number of new page 36 | */ 37 | long allocPage(int partNum); 38 | 39 | /** 40 | * Allocates a new page with a specific page number. 41 | * @param pageNum page number of new page 42 | * @return virtual page number of new page 43 | */ 44 | long allocPage(long pageNum); 45 | 46 | /** 47 | * Frees a page. The page cannot be used after this call. 48 | * @param page virtual page number of page to be released 49 | */ 50 | void freePage(long page); 51 | 52 | /** 53 | * Reads a page. 54 | * 55 | * @param page number of page to be read 56 | * @param buf byte buffer whose contents will be filled with page data 57 | */ 58 | void readPage(long page, byte[] buf); 59 | 60 | /** 61 | * Writes to a page. 62 | * 63 | * @param page number of page to be read 64 | * @param buf byte buffer that contains the new page data 65 | */ 66 | void writePage(long page, byte[] buf); 67 | 68 | /** 69 | * Checks if a page is allocated 70 | * 71 | * @param page number of page to check 72 | * @return true if the page is allocated, false otherwise 73 | */ 74 | boolean pageAllocated(long page); 75 | 76 | /** 77 | * Gets partition number from virtual page number 78 | * @param page virtual page number 79 | * @return partition number 80 | */ 81 | static int getPartNum(long page) { 82 | return (int) (page / 10000000000L); 83 | } 84 | 85 | /** 86 | * Gets data page number from virtual page number 87 | * @param page virtual page number 88 | * @return data page number 89 | */ 90 | static int getPageNum(long page) { 91 | return (int) (page % 10000000000L); 92 | } 93 | 94 | /** 95 | * Gets the virtual page number given partition/data page number 96 | * @param partNum partition number 97 | * @param pageNum data page number 98 | * @return virtual page number 99 | */ 100 | static long getVirtualPageNum(int partNum, int pageNum) { 101 | return partNum * 10000000000L + pageNum; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/io/PageException.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.io; 2 | 3 | /** 4 | * Exception thrown for errors while paging. 5 | */ 6 | public class PageException extends RuntimeException { 7 | public PageException() { 8 | super(); 9 | } 10 | 11 | public PageException(Exception e) { 12 | super(e); 13 | } 14 | 15 | public PageException(String message) { 16 | super(message); 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/memory/BufferFrame.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.memory; 2 | 3 | /** 4 | * Buffer frame. 5 | */ 6 | abstract class BufferFrame { 7 | Object tag = null; 8 | private int pinCount = 0; 9 | 10 | /** 11 | * Pin buffer frame; cannot be evicted while pinned. A "hit" happens when the 12 | * buffer frame gets pinned. 13 | */ 14 | void pin() { 15 | ++pinCount; 16 | } 17 | 18 | /** 19 | * Unpin buffer frame. 20 | */ 21 | void unpin() { 22 | if (!isPinned()) { 23 | throw new IllegalStateException("cannot unpin unpinned frame"); 24 | } 25 | --pinCount; 26 | } 27 | 28 | /** 29 | * @return whether this frame is pinned 30 | */ 31 | boolean isPinned() { 32 | return pinCount > 0; 33 | } 34 | 35 | /** 36 | * @return whether this frame is valid 37 | */ 38 | abstract boolean isValid(); 39 | 40 | /** 41 | * @return page number of this frame 42 | */ 43 | abstract long getPageNum(); 44 | 45 | /** 46 | * Flushes this buffer frame to disk, but does not unload it. 47 | */ 48 | abstract void flush(); 49 | 50 | /** 51 | * Read from the buffer frame. 52 | * @param position position in buffer frame to start reading 53 | * @param num number of bytes to read 54 | * @param buf output buffer 55 | */ 56 | abstract void readBytes(short position, short num, byte[] buf); 57 | 58 | /** 59 | * Write to the buffer frame, and mark frame as dirtied. 60 | * @param position position in buffer frame to start writing 61 | * @param num number of bytes to write 62 | * @param buf input buffer 63 | */ 64 | abstract void writeBytes(short position, short num, byte[] buf); 65 | 66 | /** 67 | * Requests a valid Frame object for the page (if invalid, a new Frame object is returned). 68 | * Frame is pinned on return. 69 | */ 70 | abstract BufferFrame requestValidFrame(); 71 | 72 | /** 73 | * @return amount of space available to user of the frame 74 | */ 75 | short getEffectivePageSize() { 76 | return BufferManager.EFFECTIVE_PAGE_SIZE; 77 | } 78 | 79 | /** 80 | * @param pageLSN new pageLSN of the page loaded in this frame 81 | */ 82 | abstract void setPageLSN(long pageLSN); 83 | 84 | /** 85 | * @return pageLSN of the page loaded in this frame 86 | */ 87 | abstract long getPageLSN(); 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/memory/BufferManager.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.memory; 2 | 3 | import edu.berkeley.cs186.database.concurrency.LockContext; 4 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 5 | 6 | import java.util.function.BiConsumer; 7 | 8 | public interface BufferManager extends AutoCloseable { 9 | // We reserve 36 bytes on each page for bookkeeping for recovery 10 | // (used to store the pageLSN, and to ensure that a redo-only/undo-only log record can 11 | // fit on one page). 12 | short RESERVED_SPACE = 36; 13 | 14 | // Effective page size available to users of buffer manager. 15 | short EFFECTIVE_PAGE_SIZE = DiskSpaceManager.PAGE_SIZE - RESERVED_SPACE; 16 | 17 | @Override 18 | void close(); 19 | 20 | /** 21 | * Fetches the specified page, with a loaded and pinned buffer frame. 22 | * 23 | * @param parentContext lock context of the **parent** of the page being fetched 24 | * @param pageNum page number 25 | * @param logPage whether the page is for the log or not 26 | * @return specified page 27 | */ 28 | Page fetchPage(LockContext parentContext, long pageNum, boolean logPage); 29 | 30 | /** 31 | * Fetches a new page, with a loaded and pinned buffer frame. 32 | * 33 | * @param parentContext parent lock context of the new page 34 | * @param partNum partition number for new page 35 | * @param logPage whether the page is for the log or not 36 | * @return the new page 37 | */ 38 | Page fetchNewPage(LockContext parentContext, int partNum, boolean logPage); 39 | 40 | /** 41 | * Frees a page - evicts the page from cache, and tells the disk space manager 42 | * that the page is no longer needed. Page must be pinned before this call, 43 | * and cannot be used after this call (aside from unpinning). 44 | * 45 | * @param page page to free 46 | */ 47 | void freePage(Page page); 48 | 49 | /** 50 | * Frees a partition - evicts all relevant pages from cache, and tells the disk space manager 51 | * that the partition is no longer needed. No pages in the partition may be pinned before this call, 52 | * and cannot be used after this call. 53 | * 54 | * @param partNum partition number to free 55 | */ 56 | void freePart(int partNum); 57 | 58 | /** 59 | * Fetches a buffer frame with data for the specified page. Reuses existing buffer frame 60 | * if page already loaded in memory. Pins the buffer frame. Cannot be used outside the package. 61 | * 62 | * @param pageNum page number 63 | * @param logPage whether the page is for the log or not 64 | * @return buffer frame with specified page loaded 65 | */ 66 | BufferFrame fetchPageFrame(long pageNum, boolean logPage); 67 | 68 | /** 69 | * Fetches a buffer frame for a new page. Pins the buffer frame. Cannot be used outside the package. 70 | * 71 | * @param partNum partition number for new page 72 | * @param logPage whether the page is for the log or not 73 | * @return buffer frame for the new page 74 | */ 75 | BufferFrame fetchNewPageFrame(int partNum, boolean logPage); 76 | 77 | /** 78 | * Calls flush on the frame of a page and unloads the page from the frame. If the page 79 | * is not loaded, this does nothing. 80 | * @param pageNum page number of page to evict 81 | */ 82 | void evict(long pageNum); 83 | 84 | /** 85 | * Calls evict on every frame in sequence. 86 | */ 87 | void evictAll(); 88 | 89 | /** 90 | * Calls the passed in method with the page number of every loaded page. 91 | * @param process method to consume page numbers. The first parameter is the page number, 92 | * and the second parameter is a boolean indicating whether the page is dirty 93 | * (has an unflushed change). 94 | */ 95 | void iterPageNums(BiConsumer process); 96 | 97 | /** 98 | * Get the number of I/Os since the buffer manager was started, excluding anything used in disk 99 | * space management, and not counting allocation/free. This is not really useful except as a 100 | * relative measure. 101 | * @return number of I/Os 102 | */ 103 | long getNumIOs(); 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/memory/ClockEvictionPolicy.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.memory; 2 | 3 | /** 4 | * Implementation of clock eviction policy, which works by adding a reference 5 | * bit to each frame, and running the algorithm. 6 | */ 7 | public class ClockEvictionPolicy implements EvictionPolicy { 8 | private int arm; 9 | 10 | private static final Object ACTIVE = true; 11 | // null not false, because default tag (before this class ever sees a frame) is null. 12 | private static final Object INACTIVE = null; 13 | 14 | public ClockEvictionPolicy() { 15 | this.arm = 0; 16 | } 17 | 18 | /** 19 | * Called to initiaize a new buffer frame. 20 | * @param frame new frame to be initialized 21 | */ 22 | @Override 23 | public void init(BufferFrame frame) {} 24 | 25 | /** 26 | * Called when a frame is hit. 27 | * @param frame Frame object that is being read from/written to 28 | */ 29 | @Override 30 | public void hit(BufferFrame frame) { 31 | frame.tag = ACTIVE; 32 | } 33 | 34 | /** 35 | * Called when a frame needs to be evicted. 36 | * @param frames Array of all frames (same length every call) 37 | * @return index of frame to be evicted 38 | * @throws IllegalStateException if everything is pinned 39 | */ 40 | @Override 41 | public BufferFrame evict(BufferFrame[] frames) { 42 | int iters = 0; 43 | // loop around the frames looking for a frame that has bit 0 44 | // iters is used to ensure that we don't loop forever - after two 45 | // passes through the frames, every frame has bit 0, so if we still haven't 46 | // found a good page to evict, everything is pinned. 47 | while ((frames[this.arm].tag == ACTIVE || frames[this.arm].isPinned()) && 48 | iters < 2 * frames.length) { 49 | frames[this.arm].tag = INACTIVE; 50 | this.arm = (this.arm + 1) % frames.length; 51 | ++iters; 52 | } 53 | if (iters == 2 * frames.length) { 54 | throw new IllegalStateException("cannot evict - everything pinned"); 55 | } 56 | BufferFrame evicted = frames[this.arm]; 57 | this.arm = (this.arm + 1) % frames.length; 58 | return evicted; 59 | } 60 | 61 | /** 62 | * Called when a frame is removed, either because it 63 | * was returned from a call to evict, or because of other constraints 64 | * (e.g. if the page is deleted on disk). 65 | * @param frame frame being removed 66 | */ 67 | @Override 68 | public void cleanup(BufferFrame frame) {} 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/memory/EvictionPolicy.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.memory; 2 | 3 | /** 4 | * Interface for eviction policies for the buffer manager. 5 | */ 6 | public interface EvictionPolicy { 7 | /** 8 | * Called to initiaize a new buffer frame. 9 | * @param frame new frame to be initialized 10 | */ 11 | void init(BufferFrame frame); 12 | 13 | /** 14 | * Called when a frame is hit. 15 | * @param frame Frame object that is being read from/written to 16 | */ 17 | void hit(BufferFrame frame); 18 | 19 | /** 20 | * Called when a frame needs to be evicted. 21 | * @param frames Array of all frames (same length every call) 22 | * @return index of frame to be evicted 23 | * @throws IllegalStateException if everything is pinned 24 | */ 25 | BufferFrame evict(BufferFrame[] frames); 26 | 27 | /** 28 | * Called when a frame is removed, either because it 29 | * was returned from a call to evict, or because of other constraints 30 | * (e.g. if the page is deleted on disk). 31 | * @param frame frame being removed 32 | */ 33 | void cleanup(BufferFrame frame); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/memory/HashPartition.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.memory; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | 6 | import edu.berkeley.cs186.database.TransactionContext; 7 | import edu.berkeley.cs186.database.databox.DataBox; 8 | import edu.berkeley.cs186.database.table.Record; 9 | import edu.berkeley.cs186.database.table.Schema; 10 | 11 | public class HashPartition { 12 | private String tempLeftTableName; 13 | private String tempRightTableName; 14 | private TransactionContext transaction; 15 | 16 | /** 17 | * A class representing a partition used in hashing operations for grace hash 18 | * join. This partition supports adding and iterating over records for two 19 | * different relations. 20 | * 21 | * @param transaction the transaction context this buffer will be used in 22 | * @param leftSchema the schema for the type of records in the left table to be added to this partition 23 | * @param rightSchema the schema for the type of records in the right table to be added to this partition 24 | */ 25 | public HashPartition(TransactionContext transaction, Schema leftSchema, Schema rightSchema) { 26 | this.tempLeftTableName = transaction.createTempTable(leftSchema); 27 | this.tempRightTableName = transaction.createTempTable(rightSchema); 28 | this.transaction = transaction; 29 | } 30 | 31 | /** 32 | * Adds a record from the left relation to this partition 33 | * @param leftRecord the record to be added 34 | */ 35 | public void addLeftRecord(Record leftRecord) { 36 | this.addLeftRecord(leftRecord.getValues()); 37 | } 38 | 39 | /** 40 | * Adds a record from the right relation to this partition 41 | * @param rightRecord the record to be added 42 | */ 43 | public void addRightRecord(Record rightRecord) { 44 | this.addRightRecord(rightRecord.getValues()); 45 | } 46 | 47 | /** 48 | * Returns the number of pages used to store records from the left relation 49 | * in this partition 50 | * @return the number of pages used to store records from the left relation 51 | */ 52 | public int getNumLeftPages() { 53 | return this.transaction.getTable(this.tempLeftTableName).getNumDataPages(); 54 | } 55 | 56 | /** 57 | * Returns the number of pages used to store records from the right relation 58 | * in this partition 59 | * @return the number of pages used to store records from the right relation 60 | */ 61 | public int getNumRightPages() { 62 | return this.transaction.getTable(this.tempRightTableName).getNumDataPages(); 63 | } 64 | 65 | /** 66 | * Adds a record from the left relation consisting of the values in the input 67 | * @param values a list of values representing a record to be added 68 | */ 69 | public void addLeftRecord(List leftValues) { 70 | transaction.addRecord(this.tempLeftTableName, leftValues); 71 | } 72 | 73 | /** 74 | * Adds a record from the right relation consisting of the values in the input 75 | * @param values a list of values representing a record to be added 76 | */ 77 | public void addRightRecord(List rightValues) { 78 | transaction.addRecord(this.tempRightTableName, rightValues); 79 | } 80 | 81 | /** 82 | * Add a list of records from the left relation to the partition 83 | * @param records records to add to the partition 84 | */ 85 | public void addLeftRecords(List records) { 86 | for (Record record : records) { 87 | this.addLeftRecord(record); 88 | } 89 | } 90 | 91 | /** 92 | * Add a list of records from the right relation to the partition 93 | * @param records records to add to the partition 94 | */ 95 | public void addRightRecords(List records) { 96 | for (Record record : records) { 97 | this.addRightRecord(record); 98 | } 99 | } 100 | 101 | /** 102 | * Returns an iterator over all of the records from the left relation 103 | * that were written to this partition 104 | * @return an iterator over all the records in this partition 105 | */ 106 | public Iterator getLeftIterator() { 107 | return this.transaction.getRecordIterator(this.tempLeftTableName); 108 | } 109 | 110 | /** 111 | * Returns an iterator over all of the records from the right relation 112 | * that were written to this partition 113 | * @return an iterator over all the records in this partition 114 | */ 115 | public Iterator getRightIterator() { 116 | return this.transaction.getRecordIterator(this.tempRightTableName); 117 | } 118 | } -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/memory/LRUEvictionPolicy.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.memory; 2 | 3 | /** 4 | * Implementation of LRU eviction policy, which works by creating a 5 | * doubly-linked list between frames in order of ascending use time. 6 | */ 7 | public class LRUEvictionPolicy implements EvictionPolicy { 8 | private Tag listHead; 9 | private Tag listTail; 10 | 11 | // Doubly-linked list between frames, in order of least to most 12 | // recently used. 13 | private class Tag { 14 | Tag prev = null; 15 | Tag next = null; 16 | BufferFrame cur = null; 17 | 18 | @Override 19 | public String toString() { 20 | String sprev = (prev == null || prev.cur == null) ? "null" : prev.cur.toString(); 21 | String snext = (next == null || next.cur == null) ? "null" : next.cur.toString(); 22 | String scur = cur == null ? "null" : cur.toString(); 23 | return scur + " (prev=" + sprev + ", next=" + snext + ")"; 24 | } 25 | } 26 | 27 | public LRUEvictionPolicy() { 28 | this.listHead = new Tag(); 29 | this.listTail = new Tag(); 30 | this.listHead.next = this.listTail; 31 | this.listTail.prev = this.listHead; 32 | } 33 | 34 | /** 35 | * Called to initiaize a new buffer frame. 36 | * @param frame new frame to be initialized 37 | */ 38 | @Override 39 | public void init(BufferFrame frame) { 40 | Tag frameTag = new Tag(); 41 | frameTag.next = listTail; 42 | frameTag.prev = listTail.prev; 43 | listTail.prev = frameTag; 44 | frameTag.prev.next = frameTag; 45 | frameTag.cur = frame; 46 | frame.tag = frameTag; 47 | } 48 | 49 | /** 50 | * Called when a frame is hit. 51 | * @param frame Frame object that is being read from/written to 52 | */ 53 | @Override 54 | public void hit(BufferFrame frame) { 55 | Tag frameTag = (Tag) frame.tag; 56 | frameTag.prev.next = frameTag.next; 57 | frameTag.next.prev = frameTag.prev; 58 | frameTag.next = this.listTail; 59 | frameTag.prev = this.listTail.prev; 60 | this.listTail.prev.next = frameTag; 61 | this.listTail.prev = frameTag; 62 | } 63 | 64 | /** 65 | * Called when a frame needs to be evicted. 66 | * @param frames Array of all frames (same length every call) 67 | * @return index of frame to be evicted 68 | * @throws IllegalStateException if everything is pinned 69 | */ 70 | @Override 71 | public BufferFrame evict(BufferFrame[] frames) { 72 | Tag frameTag = this.listHead.next; 73 | while (frameTag.cur != null && frameTag.cur.isPinned()) { 74 | frameTag = frameTag.next; 75 | } 76 | if (frameTag.cur == null) { 77 | throw new IllegalStateException("cannot evict anything - everything pinned"); 78 | } 79 | return frameTag.cur; 80 | } 81 | 82 | /** 83 | * Called when a frame is removed, either because it 84 | * was returned from a call to evict, or because of other constraints 85 | * (e.g. if the page is deleted on disk). 86 | * @param frame frame being removed 87 | */ 88 | @Override 89 | public void cleanup(BufferFrame frame) { 90 | Tag frameTag = (Tag) frame.tag; 91 | frameTag.prev.next = frameTag.next; 92 | frameTag.next.prev = frameTag.prev; 93 | frameTag.prev = frameTag.next = frameTag; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/memory/NaiveHashPartition.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.memory; 2 | 3 | import java.util.Iterator; 4 | import java.util.List; 5 | 6 | import edu.berkeley.cs186.database.TransactionContext; 7 | import edu.berkeley.cs186.database.databox.DataBox; 8 | import edu.berkeley.cs186.database.table.Record; 9 | import edu.berkeley.cs186.database.table.Schema; 10 | 11 | public class NaiveHashPartition { 12 | private String tempTableName; 13 | private TransactionContext transaction; 14 | 15 | /** 16 | * A class representing a partition used in naive hashing operations. This 17 | * partition can only hold records from a single relation, as opposed to 18 | * HashPartition which can hold records from two different relations. 19 | * 20 | * @param transaction the transaction context this buffer will be used in 21 | * @param schema the schema for the type of records to be added to this partition 22 | */ 23 | public NaiveHashPartition(TransactionContext transaction, Schema schema) { 24 | this.tempTableName = transaction.createTempTable(schema); // Partitions are backed as a tempTable 25 | this.transaction = transaction; 26 | } 27 | 28 | /** 29 | * Returns the number of pages used to store the records in this partition 30 | * @return the number of pages used to restore the records in this partition 31 | */ 32 | public int getNumPages() { 33 | return this.transaction.getTable(this.tempTableName).getNumDataPages(); 34 | } 35 | 36 | /** 37 | * Adds a record to this partition 38 | * @param record the record to be added 39 | */ 40 | public void addRecord(Record record) { 41 | this.addRecord(record.getValues()); 42 | } 43 | 44 | /** 45 | * Adds a record consisting of the values in the input 46 | * @param values a list of values representing a record to be added 47 | */ 48 | public void addRecord(List values) { 49 | transaction.addRecord(this.tempTableName, values); 50 | } 51 | 52 | /** 53 | * Add a list of records to the partition 54 | * @param records records to add to the partition 55 | */ 56 | public void addRecords(List records) { 57 | for (Record record : records) { 58 | this.addRecord(record); 59 | } 60 | } 61 | /** 62 | * Returns an iterator over all of the records that were written to this partition 63 | * @return an iterator over all the records in this partition 64 | */ 65 | public Iterator getIterator() { 66 | return this.transaction.getRecordIterator(this.tempTableName); 67 | } 68 | } -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/query/MaterializeOperator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.query; 2 | 3 | import java.util.Iterator; 4 | 5 | import edu.berkeley.cs186.database.TransactionContext; 6 | import edu.berkeley.cs186.database.table.Record; 7 | 8 | class MaterializeOperator extends SequentialScanOperator { 9 | /** 10 | * Operator that materializes the source operator into a temporary table immediately, 11 | * and then acts as a sequential scan operator over the temporary table. 12 | * @param source source operator to be materialized 13 | * @param transaction current running transaction 14 | */ 15 | MaterializeOperator(QueryOperator source, 16 | TransactionContext transaction) { 17 | super(OperatorType.MATERIALIZE, transaction, materialize(source, transaction)); 18 | } 19 | 20 | private static String materialize(QueryOperator source, TransactionContext transaction) { 21 | String materializedTableName = transaction.createTempTable(source.getOutputSchema()); 22 | for (Record record : source) { 23 | transaction.addRecord(materializedTableName, record.getValues()); 24 | } 25 | return materializedTableName; 26 | } 27 | 28 | @Override 29 | public String str() { 30 | return "type: " + this.getType(); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/query/PNLJOperator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.query; 2 | 3 | import edu.berkeley.cs186.database.TransactionContext; 4 | 5 | class PNLJOperator extends BNLJOperator { 6 | PNLJOperator(QueryOperator leftSource, 7 | QueryOperator rightSource, 8 | String leftColumnName, 9 | String rightColumnName, 10 | TransactionContext transaction) { 11 | super(leftSource, 12 | rightSource, 13 | leftColumnName, 14 | rightColumnName, 15 | transaction); 16 | 17 | joinType = JoinType.PNLJ; 18 | numBuffers = 3; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/query/QueryPlanException.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.query; 2 | 3 | public class QueryPlanException extends RuntimeException { 4 | private String message; 5 | 6 | public QueryPlanException(String message) { 7 | this.message = message; 8 | } 9 | 10 | public QueryPlanException(Exception e) { 11 | this.message = e.getClass().toString() + ": " + e.getMessage(); 12 | } 13 | 14 | @Override 15 | public String getMessage() { 16 | return this.message; 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/query/SequentialScanOperator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.query; 2 | 3 | import java.util.Iterator; 4 | 5 | import edu.berkeley.cs186.database.TransactionContext; 6 | import edu.berkeley.cs186.database.DatabaseException; 7 | import edu.berkeley.cs186.database.table.Record; 8 | import edu.berkeley.cs186.database.table.Schema; 9 | import edu.berkeley.cs186.database.table.stats.TableStats; 10 | 11 | class SequentialScanOperator extends QueryOperator { 12 | private TransactionContext transaction; 13 | private String tableName; 14 | 15 | /** 16 | * Creates a new SequentialScanOperator that provides an iterator on all tuples in a table. 17 | * 18 | * NOTE: Sequential scans don't take a source operator because they must always be at the bottom 19 | * of the DAG. 20 | * 21 | * @param transaction 22 | * @param tableName 23 | */ 24 | SequentialScanOperator(TransactionContext transaction, 25 | String tableName) { 26 | this(OperatorType.SEQSCAN, transaction, tableName); 27 | } 28 | 29 | protected SequentialScanOperator(OperatorType type, 30 | TransactionContext transaction, 31 | String tableName) { 32 | super(type); 33 | this.transaction = transaction; 34 | this.tableName = tableName; 35 | this.setOutputSchema(this.computeSchema()); 36 | 37 | this.stats = this.estimateStats(); 38 | this.cost = this.estimateIOCost(); 39 | } 40 | 41 | public String getTableName() { 42 | return this.tableName; 43 | } 44 | 45 | @Override 46 | public boolean isSequentialScan() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public Iterator iterator() { 52 | return this.transaction.getRecordIterator(tableName); 53 | } 54 | 55 | @Override 56 | public Schema computeSchema() { 57 | try { 58 | return this.transaction.getFullyQualifiedSchema(this.tableName); 59 | } catch (DatabaseException de) { 60 | throw new QueryPlanException(de); 61 | } 62 | } 63 | 64 | @Override 65 | public String str() { 66 | return "type: " + this.getType() + 67 | "\ntable: " + this.tableName; 68 | } 69 | 70 | /** 71 | * Estimates the table statistics for the result of executing this query operator. 72 | * 73 | * @return estimated TableStats 74 | */ 75 | @Override 76 | public TableStats estimateStats() { 77 | try { 78 | return this.transaction.getStats(this.tableName); 79 | } catch (DatabaseException de) { 80 | throw new QueryPlanException(de); 81 | } 82 | } 83 | 84 | @Override 85 | public int estimateIOCost() { 86 | try { 87 | return this.transaction.getNumDataPages(this.tableName); 88 | } catch (DatabaseException de) { 89 | throw new QueryPlanException(de); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/AbortTransactionLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | 9 | class AbortTransactionLogRecord extends LogRecord { 10 | private long transNum; 11 | private long prevLSN; 12 | 13 | AbortTransactionLogRecord(long transNum, long prevLSN) { 14 | super(LogType.ABORT_TRANSACTION); 15 | this.transNum = transNum; 16 | this.prevLSN = prevLSN; 17 | } 18 | 19 | @Override 20 | public Optional getTransNum() { 21 | return Optional.of(transNum); 22 | } 23 | 24 | @Override 25 | public Optional getPrevLSN() { 26 | return Optional.of(prevLSN); 27 | } 28 | 29 | @Override 30 | public byte[] toBytes() { 31 | byte[] b = new byte[1 + Long.BYTES + Long.BYTES]; 32 | ByteBuffer.wrap(b) 33 | .put((byte) getType().getValue()) 34 | .putLong(transNum) 35 | .putLong(prevLSN); 36 | return b; 37 | } 38 | 39 | public static Optional fromBytes(Buffer buf) { 40 | long transNum = buf.getLong(); 41 | long prevLSN = buf.getLong(); 42 | return Optional.of(new AbortTransactionLogRecord(transNum, prevLSN)); 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) { return true; } 48 | if (o == null || getClass() != o.getClass()) { return false; } 49 | if (!super.equals(o)) { return false; } 50 | AbortTransactionLogRecord that = (AbortTransactionLogRecord) o; 51 | return transNum == that.transNum && 52 | prevLSN == that.prevLSN; 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return Objects.hash(super.hashCode(), transNum, prevLSN); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "AbortTransactionLogRecord{" + 63 | "transNum=" + transNum + 64 | ", prevLSN=" + prevLSN + 65 | ", LSN=" + LSN + 66 | '}'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/AllocPageLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | import edu.berkeley.cs186.database.common.Pair; 6 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 7 | import edu.berkeley.cs186.database.memory.BufferManager; 8 | 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | 12 | /** 13 | * A log entry that records the allocation of a page 14 | */ 15 | class AllocPageLogRecord extends LogRecord { 16 | private long transNum; 17 | private long pageNum; 18 | private long prevLSN; 19 | 20 | AllocPageLogRecord(long transNum, long pageNum, long prevLSN) { 21 | super(LogType.ALLOC_PAGE); 22 | this.transNum = transNum; 23 | this.pageNum = pageNum; 24 | this.prevLSN = prevLSN; 25 | } 26 | 27 | @Override 28 | public Optional getTransNum() { 29 | return Optional.of(transNum); 30 | } 31 | 32 | @Override 33 | public Optional getPrevLSN() { 34 | return Optional.of(prevLSN); 35 | } 36 | 37 | @Override 38 | public Optional getPageNum() { 39 | return Optional.of(pageNum); 40 | } 41 | 42 | @Override 43 | public boolean isUndoable() { 44 | return true; 45 | } 46 | 47 | @Override 48 | public boolean isRedoable() { 49 | return true; 50 | } 51 | 52 | @Override 53 | public Pair undo(long lastLSN) { 54 | return new Pair<>(new UndoAllocPageLogRecord(transNum, pageNum, lastLSN, prevLSN), true); 55 | } 56 | 57 | @Override 58 | public void redo(DiskSpaceManager dsm, BufferManager bm) { 59 | super.redo(dsm, bm); 60 | 61 | try { 62 | dsm.allocPage(pageNum); 63 | } catch (IllegalStateException e) { 64 | /* do nothing - page already exists */ 65 | } 66 | } 67 | 68 | @Override 69 | public byte[] toBytes() { 70 | byte[] b = new byte[1 + Long.BYTES + Long.BYTES + Long.BYTES]; 71 | ByteBuffer.wrap(b) 72 | .put((byte) getType().getValue()) 73 | .putLong(transNum) 74 | .putLong(pageNum) 75 | .putLong(prevLSN); 76 | return b; 77 | } 78 | 79 | public static Optional fromBytes(Buffer buf) { 80 | long transNum = buf.getLong(); 81 | long pageNum = buf.getLong(); 82 | long prevLSN = buf.getLong(); 83 | return Optional.of(new AllocPageLogRecord(transNum, pageNum, prevLSN)); 84 | } 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (this == o) { return true; } 89 | if (o == null || getClass() != o.getClass()) { return false; } 90 | if (!super.equals(o)) { return false; } 91 | AllocPageLogRecord that = (AllocPageLogRecord) o; 92 | return transNum == that.transNum && 93 | pageNum == that.pageNum && 94 | prevLSN == that.prevLSN; 95 | } 96 | 97 | @Override 98 | public int hashCode() { 99 | return Objects.hash(super.hashCode(), transNum, pageNum, prevLSN); 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return "AllocPageLogRecord{" + 105 | "transNum=" + transNum + 106 | ", pageNum=" + pageNum + 107 | ", prevLSN=" + prevLSN + 108 | ", LSN=" + LSN + 109 | '}'; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/AllocPartLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | import edu.berkeley.cs186.database.common.Pair; 6 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 7 | import edu.berkeley.cs186.database.memory.BufferManager; 8 | 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | 12 | /** 13 | * A log entry that records the allocation of a partition 14 | */ 15 | class AllocPartLogRecord extends LogRecord { 16 | private long transNum; 17 | private int partNum; 18 | private long prevLSN; 19 | 20 | AllocPartLogRecord(long transNum, int partNum, long prevLSN) { 21 | super(LogType.ALLOC_PART); 22 | this.transNum = transNum; 23 | this.partNum = partNum; 24 | this.prevLSN = prevLSN; 25 | } 26 | 27 | @Override 28 | public Optional getTransNum() { 29 | return Optional.of(transNum); 30 | } 31 | 32 | @Override 33 | public Optional getPrevLSN() { 34 | return Optional.of(prevLSN); 35 | } 36 | 37 | @Override 38 | public Optional getPartNum() { 39 | return Optional.of(partNum); 40 | } 41 | 42 | @Override 43 | public boolean isUndoable() { 44 | return true; 45 | } 46 | 47 | @Override 48 | public boolean isRedoable() { 49 | return true; 50 | } 51 | 52 | @Override 53 | public Pair undo(long lastLSN) { 54 | return new Pair<>(new UndoAllocPartLogRecord(transNum, partNum, lastLSN, prevLSN), true); 55 | } 56 | 57 | @Override 58 | public void redo(DiskSpaceManager dsm, BufferManager bm) { 59 | super.redo(dsm, bm); 60 | 61 | try { 62 | dsm.allocPart(partNum); 63 | } catch (IllegalStateException e) { 64 | /* do nothing - partition already exists */ 65 | } 66 | } 67 | 68 | @Override 69 | public byte[] toBytes() { 70 | byte[] b = new byte[1 + Long.BYTES + Integer.BYTES + Long.BYTES]; 71 | ByteBuffer.wrap(b) 72 | .put((byte) getType().getValue()) 73 | .putLong(transNum) 74 | .putInt(partNum) 75 | .putLong(prevLSN); 76 | return b; 77 | } 78 | 79 | public static Optional fromBytes(Buffer buf) { 80 | long transNum = buf.getLong(); 81 | int partNum = buf.getInt(); 82 | long prevLSN = buf.getLong(); 83 | return Optional.of(new AllocPartLogRecord(transNum, partNum, prevLSN)); 84 | } 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (this == o) { return true; } 89 | if (o == null || getClass() != o.getClass()) { return false; } 90 | if (!super.equals(o)) { return false; } 91 | AllocPartLogRecord that = (AllocPartLogRecord) o; 92 | return transNum == that.transNum && 93 | partNum == that.partNum && 94 | prevLSN == that.prevLSN; 95 | } 96 | 97 | @Override 98 | public int hashCode() { 99 | return Objects.hash(super.hashCode(), transNum, partNum, prevLSN); 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return "AllocPartLogRecord{" + 105 | "transNum=" + transNum + 106 | ", partNum=" + partNum + 107 | ", prevLSN=" + prevLSN + 108 | ", LSN=" + LSN + 109 | '}'; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/BeginCheckpointLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | 9 | class BeginCheckpointLogRecord extends LogRecord { 10 | long maxTransNum; 11 | 12 | BeginCheckpointLogRecord(long maxTransNum) { 13 | super(LogType.BEGIN_CHECKPOINT); 14 | this.maxTransNum = maxTransNum; 15 | } 16 | 17 | @Override 18 | public Optional getMaxTransactionNum() { 19 | return Optional.of(maxTransNum); 20 | } 21 | 22 | @Override 23 | public byte[] toBytes() { 24 | byte[] b = new byte[9]; 25 | ByteBuffer.wrap(b).put((byte) getType().getValue()).putLong(maxTransNum); 26 | return b; 27 | } 28 | 29 | public static Optional fromBytes(Buffer buf) { 30 | return Optional.of(new BeginCheckpointLogRecord(buf.getLong())); 31 | } 32 | 33 | @Override 34 | public boolean equals(Object o) { 35 | if (this == o) { return true; } 36 | if (o == null || getClass() != o.getClass()) { return false; } 37 | if (!super.equals(o)) { return false; } 38 | BeginCheckpointLogRecord that = (BeginCheckpointLogRecord) o; 39 | return maxTransNum == that.maxTransNum; 40 | } 41 | 42 | @Override 43 | public int hashCode() { 44 | return Objects.hash(super.hashCode(), maxTransNum); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "BeginCheckpointLogRecord{" + 50 | "maxTransNum=" + maxTransNum + 51 | ", LSN=" + LSN + 52 | '}'; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/CommitTransactionLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | 9 | class CommitTransactionLogRecord extends LogRecord { 10 | private long transNum; 11 | private long prevLSN; 12 | 13 | CommitTransactionLogRecord(long transNum, long prevLSN) { 14 | super(LogType.COMMIT_TRANSACTION); 15 | this.transNum = transNum; 16 | this.prevLSN = prevLSN; 17 | } 18 | 19 | @Override 20 | public Optional getTransNum() { 21 | return Optional.of(transNum); 22 | } 23 | 24 | @Override 25 | public Optional getPrevLSN() { 26 | return Optional.of(prevLSN); 27 | } 28 | 29 | @Override 30 | public byte[] toBytes() { 31 | byte[] b = new byte[1 + Long.BYTES + Long.BYTES]; 32 | ByteBuffer.wrap(b) 33 | .put((byte) getType().getValue()) 34 | .putLong(transNum) 35 | .putLong(prevLSN); 36 | return b; 37 | } 38 | 39 | public static Optional fromBytes(Buffer buf) { 40 | long transNum = buf.getLong(); 41 | long prevLSN = buf.getLong(); 42 | return Optional.of(new CommitTransactionLogRecord(transNum, prevLSN)); 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) { return true; } 48 | if (o == null || getClass() != o.getClass()) { return false; } 49 | if (!super.equals(o)) { return false; } 50 | CommitTransactionLogRecord that = (CommitTransactionLogRecord) o; 51 | return transNum == that.transNum && 52 | prevLSN == that.prevLSN; 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return Objects.hash(super.hashCode(), transNum, prevLSN); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "CommitTransactionLogRecord{" + 63 | "transNum=" + transNum + 64 | ", prevLSN=" + prevLSN + 65 | ", LSN=" + LSN + 66 | '}'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/DummyRecoveryManager.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.Transaction; 4 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 5 | import edu.berkeley.cs186.database.memory.BufferManager; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class DummyRecoveryManager implements RecoveryManager { 11 | private Map runningTransactions = new HashMap<>(); 12 | 13 | @Override 14 | public void initialize() {} 15 | 16 | @Override 17 | public void setManagers(DiskSpaceManager diskSpaceManager, BufferManager bufferManager) {} 18 | 19 | @Override 20 | public void startTransaction(Transaction transaction) { 21 | runningTransactions.put(transaction.getTransNum(), transaction); 22 | } 23 | 24 | @Override 25 | public long commit(long transNum) { 26 | runningTransactions.get(transNum).setStatus(Transaction.Status.COMMITTING); 27 | return 0L; 28 | } 29 | 30 | @Override 31 | public long abort(long transNum) { 32 | throw new UnsupportedOperationException("proj5 must be implemented to use abort"); 33 | } 34 | 35 | @Override 36 | public long end(long transNum) { 37 | runningTransactions.get(transNum).setStatus(Transaction.Status.COMPLETE); 38 | runningTransactions.remove(transNum); 39 | return 0L; 40 | } 41 | 42 | @Override 43 | public void pageFlushHook(long pageLSN) {} 44 | 45 | @Override 46 | public void diskIOHook(long pageNum) {} 47 | 48 | @Override 49 | public long logPageWrite(long transNum, long pageNum, short pageOffset, byte[] before, 50 | byte[] after) { 51 | return 0L; 52 | } 53 | 54 | @Override 55 | public long logAllocPart(long transNum, int partNum) { 56 | return 0L; 57 | } 58 | 59 | @Override 60 | public long logFreePart(long transNum, int partNum) { 61 | return 0L; 62 | } 63 | 64 | @Override 65 | public long logAllocPage(long transNum, long pageNum) { 66 | return 0L; 67 | } 68 | 69 | @Override 70 | public long logFreePage(long transNum, long pageNum) { 71 | return 0L; 72 | } 73 | 74 | @Override 75 | public void savepoint(long transNum, String name) { 76 | throw new UnsupportedOperationException("proj5 must be implemented to use savepoints"); 77 | } 78 | 79 | @Override 80 | public void releaseSavepoint(long transNum, String name) { 81 | throw new UnsupportedOperationException("proj5 must be implemented to use savepoints"); 82 | } 83 | 84 | @Override 85 | public void rollbackToSavepoint(long transNum, String name) { 86 | throw new UnsupportedOperationException("proj5 must be implemented to use savepoints"); 87 | } 88 | 89 | @Override 90 | public void checkpoint() { 91 | throw new UnsupportedOperationException("proj5 must be implemented to use checkpoints"); 92 | } 93 | 94 | @Override 95 | public Runnable restart() { 96 | return () -> {}; 97 | } 98 | 99 | @Override 100 | public void close() {} 101 | } 102 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/EndTransactionLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | 9 | class EndTransactionLogRecord extends LogRecord { 10 | private long transNum; 11 | private long prevLSN; 12 | 13 | EndTransactionLogRecord(long transNum, long prevLSN) { 14 | super(LogType.END_TRANSACTION); 15 | this.transNum = transNum; 16 | this.prevLSN = prevLSN; 17 | } 18 | 19 | @Override 20 | public Optional getTransNum() { 21 | return Optional.of(transNum); 22 | } 23 | 24 | @Override 25 | public Optional getPrevLSN() { 26 | return Optional.of(prevLSN); 27 | } 28 | 29 | @Override 30 | public byte[] toBytes() { 31 | byte[] b = new byte[1 + Long.BYTES + Long.BYTES]; 32 | ByteBuffer.wrap(b) 33 | .put((byte) getType().getValue()) 34 | .putLong(transNum) 35 | .putLong(prevLSN); 36 | return b; 37 | } 38 | 39 | public static Optional fromBytes(Buffer buf) { 40 | long transNum = buf.getLong(); 41 | long prevLSN = buf.getLong(); 42 | return Optional.of(new EndTransactionLogRecord(transNum, prevLSN)); 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) { return true; } 48 | if (o == null || getClass() != o.getClass()) { return false; } 49 | if (!super.equals(o)) { return false; } 50 | EndTransactionLogRecord that = (EndTransactionLogRecord) o; 51 | return transNum == that.transNum && 52 | prevLSN == that.prevLSN; 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return Objects.hash(super.hashCode(), transNum, prevLSN); 58 | } 59 | 60 | @Override 61 | public String toString() { 62 | return "EndTransactionLogRecord{" + 63 | "transNum=" + transNum + 64 | ", prevLSN=" + prevLSN + 65 | ", LSN=" + LSN + 66 | '}'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/FreePageLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | import edu.berkeley.cs186.database.common.Pair; 6 | import edu.berkeley.cs186.database.concurrency.DummyLockContext; 7 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 8 | import edu.berkeley.cs186.database.memory.BufferManager; 9 | import edu.berkeley.cs186.database.memory.Page; 10 | 11 | import java.util.NoSuchElementException; 12 | import java.util.Objects; 13 | import java.util.Optional; 14 | 15 | class FreePageLogRecord extends LogRecord { 16 | private long transNum; 17 | private long pageNum; 18 | private long prevLSN; 19 | 20 | FreePageLogRecord(long transNum, long pageNum, long prevLSN) { 21 | super(LogType.FREE_PAGE); 22 | this.transNum = transNum; 23 | this.pageNum = pageNum; 24 | this.prevLSN = prevLSN; 25 | } 26 | 27 | @Override 28 | public Optional getTransNum() { 29 | return Optional.of(transNum); 30 | } 31 | 32 | @Override 33 | public Optional getPrevLSN() { 34 | return Optional.of(prevLSN); 35 | } 36 | 37 | @Override 38 | public Optional getPageNum() { 39 | return Optional.of(pageNum); 40 | } 41 | 42 | @Override 43 | public boolean isUndoable() { 44 | return true; 45 | } 46 | 47 | @Override 48 | public boolean isRedoable() { 49 | return true; 50 | } 51 | 52 | @Override 53 | public Pair undo(long lastLSN) { 54 | return new Pair<>(new UndoFreePageLogRecord(transNum, pageNum, lastLSN, prevLSN), true); 55 | } 56 | 57 | @Override 58 | public void redo(DiskSpaceManager dsm, BufferManager bm) { 59 | super.redo(dsm, bm); 60 | 61 | try { 62 | Page p = bm.fetchPage(new DummyLockContext(), pageNum, false); 63 | bm.freePage(p); 64 | p.unpin(); 65 | } catch (NoSuchElementException e) { 66 | /* do nothing - page already freed */ 67 | } 68 | } 69 | 70 | @Override 71 | public byte[] toBytes() { 72 | byte[] b = new byte[1 + Long.BYTES + Long.BYTES + Long.BYTES]; 73 | ByteBuffer.wrap(b) 74 | .put((byte) getType().getValue()) 75 | .putLong(transNum) 76 | .putLong(pageNum) 77 | .putLong(prevLSN); 78 | return b; 79 | } 80 | 81 | public static Optional fromBytes(Buffer buf) { 82 | long transNum = buf.getLong(); 83 | long pageNum = buf.getLong(); 84 | long prevLSN = buf.getLong(); 85 | return Optional.of(new FreePageLogRecord(transNum, pageNum, prevLSN)); 86 | } 87 | 88 | @Override 89 | public boolean equals(Object o) { 90 | if (this == o) { return true; } 91 | if (o == null || getClass() != o.getClass()) { return false; } 92 | if (!super.equals(o)) { return false; } 93 | FreePageLogRecord that = (FreePageLogRecord) o; 94 | return transNum == that.transNum && 95 | pageNum == that.pageNum && 96 | prevLSN == that.prevLSN; 97 | } 98 | 99 | @Override 100 | public int hashCode() { 101 | return Objects.hash(super.hashCode(), transNum, pageNum, prevLSN); 102 | } 103 | 104 | @Override 105 | public String toString() { 106 | return "FreePageLogRecord{" + 107 | "transNum=" + transNum + 108 | ", pageNum=" + pageNum + 109 | ", prevLSN=" + prevLSN + 110 | ", LSN=" + LSN + 111 | '}'; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/FreePartLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | import edu.berkeley.cs186.database.common.Pair; 6 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 7 | import edu.berkeley.cs186.database.memory.BufferManager; 8 | 9 | import java.util.NoSuchElementException; 10 | import java.util.Objects; 11 | import java.util.Optional; 12 | 13 | class FreePartLogRecord extends LogRecord { 14 | private long transNum; 15 | private int partNum; 16 | private long prevLSN; 17 | 18 | FreePartLogRecord(long transNum, int partNum, long prevLSN) { 19 | super(LogType.FREE_PART); 20 | this.transNum = transNum; 21 | this.partNum = partNum; 22 | this.prevLSN = prevLSN; 23 | } 24 | 25 | @Override 26 | public Optional getTransNum() { 27 | return Optional.of(transNum); 28 | } 29 | 30 | @Override 31 | public Optional getPrevLSN() { 32 | return Optional.of(prevLSN); 33 | } 34 | 35 | @Override 36 | public Optional getPartNum() { 37 | return Optional.of(partNum); 38 | } 39 | 40 | @Override 41 | public boolean isUndoable() { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean isRedoable() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public Pair undo(long lastLSN) { 52 | return new Pair<>(new UndoFreePartLogRecord(transNum, partNum, lastLSN, prevLSN), true); 53 | } 54 | 55 | @Override 56 | public void redo(DiskSpaceManager dsm, BufferManager bm) { 57 | super.redo(dsm, bm); 58 | 59 | try { 60 | dsm.freePart(partNum); 61 | } catch (NoSuchElementException e) { 62 | /* do nothing - partition already freed */ 63 | } 64 | } 65 | 66 | @Override 67 | public byte[] toBytes() { 68 | byte[] b = new byte[1 + Long.BYTES + Integer.BYTES + Long.BYTES]; 69 | ByteBuffer.wrap(b) 70 | .put((byte) getType().getValue()) 71 | .putLong(transNum) 72 | .putInt(partNum) 73 | .putLong(prevLSN); 74 | return b; 75 | } 76 | 77 | public static Optional fromBytes(Buffer buf) { 78 | long transNum = buf.getLong(); 79 | int partNum = buf.getInt(); 80 | long prevLSN = buf.getLong(); 81 | return Optional.of(new FreePartLogRecord(transNum, partNum, prevLSN)); 82 | } 83 | 84 | @Override 85 | public boolean equals(Object o) { 86 | if (this == o) { return true; } 87 | if (o == null || getClass() != o.getClass()) { return false; } 88 | if (!super.equals(o)) { return false; } 89 | FreePartLogRecord that = (FreePartLogRecord) o; 90 | return transNum == that.transNum && 91 | partNum == that.partNum && 92 | prevLSN == that.prevLSN; 93 | } 94 | 95 | @Override 96 | public int hashCode() { 97 | return Objects.hash(super.hashCode(), transNum, partNum, prevLSN); 98 | } 99 | 100 | @Override 101 | public String toString() { 102 | return "FreePartLogRecord{" + 103 | "transNum=" + transNum + 104 | ", partNum=" + partNum + 105 | ", prevLSN=" + prevLSN + 106 | ", LSN=" + LSN + 107 | '}'; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/LogManager.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import java.util.Iterator; 4 | 5 | public interface LogManager extends Iterable, AutoCloseable { 6 | /** 7 | * Writes to the first record in the log. 8 | * @param record log record to replace first record with 9 | */ 10 | void rewriteMasterRecord(MasterLogRecord record); 11 | 12 | /** 13 | * Appends a log record to the log. 14 | * @param record log record to append to the log 15 | * @return LSN of new log record 16 | */ 17 | long appendToLog(LogRecord record); 18 | 19 | /** 20 | * Fetches a specific log record. 21 | * @param LSN LSN of record to fetch 22 | * @return log record with the specified LSN or null if no record found 23 | */ 24 | LogRecord fetchLogRecord(long LSN); 25 | 26 | /** 27 | * Flushes the log to at least the specified record, 28 | * essentially flushing up to and including the page 29 | * that contains the record specified by the LSN. 30 | * @param LSN LSN up to which the log should be flushed 31 | */ 32 | void flushToLSN(long LSN); 33 | 34 | /** 35 | * @return flushedLSN 36 | */ 37 | long getFlushedLSN(); 38 | 39 | /** 40 | * Scan forward in the log from LSN. 41 | * @param LSN LSN to start scanning from 42 | * @return iterator over log entries from LSN 43 | */ 44 | Iterator scanFrom(long LSN); 45 | 46 | /** 47 | * Prints the entire log. For debugging uses only. 48 | */ 49 | void print(); 50 | 51 | /** 52 | * Scan forward in the log from the first record. 53 | * @return iterator over all log entries 54 | */ 55 | @Override 56 | Iterator iterator(); 57 | 58 | @Override 59 | void close(); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/LogType.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | enum LogType { 4 | // master log record (stores current checkpoint) 5 | MASTER, 6 | // log record for allocating a new page (via the disk space manager) 7 | ALLOC_PAGE, 8 | // log record for updating part of a page 9 | UPDATE_PAGE, 10 | // log record for freeing a page (via the disk space manager) 11 | FREE_PAGE, 12 | // log record for allocating a new partition (via the disk space manager) 13 | ALLOC_PART, 14 | // log record for freeing a partition (via the disk space manager) 15 | FREE_PART, 16 | // log record for starting a transaction commit 17 | COMMIT_TRANSACTION, 18 | // log record for starting a transaction abort 19 | ABORT_TRANSACTION, 20 | // log record for after a transaction has completely finished 21 | END_TRANSACTION, 22 | // log record for start of a checkpoint 23 | BEGIN_CHECKPOINT, 24 | // log record for finishing a checkpoint; there may be multiple of these 25 | // for a checkpoint 26 | END_CHECKPOINT, 27 | // compensation log record for undoing a page alloc 28 | UNDO_ALLOC_PAGE, 29 | // compensation log record for undoing a page update 30 | UNDO_UPDATE_PAGE, 31 | // compensation log record for undoing a page free 32 | UNDO_FREE_PAGE, 33 | // compensation log record for undoing a partition alloc 34 | UNDO_ALLOC_PART, 35 | // compensation log record for undoing a partition free 36 | UNDO_FREE_PART; 37 | 38 | private static LogType[] values = LogType.values(); 39 | 40 | public int getValue() { 41 | return ordinal() + 1; 42 | } 43 | 44 | public static LogType fromInt(int x) { 45 | if (x < 1 || x > values.length) { 46 | String err = String.format("Unknown TypeId ordinal %d.", x); 47 | throw new IllegalArgumentException(err); 48 | } 49 | return values[x - 1]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/MasterLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | 6 | import java.util.Objects; 7 | import java.util.Optional; 8 | 9 | class MasterLogRecord extends LogRecord { 10 | long lastCheckpointLSN; 11 | 12 | MasterLogRecord(long lastCheckpointLSN) { 13 | super(LogType.MASTER); 14 | this.lastCheckpointLSN = lastCheckpointLSN; 15 | } 16 | 17 | @Override 18 | public byte[] toBytes() { 19 | byte[] b = new byte[1 + Long.BYTES]; 20 | ByteBuffer.wrap(b).put((byte) getType().getValue()).putLong(lastCheckpointLSN); 21 | return b; 22 | } 23 | 24 | public static Optional fromBytes(Buffer buf) { 25 | return Optional.of(new MasterLogRecord(buf.getLong())); 26 | } 27 | 28 | @Override 29 | public boolean equals(Object o) { 30 | if (this == o) { return true; } 31 | if (o == null || getClass() != o.getClass()) { return false; } 32 | if (!super.equals(o)) { return false; } 33 | MasterLogRecord that = (MasterLogRecord) o; 34 | return lastCheckpointLSN == that.lastCheckpointLSN; 35 | } 36 | 37 | @Override 38 | public int hashCode() { 39 | return Objects.hash(super.hashCode(), lastCheckpointLSN); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "MasterLogRecord{" + 45 | "lastCheckpointLSN=" + lastCheckpointLSN + 46 | ", LSN=" + LSN + 47 | '}'; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/TransactionTableEntry.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.Transaction; 4 | 5 | import java.util.*; 6 | 7 | class TransactionTableEntry { 8 | // Transaction object for the transaction. 9 | Transaction transaction; 10 | // lastLSN of transaction, or 0 if no log entries for the transaction exist. 11 | long lastLSN = 0; 12 | // Set of page numbers of all pages this transaction has modified in some way. 13 | Set touchedPages = new HashSet<>(); 14 | // map of transaction's savepoints 15 | private Map savepoints = new HashMap<>(); 16 | 17 | TransactionTableEntry(Transaction transaction) { 18 | this.transaction = transaction; 19 | } 20 | 21 | void addSavepoint(String name) { 22 | savepoints.put(name, lastLSN); 23 | } 24 | 25 | long getSavepoint(String name) { 26 | if (!savepoints.containsKey(name)) { 27 | throw new NoSuchElementException("transaction " + transaction.getTransNum() + " has no savepoint " + 28 | name); 29 | } 30 | return savepoints.get(name); 31 | } 32 | 33 | void deleteSavepoint(String name) { 34 | if (!savepoints.containsKey(name)) { 35 | throw new NoSuchElementException("transaction " + transaction.getTransNum() + " has no savepoint " + 36 | name); 37 | } 38 | savepoints.remove(name); 39 | } 40 | 41 | @Override 42 | public boolean equals(Object o) { 43 | if (this == o) { return true; } 44 | if (o == null || getClass() != o.getClass()) { return false; } 45 | TransactionTableEntry that = (TransactionTableEntry) o; 46 | return lastLSN == that.lastLSN && 47 | Objects.equals(transaction, that.transaction) && 48 | Objects.equals(touchedPages, that.touchedPages) && 49 | Objects.equals(savepoints, that.savepoints); 50 | } 51 | 52 | @Override 53 | public int hashCode() { 54 | return Objects.hash(transaction, lastLSN, touchedPages, savepoints); 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return "TransactionTableEntry{" + 60 | "transaction=" + transaction + 61 | ", lastLSN=" + lastLSN + 62 | ", touchedPages=" + touchedPages + 63 | '}'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/UndoAllocPageLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | import edu.berkeley.cs186.database.concurrency.DummyLockContext; 6 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 7 | import edu.berkeley.cs186.database.memory.BufferManager; 8 | import edu.berkeley.cs186.database.memory.Page; 9 | 10 | import java.util.NoSuchElementException; 11 | import java.util.Objects; 12 | import java.util.Optional; 13 | 14 | class UndoAllocPageLogRecord extends LogRecord { 15 | private long transNum; 16 | private long pageNum; 17 | private long prevLSN; 18 | private long undoNextLSN; 19 | 20 | UndoAllocPageLogRecord(long transNum, long pageNum, long prevLSN, long undoNextLSN) { 21 | super(LogType.UNDO_ALLOC_PAGE); 22 | this.transNum = transNum; 23 | this.pageNum = pageNum; 24 | this.prevLSN = prevLSN; 25 | this.undoNextLSN = undoNextLSN; 26 | } 27 | 28 | @Override 29 | public Optional getTransNum() { 30 | return Optional.of(transNum); 31 | } 32 | 33 | @Override 34 | public Optional getPrevLSN() { 35 | return Optional.of(prevLSN); 36 | } 37 | 38 | @Override 39 | public Optional getPageNum() { 40 | return Optional.of(pageNum); 41 | } 42 | 43 | @Override 44 | public Optional getUndoNextLSN() { 45 | return Optional.of(undoNextLSN); 46 | } 47 | 48 | @Override 49 | public boolean isRedoable() { 50 | return true; 51 | } 52 | 53 | @Override 54 | public void redo(DiskSpaceManager dsm, BufferManager bm) { 55 | super.redo(dsm, bm); 56 | 57 | try { 58 | Page p = bm.fetchPage(new DummyLockContext(), pageNum, false); 59 | bm.freePage(p); 60 | p.unpin(); 61 | } catch (NoSuchElementException e) { 62 | /* do nothing - page already freed */ 63 | } 64 | } 65 | 66 | @Override 67 | public byte[] toBytes() { 68 | byte[] b = new byte[1 + Long.BYTES + Long.BYTES + Long.BYTES + Long.BYTES]; 69 | ByteBuffer.wrap(b) 70 | .put((byte) getType().getValue()) 71 | .putLong(transNum) 72 | .putLong(pageNum) 73 | .putLong(prevLSN) 74 | .putLong(undoNextLSN); 75 | return b; 76 | } 77 | 78 | public static Optional fromBytes(Buffer buf) { 79 | long transNum = buf.getLong(); 80 | long pageNum = buf.getLong(); 81 | long prevLSN = buf.getLong(); 82 | long undoNextLSN = buf.getLong(); 83 | return Optional.of(new UndoAllocPageLogRecord(transNum, pageNum, prevLSN, undoNextLSN)); 84 | } 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (this == o) { return true; } 89 | if (o == null || getClass() != o.getClass()) { return false; } 90 | if (!super.equals(o)) { return false; } 91 | UndoAllocPageLogRecord that = (UndoAllocPageLogRecord) o; 92 | return transNum == that.transNum && 93 | pageNum == that.pageNum && 94 | prevLSN == that.prevLSN && 95 | undoNextLSN == that.undoNextLSN; 96 | } 97 | 98 | @Override 99 | public int hashCode() { 100 | return Objects.hash(super.hashCode(), transNum, pageNum, prevLSN, undoNextLSN); 101 | } 102 | 103 | @Override 104 | public String toString() { 105 | return "UndoAllocPageLogRecord{" + 106 | "transNum=" + transNum + 107 | ", pageNum=" + pageNum + 108 | ", prevLSN=" + prevLSN + 109 | ", undoNextLSN=" + undoNextLSN + 110 | ", LSN=" + LSN + 111 | '}'; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/UndoAllocPartLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 6 | import edu.berkeley.cs186.database.memory.BufferManager; 7 | 8 | import java.util.NoSuchElementException; 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | 12 | class UndoAllocPartLogRecord extends LogRecord { 13 | private long transNum; 14 | private int partNum; 15 | private long prevLSN; 16 | private long undoNextLSN; 17 | 18 | UndoAllocPartLogRecord(long transNum, int partNum, long prevLSN, long undoNextLSN) { 19 | super(LogType.UNDO_ALLOC_PART); 20 | this.transNum = transNum; 21 | this.partNum = partNum; 22 | this.prevLSN = prevLSN; 23 | this.undoNextLSN = undoNextLSN; 24 | } 25 | 26 | @Override 27 | public Optional getTransNum() { 28 | return Optional.of(transNum); 29 | } 30 | 31 | @Override 32 | public Optional getPrevLSN() { 33 | return Optional.of(prevLSN); 34 | } 35 | 36 | @Override 37 | public Optional getPartNum() { 38 | return Optional.of(partNum); 39 | } 40 | 41 | @Override 42 | public Optional getUndoNextLSN() { 43 | return Optional.of(undoNextLSN); 44 | } 45 | 46 | @Override 47 | public boolean isRedoable() { 48 | return true; 49 | } 50 | 51 | @Override 52 | public void redo(DiskSpaceManager dsm, BufferManager bm) { 53 | super.redo(dsm, bm); 54 | 55 | try { 56 | dsm.freePart(partNum); 57 | } catch (NoSuchElementException e) { 58 | /* do nothing - partition already freed */ 59 | } 60 | } 61 | 62 | @Override 63 | public byte[] toBytes() { 64 | byte[] b = new byte[1 + Long.BYTES + Integer.BYTES + Long.BYTES + Long.BYTES]; 65 | ByteBuffer.wrap(b) 66 | .put((byte) getType().getValue()) 67 | .putLong(transNum) 68 | .putInt(partNum) 69 | .putLong(prevLSN) 70 | .putLong(undoNextLSN); 71 | return b; 72 | } 73 | 74 | public static Optional fromBytes(Buffer buf) { 75 | long transNum = buf.getLong(); 76 | int partNum = buf.getInt(); 77 | long prevLSN = buf.getLong(); 78 | long undoNextLSN = buf.getLong(); 79 | return Optional.of(new UndoAllocPartLogRecord(transNum, partNum, prevLSN, undoNextLSN)); 80 | } 81 | 82 | @Override 83 | public boolean equals(Object o) { 84 | if (this == o) { return true; } 85 | if (o == null || getClass() != o.getClass()) { return false; } 86 | if (!super.equals(o)) { return false; } 87 | UndoAllocPartLogRecord that = (UndoAllocPartLogRecord) o; 88 | return transNum == that.transNum && 89 | partNum == that.partNum && 90 | prevLSN == that.prevLSN && 91 | undoNextLSN == that.undoNextLSN; 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | return Objects.hash(super.hashCode(), transNum, partNum, prevLSN, undoNextLSN); 97 | } 98 | 99 | @Override 100 | public String toString() { 101 | return "UndoAllocPartLogRecord{" + 102 | "transNum=" + transNum + 103 | ", partNum=" + partNum + 104 | ", prevLSN=" + prevLSN + 105 | ", undoNextLSN=" + undoNextLSN + 106 | ", LSN=" + LSN + 107 | '}'; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/UndoFreePageLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 6 | import edu.berkeley.cs186.database.memory.BufferManager; 7 | 8 | import java.util.Objects; 9 | import java.util.Optional; 10 | 11 | class UndoFreePageLogRecord extends LogRecord { 12 | private long transNum; 13 | private long pageNum; 14 | private long prevLSN; 15 | private long undoNextLSN; 16 | 17 | UndoFreePageLogRecord(long transNum, long pageNum, long prevLSN, long undoNextLSN) { 18 | super(LogType.UNDO_FREE_PAGE); 19 | this.transNum = transNum; 20 | this.pageNum = pageNum; 21 | this.prevLSN = prevLSN; 22 | this.undoNextLSN = undoNextLSN; 23 | } 24 | 25 | @Override 26 | public Optional getTransNum() { 27 | return Optional.of(transNum); 28 | } 29 | 30 | @Override 31 | public Optional getPrevLSN() { 32 | return Optional.of(prevLSN); 33 | } 34 | 35 | @Override 36 | public Optional getPageNum() { 37 | return Optional.of(pageNum); 38 | } 39 | 40 | @Override 41 | public Optional getUndoNextLSN() { 42 | return Optional.of(undoNextLSN); 43 | } 44 | 45 | @Override 46 | public boolean isRedoable() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public void redo(DiskSpaceManager dsm, BufferManager bm) { 52 | super.redo(dsm, bm); 53 | 54 | try { 55 | dsm.allocPage(pageNum); 56 | } catch (IllegalStateException e) { 57 | /* do nothing - page already exists */ 58 | } 59 | } 60 | 61 | @Override 62 | public byte[] toBytes() { 63 | byte[] b = new byte[1 + Long.BYTES + Long.BYTES + Long.BYTES + Long.BYTES]; 64 | ByteBuffer.wrap(b) 65 | .put((byte) getType().getValue()) 66 | .putLong(transNum) 67 | .putLong(pageNum) 68 | .putLong(prevLSN) 69 | .putLong(undoNextLSN); 70 | return b; 71 | } 72 | 73 | public static Optional fromBytes(Buffer buf) { 74 | long transNum = buf.getLong(); 75 | long pageNum = buf.getLong(); 76 | long prevLSN = buf.getLong(); 77 | long undoNextLSN = buf.getLong(); 78 | return Optional.of(new UndoFreePageLogRecord(transNum, pageNum, prevLSN, undoNextLSN)); 79 | } 80 | 81 | @Override 82 | public boolean equals(Object o) { 83 | if (this == o) { return true; } 84 | if (o == null || getClass() != o.getClass()) { return false; } 85 | if (!super.equals(o)) { return false; } 86 | UndoFreePageLogRecord that = (UndoFreePageLogRecord) o; 87 | return transNum == that.transNum && 88 | pageNum == that.pageNum && 89 | prevLSN == that.prevLSN && 90 | undoNextLSN == that.undoNextLSN; 91 | } 92 | 93 | @Override 94 | public int hashCode() { 95 | return Objects.hash(super.hashCode(), transNum, pageNum, prevLSN, undoNextLSN); 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return "UndoFreePageLogRecord{" + 101 | "transNum=" + transNum + 102 | ", pageNum=" + pageNum + 103 | ", prevLSN=" + prevLSN + 104 | ", undoNextLSN=" + undoNextLSN + 105 | ", LSN=" + LSN + 106 | '}'; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/recovery/UndoFreePartLogRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | import edu.berkeley.cs186.database.common.ByteBuffer; 5 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 6 | import edu.berkeley.cs186.database.memory.BufferManager; 7 | 8 | import java.util.Objects; 9 | import java.util.Optional; 10 | 11 | class UndoFreePartLogRecord extends LogRecord { 12 | private long transNum; 13 | private int partNum; 14 | private long prevLSN; 15 | private long undoNextLSN; 16 | 17 | UndoFreePartLogRecord(long transNum, int partNum, long prevLSN, long undoNextLSN) { 18 | super(LogType.UNDO_FREE_PART); 19 | this.transNum = transNum; 20 | this.partNum = partNum; 21 | this.prevLSN = prevLSN; 22 | this.undoNextLSN = undoNextLSN; 23 | } 24 | 25 | @Override 26 | public Optional getTransNum() { 27 | return Optional.of(transNum); 28 | } 29 | 30 | @Override 31 | public Optional getPrevLSN() { 32 | return Optional.of(prevLSN); 33 | } 34 | 35 | @Override 36 | public Optional getPartNum() { 37 | return Optional.of(partNum); 38 | } 39 | 40 | @Override 41 | public Optional getUndoNextLSN() { 42 | return Optional.of(undoNextLSN); 43 | } 44 | 45 | @Override 46 | public boolean isRedoable() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public void redo(DiskSpaceManager dsm, BufferManager bm) { 52 | super.redo(dsm, bm); 53 | 54 | try { 55 | dsm.allocPart(partNum); 56 | } catch (IllegalStateException e) { 57 | /* do nothing - partition already exists */ 58 | } 59 | } 60 | 61 | @Override 62 | public byte[] toBytes() { 63 | byte[] b = new byte[1 + Long.BYTES + Integer.BYTES + Long.BYTES + Long.BYTES]; 64 | ByteBuffer.wrap(b) 65 | .put((byte) getType().getValue()) 66 | .putLong(transNum) 67 | .putInt(partNum) 68 | .putLong(prevLSN) 69 | .putLong(undoNextLSN); 70 | return b; 71 | } 72 | 73 | public static Optional fromBytes(Buffer buf) { 74 | long transNum = buf.getLong(); 75 | int partNum = buf.getInt(); 76 | long prevLSN = buf.getLong(); 77 | long undoNextLSN = buf.getLong(); 78 | return Optional.of(new UndoFreePartLogRecord(transNum, partNum, prevLSN, undoNextLSN)); 79 | } 80 | 81 | @Override 82 | public boolean equals(Object o) { 83 | if (this == o) { return true; } 84 | if (o == null || getClass() != o.getClass()) { return false; } 85 | if (!super.equals(o)) { return false; } 86 | UndoFreePartLogRecord that = (UndoFreePartLogRecord) o; 87 | return transNum == that.transNum && 88 | partNum == that.partNum && 89 | prevLSN == that.prevLSN && 90 | undoNextLSN == that.undoNextLSN; 91 | } 92 | 93 | @Override 94 | public int hashCode() { 95 | return Objects.hash(super.hashCode(), transNum, partNum, prevLSN, undoNextLSN); 96 | } 97 | 98 | @Override 99 | public String toString() { 100 | return "UndoFreePartLogRecord{" + 101 | "transNum=" + transNum + 102 | ", partNum=" + partNum + 103 | ", prevLSN=" + prevLSN + 104 | ", undoNextLSN=" + undoNextLSN + 105 | ", LSN=" + LSN + 106 | '}'; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/table/HeapFile.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table; 2 | 3 | import edu.berkeley.cs186.database.common.iterator.BacktrackingIterable; 4 | import edu.berkeley.cs186.database.common.iterator.BacktrackingIterator; 5 | import edu.berkeley.cs186.database.memory.Page; 6 | 7 | /** 8 | * Interface for a heap file, which receives requests for pages with 9 | * a certain amount of space, and returns a page with enough space. 10 | * Assumes a packed page layout. 11 | */ 12 | public interface HeapFile extends BacktrackingIterable { 13 | /** 14 | * @return effective page size (including metadata). 15 | */ 16 | short getEffectivePageSize(); 17 | 18 | /** 19 | * Sets the size of metadata on an empty page. 20 | * @param emptyPageMetadataSize amount of metadata on empty page 21 | */ 22 | void setEmptyPageMetadataSize(short emptyPageMetadataSize); 23 | 24 | /** 25 | * Fetches a specific pinned page. 26 | * @param pageNum page number 27 | * @return the pinned page 28 | */ 29 | Page getPage(long pageNum); 30 | 31 | /** 32 | * Fetches a data page with a certain amount of unused space. New data and 33 | * header pages may be allocated as necessary. 34 | * @param requiredSpace amount of space needed; 35 | * cannot be larger than effectivePageSize - emptyPageMetadataSize 36 | * @return pinned page with the requested area of contiguous space 37 | */ 38 | Page getPageWithSpace(short requiredSpace); 39 | 40 | /** 41 | * Updates the amount of free space on a page. Updating to effectivePageSize 42 | * frees the page, and it may no longer be used. 43 | * @param page the data page returned by getPageWithSpace 44 | * @param newFreeSpace the size (in bytes) of the free space on the page 45 | */ 46 | void updateFreeSpace(Page page, short newFreeSpace); 47 | 48 | /** 49 | * @return iterator of all allocated data pages 50 | */ 51 | @Override 52 | BacktrackingIterator iterator(); 53 | 54 | /** 55 | * Returns estimate of number of data pages. 56 | * @return estimate of number of data pages 57 | */ 58 | int getNumDataPages(); 59 | 60 | /** 61 | * Gets partition number of partition the heap file lies on. 62 | * @return partition number 63 | */ 64 | int getPartNum(); 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/table/MarkerRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table; 2 | 3 | import java.util.ArrayList; 4 | 5 | /** 6 | * An empty record used to delineate groups in the GroupByOperator (see 7 | * comments in GroupByOperator). 8 | */ 9 | public class MarkerRecord extends Record { 10 | private static final MarkerRecord record = new MarkerRecord(); 11 | 12 | private MarkerRecord() { 13 | super(new ArrayList<>()); 14 | } 15 | 16 | public static MarkerRecord getMarker() { 17 | return MarkerRecord.record; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/table/Record.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import edu.berkeley.cs186.database.common.Buffer; 8 | import edu.berkeley.cs186.database.databox.DataBox; 9 | import edu.berkeley.cs186.database.databox.Type; 10 | 11 | /** A Record is just list of DataBoxes. */ 12 | public class Record { 13 | private List values; 14 | 15 | public Record(List values) { 16 | this.values = values; 17 | } 18 | 19 | public List getValues() { 20 | return this.values; 21 | } 22 | 23 | public byte[] toBytes(Schema schema) { 24 | ByteBuffer byteBuffer = ByteBuffer.allocate(schema.getSizeInBytes()); 25 | for (DataBox value : values) { 26 | byteBuffer.put(value.toBytes()); 27 | } 28 | return byteBuffer.array(); 29 | } 30 | 31 | /** 32 | * Takes a byte[] and decodes it into a Record. This method assumes that the 33 | * input byte[] represents a record that corresponds to this schema. 34 | * 35 | * @param buf the byte array to decode 36 | * @param schema the schema used for this record 37 | * @return the decoded Record 38 | */ 39 | public static Record fromBytes(Buffer buf, Schema schema) { 40 | List values = new ArrayList<>(); 41 | for (Type t : schema.getFieldTypes()) { 42 | values.add(DataBox.fromBytes(buf, t)); 43 | } 44 | return new Record(values); 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return values.toString(); 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (o == this) { 55 | return true; 56 | } 57 | if (!(o instanceof Record)) { 58 | return false; 59 | } 60 | Record r = (Record) o; 61 | return values.equals(r.values); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return values.hashCode(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/table/RecordId.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table; 2 | 3 | import edu.berkeley.cs186.database.common.Buffer; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.util.Objects; 7 | 8 | /** 9 | * A Record in a particular table is uniquely identified by its page number 10 | * (the number of the page on which it resides) and its entry number (the 11 | * record's index in the page). A RecordId is a pair of the page number and 12 | * entry number. 13 | */ 14 | public class RecordId implements Comparable { 15 | private long pageNum; 16 | private short entryNum; 17 | 18 | public RecordId(long pageNum, short entryNum) { 19 | this.pageNum = pageNum; 20 | this.entryNum = entryNum; 21 | } 22 | 23 | public long getPageNum() { 24 | return this.pageNum; 25 | } 26 | 27 | public short getEntryNum() { 28 | return this.entryNum; 29 | } 30 | 31 | public static int getSizeInBytes() { 32 | // See toBytes. 33 | return Long.BYTES + Short.BYTES; 34 | } 35 | 36 | public byte[] toBytes() { 37 | // A RecordId is serialized as its 4-byte page number followed by its 38 | // 2-byte short. 39 | return ByteBuffer.allocate(getSizeInBytes()) 40 | .putLong(pageNum) 41 | .putShort(entryNum) 42 | .array(); 43 | } 44 | 45 | public static RecordId fromBytes(Buffer buf) { 46 | return new RecordId(buf.getLong(), buf.getShort()); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return String.format("RecordId(%d, %d)", pageNum, entryNum); 52 | } 53 | 54 | public String toSexp() { 55 | return String.format("(%d %d)", pageNum, entryNum); 56 | } 57 | 58 | @Override 59 | public boolean equals(Object o) { 60 | if (o == this) { 61 | return true; 62 | } 63 | if (!(o instanceof RecordId)) { 64 | return false; 65 | } 66 | RecordId r = (RecordId) o; 67 | return pageNum == r.pageNum && entryNum == r.entryNum; 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return Objects.hash(pageNum, entryNum); 73 | } 74 | 75 | @Override 76 | public int compareTo(RecordId r) { 77 | int x = Long.compare(pageNum, r.pageNum); 78 | return x == 0 ? Integer.compare(entryNum, r.entryNum) : x; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/table/RecordIterator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table; 2 | 3 | import java.util.Iterator; 4 | 5 | import edu.berkeley.cs186.database.common.iterator.BacktrackingIterator; 6 | import edu.berkeley.cs186.database.DatabaseException; 7 | 8 | /** 9 | * A RecordIterator wraps an Iterator to form an BacktrackingIterator. 10 | * For example, 11 | * 12 | * Iterator ridIterator = t.ridIterator(); 13 | * RecordIterator recordIterator = new RecordIterator(t, ridIterator); 14 | * recordIterator.next(); // equivalent to t.getRecord(ridIterator.next()) 15 | * recordIterator.next(); // equivalent to t.getRecord(ridIterator.next()) 16 | * recordIterator.next(); // equivalent to t.getRecord(ridIterator.next()) 17 | */ 18 | public class RecordIterator implements BacktrackingIterator { 19 | private Iterator ridIter; 20 | private Table table; 21 | 22 | public RecordIterator(Table table, Iterator ridIter) { 23 | this.ridIter = ridIter; 24 | this.table = table; 25 | } 26 | 27 | @Override 28 | public boolean hasNext() { 29 | return ridIter.hasNext(); 30 | } 31 | 32 | @Override 33 | public Record next() { 34 | try { 35 | return table.getRecord(ridIter.next()); 36 | } catch (DatabaseException e) { 37 | throw new IllegalStateException(e); 38 | } 39 | } 40 | 41 | @Override 42 | public void markPrev() { 43 | if (ridIter instanceof BacktrackingIterator) { 44 | ((BacktrackingIterator) ridIter).markPrev(); 45 | } else { 46 | throw new UnsupportedOperationException("Cannot markPrev using underlying iterator"); 47 | } 48 | } 49 | 50 | @Override 51 | public void markNext() { 52 | if (ridIter instanceof BacktrackingIterator) { 53 | ((BacktrackingIterator) ridIter).markNext(); 54 | } else { 55 | throw new UnsupportedOperationException("Cannot markNext using underlying iterator"); 56 | } 57 | } 58 | 59 | @Override 60 | public void reset() { 61 | if (ridIter instanceof BacktrackingIterator) { 62 | ((BacktrackingIterator) ridIter).reset(); 63 | } else { 64 | throw new UnsupportedOperationException("Cannot reset using underlying iterator"); 65 | } 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/main/java/edu/berkeley/cs186/database/table/stats/Bucket.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table.stats; 2 | 3 | import java.util.Objects; 4 | import java.util.HashSet; 5 | 6 | /** 7 | * A histogram bucket. There are two types of buckets: 8 | * 9 | * 1. A bounded bucket `new Bucket(start, stop)` represents a count of 10 | * values in the range [start, stop). 11 | * 2. An unbounded bucket `new Bucket(start)` represents a count of values 12 | * in the range [start, infinity). 13 | */ 14 | public class Bucket { 15 | // If end is not null, then this bucket corresponds to range [start, stop). 16 | // If end is null, then this bucket corresponds to the single value start. 17 | private T start; 18 | private T end; 19 | private int count; 20 | private int distinctCount; 21 | 22 | //todo fix later 23 | private HashSet dictionary; 24 | 25 | public Bucket(T start) { 26 | this(start, null); 27 | } 28 | 29 | public Bucket(T start, T end) { 30 | this.start = start; 31 | this.end = end; 32 | this.count = 0; 33 | 34 | this.distinctCount = 0; 35 | this.dictionary = new HashSet<>(); 36 | } 37 | 38 | public T getStart() { 39 | return start; 40 | } 41 | 42 | public T getEnd() { 43 | return end; 44 | } 45 | 46 | public int getCount() { 47 | return count; 48 | } 49 | 50 | public void setCount(int count) { 51 | this.count = count; 52 | } 53 | 54 | public void setDistinctCount(int count) { 55 | this.distinctCount = count; 56 | dictionary = new HashSet<>(); 57 | } 58 | 59 | public int getDistinctCount() { 60 | return this.distinctCount + dictionary.size(); 61 | } 62 | 63 | public void increment(float val) { 64 | count ++; 65 | dictionary.add(val); 66 | } 67 | 68 | public void decrement(float val) { 69 | count --; 70 | dictionary.remove(val); 71 | } 72 | 73 | @Override 74 | public String toString() { 75 | return String.format("[%s,%s):%d", start, end, count); 76 | } 77 | 78 | @Override 79 | public boolean equals(Object o) { 80 | if (o == this) { 81 | return true; 82 | } 83 | if (!(o instanceof Bucket)) { 84 | return false; 85 | } 86 | Bucket b = (Bucket) o; 87 | boolean startEquals = start.equals(b.start); 88 | boolean endEquals = (end == null && b.end == null) || 89 | (end != null && b.end != null && end.equals(b.end)); 90 | boolean countEquals = count == b.count; 91 | return startEquals && endEquals && countEquals; 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | return Objects.hash(start, end, count); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/TestDatabaseDeadlockPrecheck.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database; 2 | 3 | import edu.berkeley.cs186.database.categories.Proj4Part3Tests; 4 | import edu.berkeley.cs186.database.categories.Proj4Tests; 5 | import edu.berkeley.cs186.database.categories.PublicTests; 6 | import edu.berkeley.cs186.database.common.Pair; 7 | import edu.berkeley.cs186.database.concurrency.LockType; 8 | import edu.berkeley.cs186.database.concurrency.LoggingLockManager; 9 | import edu.berkeley.cs186.database.concurrency.ResourceName; 10 | import org.junit.BeforeClass; 11 | import org.junit.ClassRule; 12 | import org.junit.Rule; 13 | import org.junit.Test; 14 | import org.junit.experimental.categories.Category; 15 | import org.junit.rules.*; 16 | 17 | import java.io.File; 18 | import java.io.IOException; 19 | import java.io.UncheckedIOException; 20 | 21 | import static org.junit.Assert.assertTrue; 22 | 23 | @Category({Proj4Tests.class, Proj4Part3Tests.class}) 24 | public class TestDatabaseDeadlockPrecheck { 25 | private static final String TestDir = "testDatabaseDeadlockPrecheck"; 26 | 27 | // 7 second max per method tested. 28 | public static long timeout = (long) (7000 * TimeoutScaling.factor); 29 | 30 | @Rule 31 | public TestRule globalTimeout = new DisableOnDebug(Timeout.millis(timeout)); 32 | 33 | @Rule 34 | public TemporaryFolder tempFolder = new TemporaryFolder(); 35 | 36 | @Test 37 | @Category(PublicTests.class) 38 | public void testDeadlock() { 39 | assertTrue(performCheck(tempFolder)); 40 | } 41 | 42 | public static boolean performCheck(TemporaryFolder checkFolder) { 43 | // If we are unable to request an X lock after an X lock is requested and released, there is no point 44 | // running any of the tests in this class - every test will block the main thread. 45 | final ResourceName name = new ResourceName(new Pair<>("database", 0L)); 46 | final LockType lockType = LockType.X; 47 | 48 | Thread mainRunner = new Thread(() -> { 49 | try { 50 | File testDir = checkFolder.newFolder(TestDir); 51 | String filename = testDir.getAbsolutePath(); 52 | LoggingLockManager lockManager = new LoggingLockManager(); 53 | Database database = new Database(filename, 128, lockManager); 54 | database.setWorkMem(32); 55 | database.waitSetupFinished(); 56 | try(Transaction transaction = database.beginTransaction()) { 57 | lockManager.acquire(transaction.getTransactionContext(), name, lockType); 58 | } 59 | try(Transaction transaction = database.beginTransaction()) { 60 | lockManager.acquire(transaction.getTransactionContext(), name, lockType); 61 | } 62 | } catch (IOException e) { 63 | throw new UncheckedIOException(e); 64 | } catch (Exception e) { 65 | throw new RuntimeException(e); 66 | } 67 | }); 68 | 69 | mainRunner.start(); 70 | try { 71 | if ((new DisableOnDebug(new TestName()).isDebugging())) { 72 | mainRunner.join(); 73 | } else { 74 | mainRunner.join(timeout); 75 | } 76 | } catch (InterruptedException e) { 77 | throw new RuntimeException(e); 78 | } 79 | 80 | return mainRunner.getState() == Thread.State.TERMINATED; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/TimeoutScaling.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database; 2 | 3 | public final class TimeoutScaling { 4 | // How much to scale test timeouts by. 5 | public static final double factor = 1.0; 6 | } 7 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/HiddenTests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface HiddenTests { /* category marker */ } 4 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj0Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj0Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj2Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj2Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj3Part1Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj3Part1Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj3Part2Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj3Part2Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj3Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj3Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj4Part1Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj4Part1Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj4Part2Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj4Part2Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj4Part3Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj4Part3Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj4Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj4Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj5Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj5Tests extends ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/Proj99Tests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface Proj99Tests extends ProjTests { /* category marker for non-homework tests */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/ProjTests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface ProjTests { /* category marker */ } -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/PublicTests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface PublicTests { /* category marker */ } 4 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/StudentTestRunner.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface StudentTestRunner { /* category marker */ } 4 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/StudentTests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface StudentTests { /* category marker */ } 4 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/categories/SystemTests.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.categories; 2 | 3 | public interface SystemTests { /* category marker */ } 4 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/concurrency/DeterministicRunner.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | import java.util.concurrent.locks.Condition; 5 | import java.util.concurrent.locks.ReentrantLock; 6 | 7 | /** 8 | * A utility class for running code over multiple threads, in a specific order. Any 9 | * exceptions/errors thrown in a child thread are rethrown in the main thread. 10 | */ 11 | public class DeterministicRunner { 12 | private final Worker[] workers; 13 | private Throwable error = null; 14 | 15 | private class Worker implements Runnable { 16 | private Thread thread; 17 | private final ReentrantLock lock = new ReentrantLock(); 18 | private final Condition sleepCondition = lock.newCondition(); 19 | private final Condition wakeCondition = lock.newCondition(); 20 | private final AtomicBoolean awake = new AtomicBoolean(false); 21 | private final AtomicBoolean ready = new AtomicBoolean(false); 22 | private Runnable nextTask = null; 23 | 24 | public Worker() { 25 | this.thread = new Thread(this); 26 | } 27 | 28 | @Override 29 | public void run() { 30 | try { 31 | sleep(); 32 | while (nextTask != null) { 33 | nextTask.run(); 34 | sleep(); 35 | } 36 | } catch (Throwable throwable) { 37 | error = throwable; 38 | } 39 | } 40 | 41 | private void sleep() { 42 | lock.lock(); 43 | try { 44 | while (!ready.get()) { 45 | sleepCondition.awaitUninterruptibly(); 46 | } 47 | } finally { 48 | awake.set(true); 49 | ready.set(false); 50 | wakeCondition.signal(); 51 | lock.unlock(); 52 | } 53 | } 54 | 55 | public void start() { 56 | thread.start(); 57 | } 58 | 59 | public void runTask(Runnable next) { 60 | lock.lock(); 61 | try { 62 | nextTask = next; 63 | ready.set(true); 64 | sleepCondition.signal(); 65 | } finally { 66 | lock.unlock(); 67 | } 68 | lock.lock(); 69 | try { 70 | while (!awake.get()) { 71 | wakeCondition.awaitUninterruptibly(); 72 | } 73 | awake.set(false); 74 | } finally { 75 | lock.unlock(); 76 | } 77 | while (thread.getState() != Thread.State.WAITING && thread.getState() != Thread.State.TERMINATED && 78 | error == null) { 79 | // return when either we finished the task (and went back to sleep) 80 | // or when the task caused the thread to block 81 | Thread.yield(); 82 | } 83 | } 84 | 85 | public void join() throws InterruptedException { 86 | lock.lock(); 87 | try { 88 | nextTask = null; 89 | ready.set(true); 90 | sleepCondition.signal(); 91 | } finally { 92 | lock.unlock(); 93 | } 94 | thread.join(); 95 | } 96 | } 97 | 98 | public DeterministicRunner(int numWorkers) { 99 | this.workers = new Worker[numWorkers]; 100 | for (int i = 0; i < numWorkers; ++i) { 101 | this.workers[i] = new Worker(); 102 | this.workers[i].start(); 103 | } 104 | } 105 | 106 | public void run(int thread, Runnable task) { 107 | error = null; 108 | this.workers[thread].runTask(task); 109 | if (error != null) { 110 | rethrow(error); 111 | } 112 | } 113 | 114 | public void join(int thread) { 115 | try { 116 | this.workers[thread].join(); 117 | } catch (Throwable t) { 118 | rethrow(t); 119 | } 120 | } 121 | 122 | public void joinAll() { 123 | for (int i = 0; i < this.workers.length; ++i) { 124 | join(i); 125 | } 126 | } 127 | 128 | @SuppressWarnings("unchecked") 129 | private static void rethrow(Throwable t) throws T { 130 | // rethrows checked exceptions as unchecked 131 | throw (T) t; 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/concurrency/LoggingLockContext.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.concurrency; 2 | 3 | import edu.berkeley.cs186.database.common.Pair; 4 | 5 | public class LoggingLockContext extends LockContext { 6 | private boolean allowDisable = true; 7 | 8 | /** 9 | * A special LockContext that works with a LoggingLockManager to emit logs 10 | * when the user uses disableChildLocks() or capacity() 11 | */ 12 | LoggingLockContext(LoggingLockManager lockman, LockContext parent, Pair name) { 13 | super(lockman, parent, name); 14 | } 15 | 16 | private LoggingLockContext(LoggingLockManager lockman, LockContext parent, Pair name, 17 | boolean readonly) { 18 | super(lockman, parent, name, readonly); 19 | } 20 | 21 | /** 22 | * Disables locking children. This causes all child contexts of this context 23 | * to be readonly. This is used for indices and temporary tables (where 24 | * we disallow finer-grain locks), the former due to complexity locking 25 | * B+ trees, and the latter due to the fact that temporary tables are only 26 | * accessible to one transaction, so finer-grain locks make no sense. 27 | */ 28 | @Override 29 | public synchronized void disableChildLocks() { 30 | if (this.allowDisable) { 31 | super.disableChildLocks(); 32 | } 33 | ((LoggingLockManager) lockman).emit("disable-children " + name); 34 | } 35 | 36 | /** 37 | * Gets the context for the child with name NAME (with a readable version READABLE). 38 | */ 39 | @Override 40 | public synchronized LockContext childContext(String readable, long name) { 41 | LockContext temp = new LoggingLockContext((LoggingLockManager) lockman, this, new Pair<>(readable, 42 | name), 43 | this.childLocksDisabled || this.readonly); 44 | LockContext child = this.children.putIfAbsent(name, temp); 45 | if (child == null) { 46 | child = temp; 47 | } 48 | return child; 49 | } 50 | 51 | /** 52 | * Sets the capacity (number of children). 53 | */ 54 | @Override 55 | public synchronized void capacity(int capacity) { 56 | int oldCapacity = super.capacity; 57 | super.capacity(capacity); 58 | if (oldCapacity != capacity) { 59 | ((LoggingLockManager) lockman).emit("set-capacity " + name + " " + capacity); 60 | } 61 | } 62 | 63 | public synchronized void allowDisableChildLocks(boolean allow) { 64 | this.allowDisable = allow; 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/databox/TestBoolDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import edu.berkeley.cs186.database.categories.*; 8 | import edu.berkeley.cs186.database.common.ByteBuffer; 9 | 10 | import org.junit.Test; 11 | import org.junit.experimental.categories.Category; 12 | 13 | @Category({Proj99Tests.class, SystemTests.class}) 14 | public class TestBoolDataBox { 15 | @Test 16 | public void testType() { 17 | assertEquals(Type.boolType(), new BoolDataBox(true).type()); 18 | assertEquals(Type.boolType(), new BoolDataBox(false).type()); 19 | } 20 | 21 | @Test 22 | public void testGetBool() { 23 | assertEquals(true, new BoolDataBox(true).getBool()); 24 | assertEquals(false, new BoolDataBox(false).getBool()); 25 | } 26 | 27 | @Test(expected = DataBoxException.class) 28 | public void testGetInt() { 29 | new BoolDataBox(true).getInt(); 30 | } 31 | 32 | @Test(expected = DataBoxException.class) 33 | public void testGetLong() { 34 | new BoolDataBox(true).getLong(); 35 | } 36 | 37 | @Test(expected = DataBoxException.class) 38 | public void testGetFloat() { 39 | new BoolDataBox(true).getFloat(); 40 | } 41 | 42 | @Test(expected = DataBoxException.class) 43 | public void testGetString() { 44 | new BoolDataBox(true).getString(); 45 | } 46 | 47 | @Test 48 | public void testToAndFromBytes() { 49 | for (boolean b : new boolean[] {true, false}) { 50 | BoolDataBox d = new BoolDataBox(b); 51 | byte[] bytes = d.toBytes(); 52 | assertEquals(d, DataBox.fromBytes(ByteBuffer.wrap(bytes), Type.boolType())); 53 | } 54 | } 55 | 56 | @Test 57 | public void testEquals() { 58 | BoolDataBox tru = new BoolDataBox(true); 59 | BoolDataBox fls = new BoolDataBox(false); 60 | assertEquals(tru, tru); 61 | assertEquals(fls, fls); 62 | assertNotEquals(tru, fls); 63 | assertNotEquals(fls, tru); 64 | } 65 | 66 | @Test 67 | public void testCompareTo() { 68 | BoolDataBox tru = new BoolDataBox(true); 69 | BoolDataBox fls = new BoolDataBox(false); 70 | assertTrue(fls.compareTo(fls) == 0); 71 | assertTrue(fls.compareTo(tru) < 0); 72 | assertTrue(tru.compareTo(tru) == 0); 73 | assertTrue(tru.compareTo(fls) > 0); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/databox/TestFloatDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import edu.berkeley.cs186.database.categories.*; 8 | import edu.berkeley.cs186.database.common.ByteBuffer; 9 | 10 | import org.junit.Test; 11 | import org.junit.experimental.categories.Category; 12 | 13 | @Category({Proj99Tests.class, SystemTests.class}) 14 | public class TestFloatDataBox { 15 | @Test 16 | public void testType() { 17 | assertEquals(Type.floatType(), new FloatDataBox(0f).type()); 18 | } 19 | 20 | @Test(expected = DataBoxException.class) 21 | public void testGetBool() { 22 | new FloatDataBox(0f).getBool(); 23 | } 24 | 25 | @Test(expected = DataBoxException.class) 26 | public void testGetInt() { 27 | new FloatDataBox(0f).getInt(); 28 | } 29 | 30 | @Test(expected = DataBoxException.class) 31 | public void testGetLong() { 32 | new FloatDataBox(0f).getLong(); 33 | } 34 | 35 | @Test 36 | public void testGetFloat() { 37 | assertEquals(0f, new FloatDataBox(0f).getFloat(), 0.0001); 38 | } 39 | 40 | @Test(expected = DataBoxException.class) 41 | public void testGetString() { 42 | new FloatDataBox(0f).getString(); 43 | } 44 | 45 | @Test 46 | public void testToAndFromBytes() { 47 | for (int i = -10; i < 10; ++i) { 48 | FloatDataBox d = new FloatDataBox((float) i); 49 | byte[] bytes = d.toBytes(); 50 | assertEquals(d, DataBox.fromBytes(ByteBuffer.wrap(bytes), Type.floatType())); 51 | } 52 | } 53 | 54 | @Test 55 | public void testEquals() { 56 | FloatDataBox zero = new FloatDataBox(0f); 57 | FloatDataBox one = new FloatDataBox(1f); 58 | assertEquals(zero, zero); 59 | assertEquals(one, one); 60 | assertNotEquals(zero, one); 61 | assertNotEquals(one, zero); 62 | } 63 | 64 | @Test 65 | public void testCompareTo() { 66 | FloatDataBox zero = new FloatDataBox(0f); 67 | FloatDataBox one = new FloatDataBox(1f); 68 | assertTrue(zero.compareTo(zero) == 0); 69 | assertTrue(zero.compareTo(one) < 0); 70 | assertTrue(one.compareTo(one) == 0); 71 | assertTrue(one.compareTo(zero) > 0); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/databox/TestIntDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import edu.berkeley.cs186.database.categories.*; 8 | import edu.berkeley.cs186.database.common.ByteBuffer; 9 | 10 | import org.junit.Test; 11 | import org.junit.experimental.categories.Category; 12 | 13 | @Category({Proj99Tests.class, SystemTests.class}) 14 | public class TestIntDataBox { 15 | @Test 16 | public void testType() { 17 | assertEquals(Type.intType(), new IntDataBox(0).type()); 18 | } 19 | 20 | @Test(expected = DataBoxException.class) 21 | public void testGetBool() { 22 | new IntDataBox(0).getBool(); 23 | } 24 | 25 | @Test 26 | public void testGetInt() { 27 | assertEquals(0, new IntDataBox(0).getInt()); 28 | } 29 | 30 | @Test(expected = DataBoxException.class) 31 | public void testGetLong() { 32 | new IntDataBox(0).getLong(); 33 | } 34 | 35 | @Test(expected = DataBoxException.class) 36 | public void testGetFloat() { 37 | new IntDataBox(0).getFloat(); 38 | } 39 | 40 | @Test(expected = DataBoxException.class) 41 | public void testGetString() { 42 | new IntDataBox(0).getString(); 43 | } 44 | 45 | @Test 46 | public void testToAndFromBytes() { 47 | for (int i = -10; i < 10; ++i) { 48 | IntDataBox d = new IntDataBox(i); 49 | byte[] bytes = d.toBytes(); 50 | assertEquals(d, DataBox.fromBytes(ByteBuffer.wrap(bytes), Type.intType())); 51 | } 52 | } 53 | 54 | @Test 55 | public void testEquals() { 56 | IntDataBox zero = new IntDataBox(0); 57 | IntDataBox one = new IntDataBox(1); 58 | assertEquals(zero, zero); 59 | assertEquals(one, one); 60 | assertNotEquals(zero, one); 61 | assertNotEquals(one, zero); 62 | } 63 | 64 | @Test 65 | public void testCompareTo() { 66 | IntDataBox zero = new IntDataBox(0); 67 | IntDataBox one = new IntDataBox(1); 68 | assertTrue(zero.compareTo(zero) == 0); 69 | assertTrue(zero.compareTo(one) < 0); 70 | assertTrue(one.compareTo(one) == 0); 71 | assertTrue(one.compareTo(zero) > 0); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/databox/TestLongDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import edu.berkeley.cs186.database.categories.Proj99Tests; 4 | import edu.berkeley.cs186.database.categories.SystemTests; 5 | import edu.berkeley.cs186.database.common.ByteBuffer; 6 | import org.junit.Test; 7 | import org.junit.experimental.categories.Category; 8 | 9 | import static org.junit.Assert.*; 10 | 11 | @Category({Proj99Tests.class, SystemTests.class}) 12 | public class TestLongDataBox { 13 | @Test 14 | public void testType() { 15 | assertEquals(Type.longType(), new LongDataBox(0L).type()); 16 | } 17 | 18 | @Test(expected = DataBoxException.class) 19 | public void testGetBool() { 20 | new LongDataBox(0L).getBool(); 21 | } 22 | 23 | @Test(expected = DataBoxException.class) 24 | public void testGetInt() { 25 | new LongDataBox(0L).getInt(); 26 | } 27 | 28 | @Test 29 | public void testGetLong() { 30 | assertEquals(0L, new LongDataBox(0L).getLong()); 31 | } 32 | 33 | @Test(expected = DataBoxException.class) 34 | public void testGetFloat() { 35 | new LongDataBox(0L).getFloat(); 36 | } 37 | 38 | @Test(expected = DataBoxException.class) 39 | public void testGetString() { 40 | new LongDataBox(0L).getString(); 41 | } 42 | 43 | @Test 44 | public void testToAndFromBytes() { 45 | for (long i = -10L; i < 10L; ++i) { 46 | LongDataBox d = new LongDataBox(i); 47 | byte[] bytes = d.toBytes(); 48 | assertEquals(d, DataBox.fromBytes(ByteBuffer.wrap(bytes), Type.longType())); 49 | } 50 | } 51 | 52 | @Test 53 | public void testEquals() { 54 | LongDataBox zero = new LongDataBox(0L); 55 | LongDataBox one = new LongDataBox(1L); 56 | assertEquals(zero, zero); 57 | assertEquals(one, one); 58 | assertNotEquals(zero, one); 59 | assertNotEquals(one, zero); 60 | } 61 | 62 | @Test 63 | public void testCompareTo() { 64 | LongDataBox zero = new LongDataBox(0L); 65 | LongDataBox one = new LongDataBox(1L); 66 | assertTrue(zero.compareTo(zero) == 0L); 67 | assertTrue(zero.compareTo(one) < 0L); 68 | assertTrue(one.compareTo(one) == 0L); 69 | assertTrue(one.compareTo(zero) > 0L); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/databox/TestStringDataBox.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import edu.berkeley.cs186.database.categories.*; 8 | import edu.berkeley.cs186.database.common.ByteBuffer; 9 | 10 | import org.junit.Test; 11 | import org.junit.experimental.categories.Category; 12 | 13 | @Category({Proj99Tests.class, SystemTests.class}) 14 | public class TestStringDataBox { 15 | @Test(expected = DataBoxException.class) 16 | public void testEmptyString() { 17 | new StringDataBox("", 0); 18 | } 19 | 20 | @Test 21 | public void testType() { 22 | assertEquals(Type.stringType(3), new StringDataBox("foo", 3).type()); 23 | } 24 | 25 | @Test(expected = DataBoxException.class) 26 | public void testGetBool() { 27 | new StringDataBox("foo", 3).getBool(); 28 | } 29 | 30 | @Test(expected = DataBoxException.class) 31 | public void testGetInt() { 32 | new StringDataBox("foo", 3).getInt(); 33 | } 34 | 35 | @Test(expected = DataBoxException.class) 36 | public void testGetLong() { 37 | new StringDataBox("foo", 3).getLong(); 38 | } 39 | 40 | @Test(expected = DataBoxException.class) 41 | public void testGetFloat() { 42 | new StringDataBox("foo", 3).getFloat(); 43 | } 44 | 45 | @Test 46 | public void testGetString() { 47 | assertEquals("f", new StringDataBox("foo", 1).getString()); 48 | assertEquals("fo", new StringDataBox("foo", 2).getString()); 49 | assertEquals("foo", new StringDataBox("foo", 3).getString()); 50 | assertEquals("foo", new StringDataBox("foo", 4).getString()); 51 | assertEquals("foo", new StringDataBox("foo", 5).getString()); 52 | } 53 | 54 | @Test 55 | public void testToAndFromBytes() { 56 | for (String s : new String[] {"foo", "bar", "baz"}) { 57 | StringDataBox d = new StringDataBox(s, 3); 58 | byte[] bytes = d.toBytes(); 59 | assertEquals(d, DataBox.fromBytes(ByteBuffer.wrap(bytes), Type.stringType(3))); 60 | } 61 | } 62 | 63 | @Test 64 | public void testEquals() { 65 | StringDataBox foo = new StringDataBox("foo", 3); 66 | StringDataBox zoo = new StringDataBox("zoo", 3); 67 | assertEquals(foo, foo); 68 | assertEquals(zoo, zoo); 69 | assertNotEquals(foo, zoo); 70 | assertNotEquals(zoo, foo); 71 | } 72 | 73 | @Test 74 | public void testCompareTo() { 75 | StringDataBox foo = new StringDataBox("foo", 3); 76 | StringDataBox zoo = new StringDataBox("zoo", 3); 77 | assertTrue(foo.compareTo(foo) == 0); 78 | assertTrue(foo.compareTo(zoo) < 0); 79 | assertTrue(zoo.compareTo(zoo) == 0); 80 | assertTrue(zoo.compareTo(foo) > 0); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/databox/TestType.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | 6 | import edu.berkeley.cs186.database.categories.*; 7 | import edu.berkeley.cs186.database.common.Buffer; 8 | import edu.berkeley.cs186.database.common.ByteBuffer; 9 | 10 | import org.junit.Test; 11 | import org.junit.experimental.categories.Category; 12 | 13 | @Category({Proj99Tests.class, SystemTests.class}) 14 | public class TestType { 15 | @Test 16 | public void testBoolType() { 17 | // Check type id and size. 18 | Type boolType = Type.boolType(); 19 | assertEquals(boolType.getTypeId(), TypeId.BOOL); 20 | assertEquals(boolType.getSizeInBytes(), 1); 21 | 22 | // Check toBytes and fromBytes. 23 | Buffer buf = ByteBuffer.wrap(boolType.toBytes()); 24 | assertEquals(boolType, Type.fromBytes(buf)); 25 | 26 | // Check equality. 27 | assertEquals(boolType, Type.boolType()); 28 | assertNotEquals(boolType, Type.intType()); 29 | assertNotEquals(boolType, Type.floatType()); 30 | assertNotEquals(boolType, Type.stringType(1)); 31 | assertNotEquals(boolType, Type.stringType(2)); 32 | } 33 | 34 | @Test 35 | public void testIntType() { 36 | // Check type id and size. 37 | Type intType = Type.intType(); 38 | assertEquals(intType.getTypeId(), TypeId.INT); 39 | assertEquals(intType.getSizeInBytes(), 4); 40 | 41 | // Check toBytes and fromBytes. 42 | Buffer buf = ByteBuffer.wrap(intType.toBytes()); 43 | assertEquals(intType, Type.fromBytes(buf)); 44 | 45 | // Check equality. 46 | assertNotEquals(intType, Type.boolType()); 47 | assertEquals(intType, Type.intType()); 48 | assertNotEquals(intType, Type.floatType()); 49 | assertNotEquals(intType, Type.stringType(1)); 50 | assertNotEquals(intType, Type.stringType(2)); 51 | } 52 | 53 | @Test 54 | public void testFloatType() { 55 | // Check type id and size. 56 | Type floatType = Type.floatType(); 57 | assertEquals(floatType.getTypeId(), TypeId.FLOAT); 58 | assertEquals(floatType.getSizeInBytes(), 4); 59 | 60 | // Check toBytes and fromBytes. 61 | Buffer buf = ByteBuffer.wrap(floatType.toBytes()); 62 | assertEquals(floatType, Type.fromBytes(buf)); 63 | 64 | // Check equality. 65 | assertNotEquals(floatType, Type.boolType()); 66 | assertNotEquals(floatType, Type.intType()); 67 | assertEquals(floatType, Type.floatType()); 68 | assertNotEquals(floatType, Type.stringType(1)); 69 | assertNotEquals(floatType, Type.stringType(2)); 70 | } 71 | 72 | @Test(expected = DataBoxException.class) 73 | public void testZeroByteStringype() { 74 | Type.stringType(0); 75 | } 76 | 77 | @Test 78 | public void testOneByteStringype() { 79 | // Check type id and size. 80 | Type stringType = Type.stringType(1); 81 | assertEquals(stringType.getTypeId(), TypeId.STRING); 82 | assertEquals(stringType.getSizeInBytes(), 1); 83 | 84 | // Check toBytes and fromBytes. 85 | Buffer buf = ByteBuffer.wrap(stringType.toBytes()); 86 | assertEquals(stringType, Type.fromBytes(buf)); 87 | 88 | // Check equality. 89 | assertNotEquals(stringType, Type.boolType()); 90 | assertNotEquals(stringType, Type.intType()); 91 | assertNotEquals(stringType, Type.floatType()); 92 | assertEquals(stringType, Type.stringType(1)); 93 | assertNotEquals(stringType, Type.stringType(2)); 94 | } 95 | 96 | @Test 97 | public void testTwoByteStringype() { 98 | // Check type id and size. 99 | Type stringType = Type.stringType(2); 100 | assertEquals(stringType.getTypeId(), TypeId.STRING); 101 | assertEquals(stringType.getSizeInBytes(), 2); 102 | 103 | // Check toBytes and fromBytes. 104 | Buffer buf = ByteBuffer.wrap(stringType.toBytes()); 105 | assertEquals(stringType, Type.fromBytes(buf)); 106 | 107 | // Check equality. 108 | assertNotEquals(stringType, Type.boolType()); 109 | assertNotEquals(stringType, Type.intType()); 110 | assertNotEquals(stringType, Type.floatType()); 111 | assertNotEquals(stringType, Type.stringType(1)); 112 | assertEquals(stringType, Type.stringType(2)); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/databox/TestWelcome.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.databox; 2 | 3 | import edu.berkeley.cs186.database.categories.Proj0Tests; 4 | import edu.berkeley.cs186.database.categories.PublicTests; 5 | import org.junit.Test; 6 | import org.junit.experimental.categories.Category; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | 10 | @Category({Proj0Tests.class, PublicTests.class}) 11 | public class TestWelcome { 12 | @Test 13 | public void testComplete() { 14 | assertEquals("welcome", new StringDataBox("welcome", 7).toString()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/index/TestBPlusNode.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.index; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | import edu.berkeley.cs186.database.TimeoutScaling; 10 | import edu.berkeley.cs186.database.concurrency.DummyLockContext; 11 | import edu.berkeley.cs186.database.concurrency.LockContext; 12 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 13 | import edu.berkeley.cs186.database.io.MemoryDiskSpaceManager; 14 | import edu.berkeley.cs186.database.memory.BufferManager; 15 | import edu.berkeley.cs186.database.memory.BufferManagerImpl; 16 | import edu.berkeley.cs186.database.memory.ClockEvictionPolicy; 17 | import edu.berkeley.cs186.database.recovery.DummyRecoveryManager; 18 | import org.junit.*; 19 | import org.junit.experimental.categories.Category; 20 | import org.junit.rules.DisableOnDebug; 21 | import org.junit.rules.TestRule; 22 | import org.junit.rules.Timeout; 23 | 24 | import edu.berkeley.cs186.database.categories.*; 25 | import edu.berkeley.cs186.database.databox.DataBox; 26 | import edu.berkeley.cs186.database.databox.IntDataBox; 27 | import edu.berkeley.cs186.database.databox.Type; 28 | import edu.berkeley.cs186.database.table.RecordId; 29 | 30 | @Category(Proj2Tests.class) 31 | public class TestBPlusNode { 32 | private static final int ORDER = 5; 33 | 34 | private BufferManager bufferManager; 35 | private BPlusTreeMetadata metadata; 36 | private LockContext treeContext; 37 | 38 | // 1 seconds max per method tested. 39 | @Rule 40 | public TestRule globalTimeout = new DisableOnDebug(Timeout.millis((long) ( 41 | 1000 * TimeoutScaling.factor))); 42 | 43 | @Before 44 | public void setup() { 45 | DiskSpaceManager diskSpaceManager = new MemoryDiskSpaceManager(); 46 | diskSpaceManager.allocPart(0); 47 | this.bufferManager = new BufferManagerImpl(diskSpaceManager, new DummyRecoveryManager(), 1024, 48 | new ClockEvictionPolicy()); 49 | this.treeContext = new DummyLockContext(); 50 | this.metadata = new BPlusTreeMetadata("test", "col", Type.intType(), ORDER, 51 | 0, DiskSpaceManager.INVALID_PAGE_NUM, -1); 52 | } 53 | 54 | @After 55 | public void cleanup() { 56 | this.bufferManager.close(); 57 | } 58 | 59 | @Test 60 | @Category(PublicTests.class) 61 | public void testFromBytes() { 62 | // Leaf node. 63 | List leafKeys = new ArrayList<>(); 64 | List leafRids = new ArrayList<>(); 65 | for (int i = 0; i < 2 * ORDER; ++i) { 66 | leafKeys.add(new IntDataBox(i)); 67 | leafRids.add(new RecordId(i, (short) i)); 68 | } 69 | LeafNode leaf = new LeafNode(metadata, bufferManager, leafKeys, leafRids, Optional.of(42L), 70 | treeContext); 71 | 72 | // Inner node. 73 | List innerKeys = new ArrayList<>(); 74 | List innerChildren = new ArrayList<>(); 75 | for (int i = 0; i < 2 * ORDER; ++i) { 76 | innerKeys.add(new IntDataBox(i)); 77 | innerChildren.add((long) i); 78 | } 79 | innerChildren.add((long) 2 * ORDER); 80 | InnerNode inner = new InnerNode(metadata, bufferManager, innerKeys, innerChildren, 81 | treeContext); 82 | 83 | long leafPageNum = leaf.getPage().getPageNum(); 84 | long innerPageNum = inner.getPage().getPageNum(); 85 | assertEquals(leaf, BPlusNode.fromBytes(metadata, bufferManager, treeContext, leafPageNum)); 86 | assertEquals(inner, BPlusNode.fromBytes(metadata, bufferManager, treeContext, innerPageNum)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/io/MemoryDiskSpaceManager.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.io; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * "Disk" space manager that really just keeps things in memory. Not thread safe. 7 | */ 8 | public class MemoryDiskSpaceManager implements DiskSpaceManager { 9 | private Map> partitions = new HashMap<>(); 10 | private Map nextPageNum = new HashMap<>(); 11 | private Map pages = new HashMap<>(); 12 | private int nextPartitionNum = 0; 13 | 14 | @Override 15 | public void close() {} 16 | 17 | @Override 18 | public int allocPart() { 19 | partitions.put(nextPartitionNum, new HashSet<>()); 20 | nextPageNum.put(nextPartitionNum, 0); 21 | return nextPartitionNum++; 22 | } 23 | 24 | @Override 25 | public int allocPart(int partNum) { 26 | if (partitions.containsKey(partNum)) { 27 | throw new IllegalStateException("partition " + partNum + " already allocated"); 28 | } 29 | partitions.put(partNum, new HashSet<>()); 30 | nextPageNum.put(partNum, 0); 31 | nextPartitionNum = partNum + 1; 32 | return partNum; 33 | } 34 | 35 | @Override 36 | public void freePart(int partNum) { 37 | if (!partitions.containsKey(partNum)) { 38 | throw new NoSuchElementException("partition " + partNum + " not allocated"); 39 | } 40 | for (Integer pageNum : partitions.remove(partNum)) { 41 | pages.remove(DiskSpaceManager.getVirtualPageNum(partNum, pageNum)); 42 | } 43 | nextPageNum.remove(partNum); 44 | } 45 | 46 | @Override 47 | public long allocPage(int partNum) { 48 | if (!partitions.containsKey(partNum)) { 49 | throw new IllegalArgumentException("partition " + partNum + " not allocated"); 50 | } 51 | int ppageNum = nextPageNum.get(partNum); 52 | nextPageNum.put(partNum, ppageNum + 1); 53 | long pageNum = DiskSpaceManager.getVirtualPageNum(partNum, ppageNum); 54 | partitions.get(partNum).add(ppageNum); 55 | pages.put(pageNum, new byte[DiskSpaceManager.PAGE_SIZE]); 56 | return pageNum; 57 | } 58 | 59 | @Override 60 | public long allocPage(long page) { 61 | int partNum = DiskSpaceManager.getPartNum(page); 62 | int ppageNum = DiskSpaceManager.getPageNum(page); 63 | if (!partitions.containsKey(partNum)) { 64 | throw new IllegalArgumentException("partition " + partNum + " not allocated"); 65 | } 66 | if (pages.containsKey(page)) { 67 | throw new IllegalStateException("page " + page + " already allocated"); 68 | } 69 | nextPageNum.put(partNum, ppageNum + 1); 70 | partitions.get(partNum).add(ppageNum); 71 | pages.put(page, new byte[DiskSpaceManager.PAGE_SIZE]); 72 | return page; 73 | } 74 | 75 | @Override 76 | public void freePage(long page) { 77 | if (!pages.containsKey(page)) { 78 | throw new NoSuchElementException("page " + page + " not allocated"); 79 | } 80 | int partNum = DiskSpaceManager.getPartNum(page); 81 | int pageNum = DiskSpaceManager.getPageNum(page); 82 | partitions.get(partNum).remove(pageNum); 83 | pages.remove(page); 84 | } 85 | 86 | @Override 87 | public void readPage(long page, byte[] buf) { 88 | if (buf.length != DiskSpaceManager.PAGE_SIZE) { 89 | throw new IllegalArgumentException("bad buffer size"); 90 | } 91 | if (!pages.containsKey(page)) { 92 | throw new PageException("page " + page + " not allocated"); 93 | } 94 | System.arraycopy(pages.get(page), 0, buf, 0, DiskSpaceManager.PAGE_SIZE); 95 | } 96 | 97 | @Override 98 | public void writePage(long page, byte[] buf) { 99 | if (buf.length != DiskSpaceManager.PAGE_SIZE) { 100 | throw new IllegalArgumentException("bad buffer size"); 101 | } 102 | if (!pages.containsKey(page)) { 103 | throw new PageException("page " + page + " not allocated"); 104 | } 105 | System.arraycopy(buf, 0, pages.get(page), 0, DiskSpaceManager.PAGE_SIZE); 106 | } 107 | 108 | @Override 109 | public boolean pageAllocated(long page) { 110 | return pages.containsKey(page); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/query/TestOptimization2.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.query; 2 | 3 | import edu.berkeley.cs186.database.*; 4 | import edu.berkeley.cs186.database.categories.*; 5 | import org.junit.*; 6 | import org.junit.experimental.categories.Category; 7 | import org.junit.rules.TemporaryFolder; 8 | 9 | import java.io.File; 10 | 11 | import edu.berkeley.cs186.database.table.Schema; 12 | 13 | import edu.berkeley.cs186.database.table.Table; 14 | import edu.berkeley.cs186.database.table.Record; 15 | import edu.berkeley.cs186.database.databox.IntDataBox; 16 | import edu.berkeley.cs186.database.databox.StringDataBox; 17 | import edu.berkeley.cs186.database.databox.FloatDataBox; 18 | import edu.berkeley.cs186.database.databox.BoolDataBox; 19 | 20 | import org.junit.After; 21 | 22 | import edu.berkeley.cs186.database.TimeoutScaling; 23 | import org.junit.rules.DisableOnDebug; 24 | import org.junit.rules.TestRule; 25 | import org.junit.rules.Timeout; 26 | 27 | @Category({Proj3Tests.class, Proj3Part2Tests.class}) 28 | public class TestOptimization2 { 29 | private static final String TABLENAME = "T"; 30 | 31 | private static final String TestDir = "testDatabase"; 32 | private Database db; 33 | 34 | //Before every test you create a temporary table, after every test you close it 35 | @Rule 36 | public TemporaryFolder tempFolder = new TemporaryFolder(); 37 | 38 | // 1 second max per method tested. 39 | @Rule 40 | public TestRule globalTimeout = new DisableOnDebug(Timeout.millis((long) ( 41 | 1000 * TimeoutScaling.factor))); 42 | 43 | @Before 44 | public void beforeEach() throws Exception { 45 | File testDir = tempFolder.newFolder(TestDir); 46 | String filename = testDir.getAbsolutePath(); 47 | this.db = new Database(filename, 32); 48 | this.db.setWorkMem(5); // B=5 49 | 50 | try(Transaction t = this.db.beginTransaction()) { 51 | t.dropAllTables(); 52 | 53 | Schema schema = TestUtils.createSchemaWithAllTypes(); 54 | 55 | t.createTable(schema, TABLENAME); 56 | 57 | //t.createTableWithIndices(schema, TABLENAME, Arrays.asList("int")); 58 | } 59 | this.db.waitAllTransactions(); 60 | } 61 | 62 | @After 63 | public void afterEach() { 64 | this.db.waitAllTransactions(); 65 | try(Transaction t = this.db.beginTransaction()) { 66 | t.dropAllTables(); 67 | } 68 | this.db.close(); 69 | } 70 | 71 | //creates a record with all specified types 72 | private static Record createRecordWithAllTypes(boolean a1, int a2, String a3, float a4) { 73 | Record r = TestUtils.createRecordWithAllTypes(); 74 | r.getValues().set(0, new BoolDataBox(a1)); 75 | r.getValues().set(1, new IntDataBox(a2)); 76 | r.getValues().set(2, new StringDataBox(a3, 1)); 77 | r.getValues().set(3, new FloatDataBox(a4)); 78 | return r; 79 | } 80 | 81 | @Test 82 | @Category(PublicTests.class) 83 | public void test() { 84 | try(Transaction transaction = this.db.beginTransaction()) { 85 | //creates a 100 records int 0 to 99 86 | for (int i = 0; i < 2000; ++i) { 87 | Record r = createRecordWithAllTypes(false, i, "!", 0.0f); 88 | transaction.insert(TABLENAME, r.getValues()); 89 | } 90 | 91 | //build the statistics on the table 92 | transaction.getTransactionContext().getTable(TABLENAME).buildStatistics(10); 93 | 94 | // add a join and a select to the QueryPlan 95 | QueryPlan query = transaction.query("T", "t1"); 96 | query.join("T", "t2", "t1.int", "t2.int"); 97 | //query.select("int", PredicateOperator.EQUALS, new IntDataBox(10)); 98 | 99 | // execute the query and get the output 100 | query.execute(); 101 | query.getFinalOperator(); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/query/TestSourceOperator.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.query; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | 7 | import edu.berkeley.cs186.database.TestUtils; 8 | import edu.berkeley.cs186.database.memory.BufferManager; 9 | import edu.berkeley.cs186.database.table.Record; 10 | import edu.berkeley.cs186.database.table.Schema; 11 | import edu.berkeley.cs186.database.table.Table; 12 | import edu.berkeley.cs186.database.table.stats.TableStats; 13 | 14 | public class TestSourceOperator extends QueryOperator { 15 | private List recordList; 16 | private Schema setSchema; 17 | private int numRecords; 18 | 19 | public TestSourceOperator() { 20 | super(OperatorType.SEQSCAN, null); 21 | this.recordList = null; 22 | this.setSchema = null; 23 | this.numRecords = 100; 24 | 25 | this.stats = this.estimateStats(); 26 | this.cost = this.estimateIOCost(); 27 | } 28 | 29 | public TestSourceOperator(List recordIterator, Schema schema) { 30 | super(OperatorType.SEQSCAN); 31 | 32 | this.recordList = recordIterator; 33 | this.setOutputSchema(schema); 34 | this.setSchema = schema; 35 | this.numRecords = 100; 36 | 37 | this.stats = this.estimateStats(); 38 | this.cost = this.estimateIOCost(); 39 | } 40 | 41 | @Override 42 | public boolean isSequentialScan() { 43 | return false; 44 | } 45 | 46 | public TestSourceOperator(int numRecords) { 47 | super(OperatorType.SEQSCAN, null); 48 | this.recordList = null; 49 | this.setSchema = null; 50 | this.numRecords = numRecords; 51 | 52 | this.stats = this.estimateStats(); 53 | this.cost = this.estimateIOCost(); 54 | } 55 | 56 | @Override 57 | public Iterator execute() { 58 | if (this.recordList == null) { 59 | ArrayList recordList = new ArrayList(); 60 | for (int i = 0; i < this.numRecords; i++) { 61 | recordList.add(TestUtils.createRecordWithAllTypes()); 62 | } 63 | 64 | return recordList.iterator(); 65 | } 66 | return this.recordList.iterator(); 67 | } 68 | 69 | @Override 70 | public Iterator iterator() { 71 | return this.execute(); 72 | } 73 | 74 | @Override 75 | protected Schema computeSchema() { 76 | if (this.setSchema == null) { 77 | return TestUtils.createSchemaWithAllTypes(); 78 | } 79 | return this.setSchema; 80 | } 81 | 82 | @Override 83 | public TableStats estimateStats() { 84 | Schema schema = this.computeSchema(); 85 | return new TableStats(schema, Table.computeNumRecordsPerPage(BufferManager.EFFECTIVE_PAGE_SIZE, 86 | schema)); 87 | } 88 | 89 | @Override 90 | public int estimateIOCost() { 91 | return 1; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/recovery/ARIESRecoveryManagerNoLocking.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | import edu.berkeley.cs186.database.Transaction; 4 | import edu.berkeley.cs186.database.concurrency.LockContext; 5 | 6 | import java.util.function.Function; 7 | 8 | // Instrumented version of ARIESRecoveryManager for testing, without locking so that tests pass without Proj4. 9 | class ARIESRecoveryManagerNoLocking extends ARIESRecoveryManager { 10 | long transactionCounter = 0L; 11 | 12 | ARIESRecoveryManagerNoLocking(LockContext dbContext, 13 | Function newTransaction) { 14 | super(dbContext, newTransaction, null, null, true); 15 | super.updateTransactionCounter = x -> transactionCounter = x; 16 | super.getTransactionCounter = () -> transactionCounter; 17 | } 18 | 19 | @Override 20 | public long logFreePage(long transNum, long pageNum) { 21 | long rv = super.logFreePage(transNum, pageNum); 22 | transactionTable.get(transNum).touchedPages.add(pageNum); 23 | dirtyPageTable.remove(pageNum); 24 | return rv; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/recovery/TestARIESStudentRunner.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.recovery; 2 | 3 | public class TestARIESStudentRunner extends TestARIESStudentRunnerBase { @Override String getSuffix() { return ""; } } 4 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/table/MemoryHeapFile.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table; 2 | 3 | import edu.berkeley.cs186.database.common.iterator.BacktrackingIterator; 4 | import edu.berkeley.cs186.database.common.iterator.IndexBacktrackingIterator; 5 | import edu.berkeley.cs186.database.concurrency.DummyLockContext; 6 | import edu.berkeley.cs186.database.io.DiskSpaceManager; 7 | import edu.berkeley.cs186.database.io.MemoryDiskSpaceManager; 8 | import edu.berkeley.cs186.database.io.PageException; 9 | import edu.berkeley.cs186.database.memory.BufferManager; 10 | import edu.berkeley.cs186.database.memory.BufferManagerImpl; 11 | import edu.berkeley.cs186.database.memory.ClockEvictionPolicy; 12 | import edu.berkeley.cs186.database.memory.Page; 13 | import edu.berkeley.cs186.database.recovery.DummyRecoveryManager; 14 | 15 | import java.util.*; 16 | 17 | /** 18 | * Heap file implementation that is entirely in memory. Not thread safe. 19 | */ 20 | public class MemoryHeapFile implements HeapFile, AutoCloseable { 21 | private List pageNums = new ArrayList<>(); 22 | private Map pages = new HashMap<>(); 23 | private Map freeSpace = new HashMap<>(); 24 | private short emptyPageMetadataSize = 0; 25 | private BufferManager bufferManager; 26 | private int numDataPages = 0; 27 | 28 | public MemoryHeapFile() { 29 | DiskSpaceManager diskSpaceManager = new MemoryDiskSpaceManager(); 30 | diskSpaceManager.allocPart(0); 31 | this.bufferManager = new BufferManagerImpl(diskSpaceManager, new DummyRecoveryManager(), 1024, 32 | new ClockEvictionPolicy()); 33 | } 34 | 35 | @Override 36 | public void close() { 37 | this.bufferManager.close(); 38 | } 39 | 40 | @Override 41 | public short getEffectivePageSize() { 42 | return BufferManager.EFFECTIVE_PAGE_SIZE; 43 | } 44 | 45 | @Override 46 | public void setEmptyPageMetadataSize(short emptyPageMetadataSize) { 47 | this.emptyPageMetadataSize = emptyPageMetadataSize; 48 | } 49 | 50 | @Override 51 | public Page getPage(long pageNum) { 52 | if (!pages.containsKey(pageNum) || pages.get(pageNum) == null) { 53 | throw new PageException("page not found"); 54 | } 55 | return bufferManager.fetchPage(new DummyLockContext(), pageNum, false); 56 | } 57 | 58 | @Override 59 | public Page getPageWithSpace(short requiredSpace) { 60 | for (Map.Entry entry : freeSpace.entrySet()) { 61 | if (entry.getValue() >= requiredSpace) { 62 | freeSpace.put(entry.getKey(), (short) (entry.getValue() - requiredSpace)); 63 | return bufferManager.fetchPage(new DummyLockContext(), entry.getKey(), false); 64 | } 65 | } 66 | Page page = bufferManager.fetchNewPage(new DummyLockContext(), 0, false); 67 | pageNums.add(page.getPageNum()); 68 | pages.put(page.getPageNum(), page); 69 | freeSpace.put(page.getPageNum(), 70 | (short) (getEffectivePageSize() - emptyPageMetadataSize - requiredSpace)); 71 | ++numDataPages; 72 | return page; 73 | } 74 | 75 | @Override 76 | public void updateFreeSpace(Page page, short newFreeSpace) { 77 | if (newFreeSpace == getEffectivePageSize() - emptyPageMetadataSize) { 78 | pages.put(page.getPageNum(), null); 79 | --numDataPages; 80 | } 81 | freeSpace.put(page.getPageNum(), newFreeSpace); 82 | } 83 | 84 | @Override 85 | public BacktrackingIterator iterator() { 86 | return new PageIterator(); 87 | } 88 | 89 | @Override 90 | public int getNumDataPages() { 91 | return numDataPages; 92 | } 93 | 94 | @Override 95 | public int getPartNum() { 96 | return 0; 97 | } 98 | 99 | private class PageIterator extends IndexBacktrackingIterator { 100 | PageIterator() { 101 | super(pageNums.size()); 102 | } 103 | 104 | @Override 105 | protected int getNextNonempty(int currentIndex) { 106 | ++currentIndex; 107 | while (currentIndex < pageNums.size()) { 108 | Page page = getValue(currentIndex); 109 | if (page != null) { 110 | page.unpin(); 111 | break; 112 | } 113 | ++currentIndex; 114 | } 115 | return currentIndex; 116 | } 117 | 118 | @Override 119 | protected Page getValue(int index) { 120 | return bufferManager.fetchPage(new DummyLockContext(), pageNums.get(index), false); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/table/TestRecord.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | 6 | import java.util.Arrays; 7 | 8 | import edu.berkeley.cs186.database.categories.*; 9 | import org.junit.Test; 10 | 11 | import edu.berkeley.cs186.database.common.ByteBuffer; 12 | import edu.berkeley.cs186.database.databox.BoolDataBox; 13 | import edu.berkeley.cs186.database.databox.FloatDataBox; 14 | import edu.berkeley.cs186.database.databox.IntDataBox; 15 | import edu.berkeley.cs186.database.databox.StringDataBox; 16 | import edu.berkeley.cs186.database.databox.Type; 17 | import org.junit.experimental.categories.Category; 18 | 19 | @Category({Proj99Tests.class, SystemTests.class}) 20 | public class TestRecord { 21 | @Test 22 | public void testToAndFromBytes() { 23 | Schema[] schemas = { 24 | new Schema(Arrays.asList("x"), Arrays.asList(Type.boolType())), 25 | new Schema(Arrays.asList("x"), Arrays.asList(Type.intType())), 26 | new Schema(Arrays.asList("x"), Arrays.asList(Type.floatType())), 27 | new Schema(Arrays.asList("x"), Arrays.asList(Type.stringType(3))), 28 | new Schema(Arrays.asList("w", "x", "y", "z"), 29 | Arrays.asList(Type.boolType(), Type.intType(), 30 | Type.floatType(), Type.stringType(3))), 31 | }; 32 | 33 | Record[] records = { 34 | new Record(Arrays.asList(new BoolDataBox(false))), 35 | new Record(Arrays.asList(new IntDataBox(0))), 36 | new Record(Arrays.asList(new FloatDataBox(0f))), 37 | new Record(Arrays.asList(new StringDataBox("foo", 3))), 38 | new Record(Arrays.asList( 39 | new BoolDataBox(false), 40 | new IntDataBox(0), 41 | new FloatDataBox(0f), 42 | new StringDataBox("foo", 3) 43 | )) 44 | }; 45 | 46 | assert(schemas.length == records.length); 47 | for (int i = 0; i < schemas.length; ++i) { 48 | Schema s = schemas[i]; 49 | Record r = records[i]; 50 | assertEquals(r, Record.fromBytes(ByteBuffer.wrap(r.toBytes(s)), s)); 51 | } 52 | } 53 | 54 | @Test 55 | public void testEquals() { 56 | Record a = new Record(Arrays.asList(new BoolDataBox(false))); 57 | Record b = new Record(Arrays.asList(new BoolDataBox(true))); 58 | Record c = new Record(Arrays.asList(new IntDataBox(0))); 59 | 60 | assertEquals(a, a); 61 | assertNotEquals(a, b); 62 | assertNotEquals(a, c); 63 | assertNotEquals(b, a); 64 | assertEquals(b, b); 65 | assertNotEquals(b, c); 66 | assertNotEquals(c, a); 67 | assertNotEquals(c, b); 68 | assertEquals(c, c); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/edu/berkeley/cs186/database/table/TestRecordId.java: -------------------------------------------------------------------------------- 1 | package edu.berkeley.cs186.database.table; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertNotEquals; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import edu.berkeley.cs186.database.categories.*; 8 | import edu.berkeley.cs186.database.common.ByteBuffer; 9 | 10 | import org.junit.Test; 11 | import org.junit.experimental.categories.Category; 12 | 13 | @Category({Proj99Tests.class, SystemTests.class}) 14 | public class TestRecordId { 15 | @Test 16 | public void testSizeInBytes() { 17 | assertEquals(10, RecordId.getSizeInBytes()); 18 | } 19 | 20 | @Test 21 | public void testToAndFromBytes() { 22 | for (int i = 0; i < 10; ++i) { 23 | for (short j = 0; j < 10; ++j) { 24 | RecordId rid = new RecordId(i, j); 25 | assertEquals(rid, RecordId.fromBytes(ByteBuffer.wrap(rid.toBytes()))); 26 | } 27 | } 28 | } 29 | 30 | @Test 31 | public void testEquals() { 32 | RecordId a = new RecordId(0, (short) 0); 33 | RecordId b = new RecordId(1, (short) 0); 34 | RecordId c = new RecordId(0, (short) 1); 35 | 36 | assertEquals(a, a); 37 | assertNotEquals(a, b); 38 | assertNotEquals(a, c); 39 | assertNotEquals(b, a); 40 | assertEquals(b, b); 41 | assertNotEquals(b, c); 42 | assertNotEquals(c, a); 43 | assertNotEquals(c, b); 44 | assertEquals(c, c); 45 | } 46 | 47 | @Test 48 | public void testCompareTo() { 49 | RecordId a = new RecordId(0, (short) 0); 50 | RecordId b = new RecordId(0, (short) 1); 51 | RecordId c = new RecordId(1, (short) 0); 52 | RecordId d = new RecordId(1, (short) 1); 53 | 54 | assertTrue(a.compareTo(a) == 0); 55 | assertTrue(b.compareTo(b) == 0); 56 | assertTrue(c.compareTo(c) == 0); 57 | assertTrue(d.compareTo(d) == 0); 58 | 59 | assertTrue(a.compareTo(b) < 0); 60 | assertTrue(b.compareTo(c) < 0); 61 | assertTrue(c.compareTo(d) < 0); 62 | 63 | assertTrue(d.compareTo(c) > 0); 64 | assertTrue(c.compareTo(b) > 0); 65 | assertTrue(b.compareTo(a) > 0); 66 | } 67 | } 68 | --------------------------------------------------------------------------------