├── rmi ├── META-INF │ └── MANIFEST.MF ├── example-hello.jar ├── example │ └── hello │ │ ├── Hello.java │ │ ├── Client.java │ │ └── Server.java └── README.md ├── simpledb ├── README.md ├── .gitignore ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── app │ ├── src │ │ ├── test │ │ │ └── java │ │ │ │ └── simpledb │ │ │ │ ├── AppTest.java │ │ │ │ ├── materialize │ │ │ │ ├── CountFnTest.java │ │ │ │ ├── MergeJoinScanTest.java │ │ │ │ ├── GroupByScanTest.java │ │ │ │ └── RecordComparatorTest.java │ │ │ │ ├── parse │ │ │ │ ├── LexerTest.java │ │ │ │ └── QueryDataTest.java │ │ │ │ ├── multibuffer │ │ │ │ └── BufferNeedsTest.java │ │ │ │ ├── index │ │ │ │ ├── query │ │ │ │ │ ├── IndexSelectScanTest.java │ │ │ │ │ └── IndexJoinScanTest.java │ │ │ │ └── btree │ │ │ │ │ ├── BTreeDirTest.java │ │ │ │ │ └── BTPageTest.java │ │ │ │ ├── plan │ │ │ │ └── PlannerTest.java │ │ │ │ └── record │ │ │ │ └── TableScanTest.java │ │ └── main │ │ │ └── java │ │ │ └── simpledb │ │ │ ├── parse │ │ │ ├── BadSyntaxException.java │ │ │ ├── CreateTableData.java │ │ │ ├── CreateViewData.java │ │ │ ├── DeleteData.java │ │ │ ├── CreateIndexData.java │ │ │ ├── InsertData.java │ │ │ ├── ModifyData.java │ │ │ ├── QueryData.java │ │ │ └── Lexer.java │ │ │ ├── buffer │ │ │ ├── BufferAbortException.java │ │ │ ├── Buffer.java │ │ │ └── BufferMgr.java │ │ │ ├── tx │ │ │ ├── concurrency │ │ │ │ ├── LockAbortException.java │ │ │ │ ├── ConcurrencyMgr.java │ │ │ │ └── LockTable.java │ │ │ ├── recovery │ │ │ │ ├── CheckpointRecord.java │ │ │ │ ├── StartRecord.java │ │ │ │ ├── CommitRecord.java │ │ │ │ ├── RollbackRecord.java │ │ │ │ ├── LogRecord.java │ │ │ │ ├── SetIntRecord.java │ │ │ │ ├── SetStringRecord.java │ │ │ │ └── RecoveryMgr.java │ │ │ ├── BufferList.java │ │ │ └── Transaction.java │ │ │ ├── plan │ │ │ ├── QueryPlanner.java │ │ │ ├── UpdatePlanner.java │ │ │ ├── Plan.java │ │ │ ├── ProjectPlan.java │ │ │ ├── TablePlan.java │ │ │ ├── SelectPlan.java │ │ │ ├── BasicQueryPlanner.java │ │ │ ├── ProductPlan.java │ │ │ ├── Planner.java │ │ │ └── BasicUpdatePlanner.java │ │ │ ├── materialize │ │ │ ├── AggregationFn.java │ │ │ ├── CountFn.java │ │ │ ├── MaxFn.java │ │ │ ├── RecordComparator.java │ │ │ ├── TempTable.java │ │ │ ├── GroupValue.java │ │ │ ├── GroupByPlan.java │ │ │ ├── MergeJoinPlan.java │ │ │ ├── GroupByScan.java │ │ │ ├── MaterializePlan.java │ │ │ ├── MergeJoinScan.java │ │ │ └── SortScan.java │ │ │ ├── jdbc │ │ │ ├── network │ │ │ │ ├── RemoteDriver.java │ │ │ │ ├── RemoteConnection.java │ │ │ │ ├── RemoteDriverImpl.java │ │ │ │ ├── RemoteStatement.java │ │ │ │ ├── RemoteMetaData.java │ │ │ │ ├── RemoteResultSet.java │ │ │ │ ├── NetworkConnection.java │ │ │ │ ├── NetworkStatement.java │ │ │ │ ├── NetworkMetaData.java │ │ │ │ ├── NetworkDriver.java │ │ │ │ ├── RemoteConnectionImpl.java │ │ │ │ ├── NetworkResultSet.java │ │ │ │ ├── RemoteStatementImpl.java │ │ │ │ ├── RemoteMetaDataImpl.java │ │ │ │ └── RemoteResultSetImpl.java │ │ │ ├── embedded │ │ │ │ ├── EmbeddedDriver.java │ │ │ │ ├── EmbeddedMetaData.java │ │ │ │ ├── EmbeddedConnection.java │ │ │ │ ├── EmbeddedStatement.java │ │ │ │ └── EmbeddedResultSet.java │ │ │ └── DriverAdapter.java │ │ │ ├── index │ │ │ ├── btree │ │ │ │ ├── DirEntry.java │ │ │ │ └── BTreeDir.java │ │ │ ├── Index.java │ │ │ ├── planner │ │ │ │ ├── IndexSelectPlan.java │ │ │ │ └── IndexJoinPlan.java │ │ │ └── query │ │ │ │ ├── IndexSelectScan.java │ │ │ │ └── IndexJoinScan.java │ │ │ ├── query │ │ │ ├── UpdateScan.java │ │ │ ├── Constant.java │ │ │ ├── Scan.java │ │ │ ├── Expression.java │ │ │ ├── ProjectScan.java │ │ │ ├── ProductScan.java │ │ │ ├── SelectScan.java │ │ │ ├── Predicate.java │ │ │ └── Term.java │ │ │ ├── server │ │ │ ├── StartServer.java │ │ │ └── SimpleDB.java │ │ │ ├── record │ │ │ ├── RID.java │ │ │ ├── Layout.java │ │ │ └── Schema.java │ │ │ ├── file │ │ │ ├── BlockId.java │ │ │ ├── Page.java │ │ │ └── FileMgr.java │ │ │ ├── metadata │ │ │ ├── StatInfo.java │ │ │ ├── ViewMgr.java │ │ │ ├── MetadataMgr.java │ │ │ ├── StatMgr.java │ │ │ ├── IndexInfo.java │ │ │ └── IndexMgr.java │ │ │ ├── multibuffer │ │ │ ├── BufferNeeds.java │ │ │ ├── ChunkScan.java │ │ │ ├── MultibufferProductPlan.java │ │ │ └── MultibufferProductScan.java │ │ │ ├── log │ │ │ ├── LogIterator.java │ │ │ └── LogMgr.java │ │ │ └── client │ │ │ └── network │ │ │ ├── JdbcEmbeddedDriverExample.java │ │ │ └── JdbcNetworkDriverExample.java │ └── build.gradle.kts ├── .gitattributes ├── settings.gradle.kts ├── docs │ └── 00-initialize-project.md └── gradlew.bat ├── docs └── 01-database-systems │ ├── 04-simpledb │ ├── simpleij.png │ ├── startserver.png │ └── README.md │ └── 02-derbydatabase-system │ ├── loadAIRLINES.sql │ ├── loadTables.sql │ ├── README.md │ └── derby.log ├── .gitignore ├── .github └── workflows │ └── test.yml └── README.md /rmi/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Main-Class: example.hello.Server 2 | -------------------------------------------------------------------------------- /simpledb/README.md: -------------------------------------------------------------------------------- 1 | # simpledb 2 | 3 | ![](docs/simpledb.drawio.svg) 4 | -------------------------------------------------------------------------------- /rmi/example-hello.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakamasato/database-design-and-implementation/HEAD/rmi/example-hello.jar -------------------------------------------------------------------------------- /simpledb/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | -------------------------------------------------------------------------------- /simpledb/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakamasato/database-design-and-implementation/HEAD/simpledb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /docs/01-database-systems/04-simpledb/simpleij.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakamasato/database-design-and-implementation/HEAD/docs/01-database-systems/04-simpledb/simpleij.png -------------------------------------------------------------------------------- /docs/01-database-systems/04-simpledb/startserver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nakamasato/database-design-and-implementation/HEAD/docs/01-database-systems/04-simpledb/startserver.png -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/AppTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This Java source file was generated by the Gradle 'init' task. 3 | */ 4 | package simpledb; 5 | 6 | class AppTest { 7 | } 8 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/BadSyntaxException.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | @SuppressWarnings("serial") 4 | public class BadSyntaxException extends RuntimeException { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/buffer/BufferAbortException.java: -------------------------------------------------------------------------------- 1 | package simpledb.buffer; 2 | 3 | @SuppressWarnings("serial") 4 | public class BufferAbortException extends RuntimeException { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/concurrency/LockAbortException.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.concurrency; 2 | 3 | @SuppressWarnings("serial") 4 | public class LockAbortException extends RuntimeException { 5 | } 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | firstdb 2 | 3 | # Ignore Gradle project-specific cache directory 4 | .gradle 5 | 6 | # Ignore Gradle build output directory 7 | build 8 | 9 | simpledb/app/bin 10 | datadir 11 | .vscode 12 | .class 13 | -------------------------------------------------------------------------------- /simpledb/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /simpledb/.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # Linux start script should use lf 5 | /gradlew text eol=lf 6 | 7 | # These are Windows script files and should use crlf 8 | *.bat text eol=crlf 9 | 10 | -------------------------------------------------------------------------------- /rmi/example/hello/Hello.java: -------------------------------------------------------------------------------- 1 | package example.hello; 2 | 3 | import java.io.Serializable; 4 | import java.rmi.Remote; 5 | import java.rmi.RemoteException; 6 | 7 | public interface Hello extends Remote, Serializable { 8 | String sayHello() throws RemoteException; 9 | } 10 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/QueryPlanner.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import simpledb.parse.QueryData; 4 | import simpledb.tx.Transaction; 5 | 6 | /* 7 | * Interface implemented by planners for 8 | * the SQL select statement. 9 | */ 10 | public interface QueryPlanner { 11 | public Plan createPlan(QueryData data, Transaction tx); 12 | } 13 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/AggregationFn.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import simpledb.query.Constant; 4 | import simpledb.query.Scan; 5 | 6 | /* 7 | * Aggregation function used by groupby operator 8 | */ 9 | public interface AggregationFn { 10 | void processFirst(Scan s); 11 | 12 | void processNext(Scan s); 13 | 14 | String fieldName(); 15 | 16 | Constant value(); 17 | } 18 | -------------------------------------------------------------------------------- /simpledb/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/7.5.1/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "simpledb" 11 | include("app") 12 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteDriver.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.Remote; 4 | import java.rmi.RemoteException; 5 | 6 | /* 7 | * The RMI remote interface corresponding to Driver. 8 | * The method is similar to that of Driver, 9 | * except that it takes no arguments and 10 | * throws RemoteException instead of SQLException 11 | */ 12 | public interface RemoteDriver extends Remote { 13 | public RemoteConnection connect() throws RemoteException; 14 | } 15 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/CreateTableData.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | import simpledb.record.Schema; 4 | 5 | public class CreateTableData { 6 | private String tblname; 7 | private Schema sch; 8 | 9 | public CreateTableData(String tblname, Schema sch) { 10 | this.tblname = tblname; 11 | this.sch = sch; 12 | } 13 | 14 | public String tableName() { 15 | return tblname; 16 | } 17 | 18 | public Schema newSchema() { 19 | return sch; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/CreateViewData.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | /* 4 | * Data for the SQL create view statement 5 | */ 6 | public class CreateViewData { 7 | private String viewname; 8 | private QueryData qrydata; 9 | 10 | public CreateViewData(String viewname, QueryData qrydata) { 11 | this.viewname = viewname; 12 | this.qrydata = qrydata; 13 | } 14 | 15 | public String viewName() { 16 | return viewname; 17 | } 18 | 19 | public String viewDef() { 20 | return qrydata.toString(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/DeleteData.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | import simpledb.query.Predicate; 4 | 5 | /* 6 | * Data for the SQL delete statement 7 | */ 8 | public class DeleteData { 9 | private String tblname; 10 | private Predicate pred; 11 | 12 | public DeleteData(String tblname, Predicate pred) { 13 | this.tblname = tblname; 14 | this.pred = pred; 15 | } 16 | 17 | public String tableName() { 18 | return tblname; 19 | } 20 | 21 | public Predicate pred() { 22 | return pred; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteConnection.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.Remote; 4 | import java.rmi.RemoteException; 5 | 6 | /* 7 | * The RMI remote interface corresponding to Connection. 8 | * The methods are identical to those of Connection, 9 | * except that they throw RemoteException instead of SQLException. 10 | */ 11 | public interface RemoteConnection extends Remote { 12 | public RemoteStatement createStatement() throws RemoteException; 13 | 14 | public void close() throws RemoteException; 15 | } 16 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/embedded/EmbeddedDriver.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.embedded; 2 | 3 | import java.sql.SQLException; 4 | import java.util.Properties; 5 | 6 | import simpledb.jdbc.DriverAdapter; 7 | import simpledb.server.SimpleDB; 8 | 9 | public class EmbeddedDriver extends DriverAdapter { 10 | 11 | public EmbeddedConnection connect(String url, Properties p) throws SQLException { 12 | String dbname = url.replace("jdbc:simpledb:", ""); 13 | SimpleDB db = new SimpleDB(dbname); 14 | return new EmbeddedConnection(db); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/materialize/CountFnTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import simpledb.query.Constant; 8 | 9 | public class CountFnTest { 10 | 11 | @Test 12 | public void testCountFn() { 13 | AggregationFn fn = new CountFn("fld"); 14 | assertEquals("countoffld", fn.fieldName()); 15 | fn.processFirst(null); 16 | assertEquals(new Constant(1), fn.value()); 17 | fn.processNext(null); 18 | assertEquals(new Constant(2), fn.value()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteDriverImpl.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.RemoteException; 4 | import java.rmi.server.UnicastRemoteObject; 5 | 6 | import simpledb.server.SimpleDB; 7 | 8 | @SuppressWarnings("serial") 9 | public class RemoteDriverImpl extends UnicastRemoteObject implements RemoteDriver { 10 | private SimpleDB db; 11 | 12 | public RemoteDriverImpl(SimpleDB db) throws RemoteException { 13 | this.db = db; 14 | } 15 | 16 | public RemoteConnection connect() throws RemoteException { 17 | return new RemoteConnectionImpl(db); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteStatement.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.Remote; 4 | import java.rmi.RemoteException; 5 | 6 | /* 7 | * The RMI remote interface corresponding to Statement. 8 | * The methods are identical to those of Statement, 9 | * except that they throw RemoteException instead of SQLException. 10 | */ 11 | public interface RemoteStatement extends Remote { 12 | public RemoteResultSet executeQuery(String qry) throws RemoteException; 13 | 14 | public int executeUpdate(String cmd) throws RemoteException; 15 | 16 | public void close() throws RemoteException; 17 | } 18 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/CreateIndexData.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | /* 4 | * Data for the SQL create index statement 5 | */ 6 | public class CreateIndexData { 7 | private String idxname; 8 | private String tblname; 9 | private String fldname; 10 | 11 | public CreateIndexData(String idxname, String tblname, String fldname) { 12 | this.idxname = idxname; 13 | this.tblname = tblname; 14 | this.fldname = fldname; 15 | } 16 | 17 | public String indexName() { 18 | return idxname; 19 | } 20 | 21 | public String tableName() { 22 | return tblname; 23 | } 24 | 25 | public String fieldName() { 26 | return fldname; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /rmi/example/hello/Client.java: -------------------------------------------------------------------------------- 1 | package example.hello; 2 | 3 | import java.rmi.registry.LocateRegistry; 4 | import java.rmi.registry.Registry; 5 | 6 | public class Client { 7 | private Client() { 8 | } 9 | 10 | public static void main(String args[]) { 11 | String host = (args.length < 1) ? null : args[0]; 12 | try { 13 | Registry registry = LocateRegistry.getRegistry(host); 14 | Hello stub = (Hello) registry.lookup("Hello"); 15 | String response = stub.sayHello(); 16 | System.out.println("response:" + response); 17 | } catch (Exception e) { 18 | System.err.println("Client exception: " + e.toString()); 19 | e.printStackTrace(); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/index/btree/DirEntry.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.btree; 2 | 3 | import simpledb.query.Constant; 4 | 5 | /* 6 | * A directory entry has two components: 7 | * 1. the block number of the child block 8 | * 2. the dataval of the first record in the block 9 | */ 10 | public class DirEntry { 11 | private Constant dataval; // dataval of the first record 12 | private int blocknum; // child block 13 | 14 | public DirEntry(Constant dataval, int blocknum) { 15 | this.dataval = dataval; 16 | this.blocknum = blocknum; 17 | } 18 | 19 | public Constant dataVal() { 20 | return dataval; 21 | } 22 | 23 | public int blockNumber() { 24 | return blocknum; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteMetaData.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.Remote; 4 | import java.rmi.RemoteException; 5 | 6 | /* 7 | * The RMI remote interface corresponding to ResultSetMetaData. 8 | * The methods are identical to those of ResultSetMetaData, 9 | * except that they throw RemoteException instead of SQLException. 10 | */ 11 | public interface RemoteMetaData extends Remote { 12 | public int getColumnCount() throws RemoteException; 13 | 14 | public String getColumnName(int column) throws RemoteException; 15 | 16 | public int getColumnType(int column) throws RemoteException; 17 | 18 | public int getColumnDisplaySize(int column) throws RemoteException; 19 | } 20 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/CountFn.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import simpledb.query.Constant; 4 | import simpledb.query.Scan; 5 | 6 | public class CountFn implements AggregationFn { 7 | private String fldname; 8 | private int count; 9 | 10 | public CountFn(String fldname) { 11 | this.fldname = fldname; 12 | } 13 | 14 | @Override 15 | public void processFirst(Scan s) { 16 | count = 1; 17 | } 18 | 19 | @Override 20 | public void processNext(Scan s) { 21 | count++; 22 | } 23 | 24 | @Override 25 | public String fieldName() { 26 | return "countof" + fldname; 27 | } 28 | 29 | @Override 30 | public Constant value() { 31 | return new Constant(count); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteResultSet.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.Remote; 4 | import java.rmi.RemoteException; 5 | 6 | /* 7 | * The RMI remote interface corresponding to ResultSet. 8 | * The methods are identical to those of ResultSet, 9 | * except that they throw RemoteException instead of SQLException. 10 | */ 11 | public interface RemoteResultSet extends Remote { 12 | public boolean next() throws RemoteException; 13 | 14 | public int getInt(String fldname) throws RemoteException; 15 | 16 | public String getString(String fldname) throws RemoteException; 17 | 18 | public RemoteMetaData getMetaData() throws RemoteException; 19 | 20 | public void close() throws RemoteException; 21 | } 22 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/InsertData.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | import java.util.List; 4 | 5 | import simpledb.query.Constant; 6 | 7 | public class InsertData { 8 | private String tblname; 9 | private List flds; 10 | private List vals; 11 | 12 | /* 13 | * Insert one record 14 | * the length of flds and vals must be the same 15 | */ 16 | public InsertData(String tblname, List flds, List vals) { 17 | this.tblname = tblname; 18 | this.flds = flds; 19 | this.vals = vals; 20 | } 21 | 22 | public String tableName() { 23 | return tblname; 24 | } 25 | 26 | public List fields() { 27 | return flds; 28 | } 29 | 30 | public List vals() { 31 | return vals; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/UpdateScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | import simpledb.record.RID; 4 | 5 | /* 6 | * The interface will be implemented by all updatable scans. 7 | */ 8 | public interface UpdateScan extends Scan { 9 | 10 | /* 11 | * Modify the field value of the current record. 12 | */ 13 | public void setVal(String fldname, Constant val); 14 | 15 | /* 16 | * Modify the field value of the current record. 17 | */ 18 | public void setInt(String fldname, int val); 19 | 20 | /* 21 | * Modify the field value of the current record. 22 | */ 23 | public void setString(String fldname, String val); 24 | 25 | public void insert(); 26 | 27 | public void delete(); 28 | 29 | public RID getRid(); 30 | 31 | public void moveToRid(RID rid); 32 | } 33 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/server/StartServer.java: -------------------------------------------------------------------------------- 1 | package simpledb.server; 2 | 3 | import java.rmi.registry.LocateRegistry; 4 | import java.rmi.registry.Registry; 5 | 6 | import simpledb.jdbc.network.RemoteDriver; 7 | import simpledb.jdbc.network.RemoteDriverImpl; 8 | 9 | public class StartServer { 10 | 11 | public static void main(String args[]) throws Exception { 12 | // Init SimpleDB 13 | String dirname = (args.length == 0) ? "datadir" : args[0]; 14 | SimpleDB db = new SimpleDB(dirname); 15 | 16 | // Create RMI registry 17 | Registry reg = LocateRegistry.createRegistry(1099); 18 | 19 | // Post the server entry 20 | RemoteDriver d = new RemoteDriverImpl(db); 21 | reg.rebind("simpledb", d); 22 | 23 | System.out.println("database server's ready"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/record/RID.java: -------------------------------------------------------------------------------- 1 | package simpledb.record; 2 | 3 | /* 4 | * An identifier of a record within a file. 5 | * A RID consists of the block number of the file and 6 | * the location of the record in the block. 7 | */ 8 | public class RID { 9 | private int blknum; 10 | private int slot; 11 | 12 | public RID(int blknum, int slot) { 13 | this.blknum = blknum; 14 | this.slot = slot; 15 | } 16 | 17 | public int blockNumber() { 18 | return blknum; 19 | } 20 | 21 | public int slot() { 22 | return slot; 23 | } 24 | 25 | public boolean equals(Object obj) { 26 | RID r = (RID) obj; 27 | return r != null && blknum == r.blknum && slot == r.slot; 28 | } 29 | 30 | public String toString() { 31 | return "[" + blknum + ", " + slot + "]"; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/MaxFn.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import simpledb.query.Constant; 4 | import simpledb.query.Scan; 5 | 6 | public class MaxFn implements AggregationFn { 7 | private String fldname; 8 | private Constant val; 9 | 10 | public MaxFn(String fldname) { 11 | this.fldname = fldname; 12 | } 13 | 14 | @Override 15 | public void processFirst(Scan s) { 16 | val = s.getVal(fldname); 17 | } 18 | 19 | @Override 20 | public void processNext(Scan s) { 21 | Constant newval = s.getVal(fldname); 22 | if (val.compareTo(newval) > 0) 23 | val = newval; 24 | } 25 | 26 | @Override 27 | public String fieldName() { 28 | return "maxof" + fldname; 29 | } 30 | 31 | @Override 32 | public Constant value() { 33 | return val; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/recovery/CheckpointRecord.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.recovery; 2 | 3 | import simpledb.file.Page; 4 | import simpledb.log.LogMgr; 5 | import simpledb.tx.Transaction; 6 | 7 | public class CheckpointRecord implements LogRecord { 8 | 9 | public int op() { 10 | return CHECKPOINT; 11 | } 12 | 13 | public int txNumber() { 14 | return -1; 15 | } 16 | 17 | public void undo(Transaction tx) { 18 | // Do nothing. because a checkpoint record 19 | // contains no undo information. 20 | } 21 | 22 | public String toString() { 23 | return ""; 24 | } 25 | 26 | public static int writeToLog(LogMgr lm) { 27 | byte[] rec = new byte[Integer.BYTES]; 28 | Page p = new Page(rec); 29 | p.setInt(0, CHECKPOINT); 30 | return lm.append(rec); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | 12 | - name: setup java 13 | uses: actions/setup-java@v3 14 | with: 15 | distribution: 'temurin' 16 | java-version: '18' 17 | cache: 'gradle' 18 | 19 | - name: setup gradle 20 | uses: gradle/gradle-build-action@v2 21 | 22 | - name: test 23 | working-directory: simpledb 24 | run: ./gradlew test 25 | 26 | - name: run 27 | working-directory: simpledb 28 | run: ./gradlew run 29 | 30 | - name: embeddedclient 31 | working-directory: simpledb 32 | run: | 33 | rm -rf app/datadir 34 | ./gradlew embeddedclient 35 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/file/BlockId.java: -------------------------------------------------------------------------------- 1 | package simpledb.file; 2 | 3 | public class BlockId { 4 | private String filename; 5 | private int blknum; 6 | 7 | public BlockId(String filename, int blknum) { 8 | this.filename = filename; 9 | this.blknum = blknum; 10 | } 11 | 12 | public String fileName() { 13 | return filename; 14 | } 15 | 16 | public int number() { 17 | return blknum; 18 | } 19 | 20 | public boolean equals(Object obj) { 21 | BlockId blk = (BlockId) obj; 22 | if (blk == null) 23 | return false; 24 | return filename.equals(blk.fileName()) && blknum == blk.number(); 25 | } 26 | 27 | public String toString() { 28 | return "[file " + filename + ", block " + blknum + "]"; 29 | } 30 | 31 | public int hashCode() { 32 | return toString().hashCode(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/NetworkConnection.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.sql.SQLException; 4 | import java.sql.Statement; 5 | 6 | import simpledb.jdbc.ConnectionAdapter; 7 | 8 | public class NetworkConnection extends ConnectionAdapter { 9 | private RemoteConnection rconn; 10 | 11 | public NetworkConnection(RemoteConnection c) { 12 | rconn = c; 13 | } 14 | 15 | public Statement createStatement() throws SQLException { 16 | try { 17 | RemoteStatement rstmt = rconn.createStatement(); 18 | return new NetworkStatement(rstmt); 19 | } catch (Exception e) { 20 | throw new SQLException(e); 21 | } 22 | } 23 | 24 | public void close() throws SQLException { 25 | try { 26 | rconn.close(); 27 | } catch (Exception e) { 28 | throw new SQLException(e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/ModifyData.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | import simpledb.query.Expression; 4 | import simpledb.query.Predicate; 5 | 6 | /* 7 | * Data for the SQL update statement 8 | */ 9 | public class ModifyData { 10 | private String tblname; 11 | private String fldname; 12 | private Expression newval; 13 | private Predicate pred; 14 | 15 | public ModifyData(String tblname, String fldname, Expression newval, Predicate pred) { 16 | this.tblname = tblname; 17 | this.fldname = fldname; 18 | this.newval = newval; 19 | this.pred = pred; 20 | } 21 | 22 | public String tableName() { 23 | return tblname; 24 | } 25 | 26 | public String targetField() { 27 | return fldname; 28 | } 29 | 30 | public Expression newValue() { 31 | return newval; 32 | } 33 | 34 | public Predicate pred() { 35 | return pred; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rmi/example/hello/Server.java: -------------------------------------------------------------------------------- 1 | package example.hello; 2 | 3 | import java.rmi.RemoteException; 4 | import java.rmi.registry.LocateRegistry; 5 | import java.rmi.registry.Registry; 6 | import java.rmi.server.UnicastRemoteObject; 7 | 8 | public class Server implements Hello { 9 | public Server() { 10 | } 11 | 12 | public static void main(String args[]) { 13 | try { 14 | Server obj = new Server(); 15 | Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 0); 16 | 17 | Registry registry = LocateRegistry.createRegistry(1099); 18 | registry.bind("Hello", stub); 19 | System.out.println("Server ready"); 20 | } catch (Exception e) { 21 | System.err.println("Server exception: " + e.toString()); 22 | e.printStackTrace(); 23 | } 24 | } 25 | 26 | @Override 27 | public String sayHello() throws RemoteException { 28 | return "Hello, World!"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/RecordComparator.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import java.util.Comparator; 4 | import java.util.List; 5 | 6 | import simpledb.query.Constant; 7 | import simpledb.query.Scan; 8 | 9 | /* 10 | * A comparator for Scans with the specified fields 11 | */ 12 | public class RecordComparator implements Comparator { 13 | private List fields; 14 | 15 | public RecordComparator(List fields) { 16 | this.fields = fields; 17 | } 18 | 19 | /* 20 | * Compare the current records of the two specified scans 21 | */ 22 | @Override 23 | public int compare(Scan s1, Scan s2) { 24 | for (String fldname : fields) { 25 | Constant val1 = s1.getVal(fldname); 26 | Constant val2 = s2.getVal(fldname); 27 | int result = val1.compareTo(val2); 28 | if (result != 0) 29 | return result; 30 | } 31 | return 0; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/UpdatePlanner.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import simpledb.parse.CreateIndexData; 4 | import simpledb.parse.CreateTableData; 5 | import simpledb.parse.CreateViewData; 6 | import simpledb.parse.DeleteData; 7 | import simpledb.parse.InsertData; 8 | import simpledb.parse.ModifyData; 9 | import simpledb.tx.Transaction; 10 | 11 | /* 12 | * Interface implemented by planners for SQL 13 | * insert, delete, and modify statement 14 | */ 15 | public interface UpdatePlanner { 16 | public int executeInsert(InsertData data, Transaction tx); 17 | 18 | public int executeDelete(DeleteData data, Transaction tx); 19 | 20 | public int executeModify(ModifyData data, Transaction tx); 21 | 22 | public int executeCreateTable(CreateTableData data, Transaction tx); 23 | 24 | public int executeCreateView(CreateViewData data, Transaction tx); 25 | 26 | public int executeCreateIndex(CreateIndexData data, Transaction tx); 27 | } 28 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/metadata/StatInfo.java: -------------------------------------------------------------------------------- 1 | package simpledb.metadata; 2 | 3 | /* 4 | * StatInfo stores three pieces of information of a table: 5 | * 1. the number of blocks 6 | * 2. the number of records 7 | * 3. the number of distinct values for each fields (TODO) 8 | */ 9 | public class StatInfo { 10 | private int numBlocks; 11 | private int numRecs; 12 | 13 | public StatInfo(int numBlocks, int numRecs) { 14 | this.numBlocks = numBlocks; 15 | this.numRecs = numRecs; 16 | } 17 | 18 | public int blocksAccessed() { 19 | return numBlocks; 20 | } 21 | 22 | public int recordsOutput() { 23 | return numRecs; 24 | } 25 | 26 | /* 27 | * Return the estimated number of distinct values for the specified fields. 28 | * Current implementation always returns one thirds of the number of records. 29 | */ 30 | public int distinctValues(String fldname) { 31 | // TODO: implement real logic to calculate 32 | return 1 + (numRecs / 3); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/parse/LexerTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | public class LexerTest { 10 | @Test 11 | public void testIdEqualsInt() { 12 | String s = "a = 10"; 13 | Lexer lex = new Lexer(s); 14 | 15 | assertTrue(lex.matchId()); 16 | String x = lex.eatId(); 17 | lex.eatDelim('='); 18 | int y = lex.eatIntConstant(); 19 | assertEquals("a", x); 20 | assertEquals(10, y); 21 | } 22 | 23 | @Test 24 | public void testIntEqualsId() { 25 | String s = "10 = a"; 26 | Lexer lex = new Lexer(s); 27 | 28 | assertFalse(lex.matchId()); 29 | int x = lex.eatIntConstant(); 30 | lex.eatDelim('='); 31 | String y = lex.eatId(); 32 | assertEquals(10, x); 33 | assertEquals("a", y); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/recovery/StartRecord.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.recovery; 2 | 3 | import simpledb.file.Page; 4 | import simpledb.log.LogMgr; 5 | import simpledb.tx.Transaction; 6 | 7 | public class StartRecord implements LogRecord { 8 | private int txnum; 9 | 10 | public StartRecord(Page p) { 11 | int tpos = Integer.BYTES; 12 | txnum = p.getInt(tpos); 13 | } 14 | 15 | public int op() { 16 | return START; 17 | } 18 | 19 | public int txNumber() { 20 | return txnum; 21 | } 22 | 23 | public void undo(Transaction tx) { 24 | // Do nothing. because a start record 25 | // contains no undo information. 26 | } 27 | 28 | public String toString() { 29 | return ""; 30 | } 31 | 32 | public static int writeToLog(LogMgr lm, int txnum) { 33 | byte[] rec = new byte[2 * Integer.BYTES]; 34 | Page p = new Page(rec); 35 | p.setInt(0, START); 36 | p.setInt(Integer.BYTES, txnum); 37 | return lm.append(rec); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/recovery/CommitRecord.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.recovery; 2 | 3 | import simpledb.file.Page; 4 | import simpledb.log.LogMgr; 5 | import simpledb.tx.Transaction; 6 | 7 | public class CommitRecord implements LogRecord { 8 | private int txnum; 9 | 10 | public CommitRecord(Page p) { 11 | int tpos = Integer.BYTES; 12 | txnum = p.getInt(tpos); 13 | } 14 | 15 | public int op() { 16 | return COMMIT; 17 | } 18 | 19 | public int txNumber() { 20 | return txnum; 21 | } 22 | 23 | public void undo(Transaction tx) { 24 | // Do nothing. because a commit record 25 | // contains no undo information. 26 | } 27 | 28 | public String toString() { 29 | return ""; 30 | } 31 | 32 | public static int writeToLog(LogMgr lm, int txnum) { 33 | byte[] rec = new byte[2 * Integer.BYTES]; 34 | Page p = new Page(rec); 35 | p.setInt(0, COMMIT); 36 | p.setInt(Integer.BYTES, txnum); 37 | return lm.append(rec); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/recovery/RollbackRecord.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.recovery; 2 | 3 | import simpledb.file.Page; 4 | import simpledb.log.LogMgr; 5 | import simpledb.tx.Transaction; 6 | 7 | public class RollbackRecord implements LogRecord { 8 | private int txnum; 9 | 10 | public RollbackRecord(Page p) { 11 | int tpos = Integer.BYTES; 12 | txnum = p.getInt(tpos); 13 | } 14 | 15 | public int op() { 16 | return ROLLBACK; 17 | } 18 | 19 | public int txNumber() { 20 | return txnum; 21 | } 22 | 23 | public void undo(Transaction tx) { 24 | // Do nothing. because a rollback record 25 | // contains no undo information. 26 | } 27 | 28 | public String toString() { 29 | return ""; 30 | } 31 | 32 | public static int writeToLog(LogMgr lm, int txnum) { 33 | byte[] rec = new byte[2 * Integer.BYTES]; 34 | Page p = new Page(rec); 35 | p.setInt(0, ROLLBACK); 36 | p.setInt(Integer.BYTES, txnum); 37 | return lm.append(rec); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/TempTable.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import simpledb.query.UpdateScan; 4 | import simpledb.record.Layout; 5 | import simpledb.record.Schema; 6 | import simpledb.record.TableScan; 7 | import simpledb.tx.Transaction; 8 | 9 | public class TempTable { 10 | private static int nextTableNum = 0; 11 | private Transaction tx; 12 | private String tblname; 13 | private Layout layout; 14 | 15 | public TempTable(Transaction tx, Schema sch) { 16 | this.tx = tx; 17 | tblname = nextTableName(); 18 | System.out.println("[TempTable] " + tblname); 19 | layout = new Layout(sch); 20 | } 21 | 22 | public UpdateScan open() { 23 | return new TableScan(tx, tblname, layout); 24 | } 25 | 26 | public String tableName() { 27 | return tblname; 28 | } 29 | 30 | public Layout getLayout() { 31 | return layout; 32 | } 33 | 34 | private static synchronized String nextTableName() { 35 | nextTableNum++; 36 | return "temp" + nextTableNum; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/embedded/EmbeddedMetaData.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.embedded; 2 | 3 | import static java.sql.Types.INTEGER; 4 | 5 | import java.sql.SQLException; 6 | 7 | import simpledb.jdbc.ResultSetMetaDataAdapter; 8 | import simpledb.record.Schema; 9 | 10 | public class EmbeddedMetaData extends ResultSetMetaDataAdapter { 11 | private Schema sch; 12 | 13 | public EmbeddedMetaData(Schema sch) { 14 | this.sch = sch; 15 | } 16 | 17 | public String getColumnName(int column) throws SQLException { 18 | return sch.fields().get(column - 1); 19 | } 20 | 21 | public int getColumnType(int column) throws SQLException { 22 | String fldname = getColumnName(column); 23 | return sch.type(fldname); 24 | } 25 | 26 | public int getColumnDisplaySize(int column) throws SQLException { 27 | String fldname = getColumnName(column); 28 | int fldtype = sch.type(fldname); 29 | int fldlength = (fldtype == INTEGER) ? 6 : sch.length(fldname); 30 | return Math.max(fldname.length(), fldlength) + 1; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/index/Index.java: -------------------------------------------------------------------------------- 1 | package simpledb.index; 2 | 3 | import simpledb.query.Constant; 4 | import simpledb.record.RID; 5 | 6 | public interface Index { 7 | 8 | /* 9 | * Positions the index before the first record 10 | * having the specified search key 11 | */ 12 | public void beforeFirst(Constant searchkey); 13 | 14 | /* 15 | * Moves the index to the next record having 16 | * the search key specified in the beforeFirst method 17 | */ 18 | public boolean next(); 19 | 20 | /* 21 | * Returns the dataRID value stored in the current index record 22 | */ 23 | public RID getDataRid(); 24 | 25 | /* 26 | * Inserts an index record having the specified dataval and dataRID values. 27 | */ 28 | public void insert(Constant dataval, RID datarid); 29 | 30 | /* 31 | * Deletes the index record having the specified dataval and dataRID values. 32 | */ 33 | public void delete(Constant dataval, RID datarid); 34 | 35 | /* 36 | * Closes the index. 37 | */ 38 | public void close(); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/Constant.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | public class Constant implements Comparable { 4 | private Integer ival = null; 5 | private String sval = null; 6 | 7 | public Constant(Integer ival) { 8 | this.ival = ival; 9 | } 10 | 11 | public Constant(String sval) { 12 | this.sval = sval; 13 | } 14 | 15 | public int asInt() { 16 | return ival; 17 | } 18 | 19 | public String asString() { 20 | return sval; 21 | } 22 | 23 | public boolean equals(Object obj) { 24 | Constant c = (Constant) obj; 25 | if (c == null) 26 | return false; 27 | return (ival != null) ? ival.equals(c.ival) : sval.equals(c.sval); 28 | } 29 | 30 | public int compareTo(Constant c) { 31 | return (ival != null) ? ival.compareTo(c.ival) : sval.compareTo(c.sval); 32 | } 33 | 34 | public int hashCode() { 35 | return (ival != null) ? ival.hashCode() : sval.hashCode(); 36 | } 37 | 38 | public String toString() { 39 | return (ival != null) ? ival.toString() : sval.toString(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/NetworkStatement.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.sql.ResultSet; 4 | import java.sql.SQLException; 5 | 6 | import simpledb.jdbc.StatementAdapter; 7 | 8 | public class NetworkStatement extends StatementAdapter { 9 | private RemoteStatement rstmt; 10 | 11 | public NetworkStatement(RemoteStatement s) { 12 | rstmt = s; 13 | } 14 | 15 | public ResultSet executeQuery(String qry) throws SQLException { 16 | try { 17 | RemoteResultSet rrs = rstmt.executeQuery(qry); 18 | return new NetworkResultSet(rrs); 19 | } catch (Exception e) { 20 | throw new SQLException(e); 21 | } 22 | } 23 | 24 | public int executeUpdate(String cmd) throws SQLException { 25 | try { 26 | return rstmt.executeUpdate(cmd); 27 | } catch (Exception e) { 28 | throw new SQLException(e); 29 | } 30 | } 31 | 32 | public void close() throws SQLException { 33 | try { 34 | rstmt.close(); 35 | } catch (Exception e) { 36 | throw new SQLException(e); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/01-database-systems/02-derbydatabase-system/loadAIRLINES.sql: -------------------------------------------------------------------------------- 1 | -- Licensed to the Apache Software Foundation (ASF) under one or more 2 | -- contributor license agreements. See the NOTICE file distributed with 3 | -- this work for additional information regarding copyright ownership. 4 | -- The ASF licenses this file to You under the Apache License, Version 2.0 5 | -- (the "License"); you may not use this file except in compliance with 6 | -- the License. You may obtain a copy of the License at 7 | -- 8 | -- http://www.apache.org/licenses/LICENSE-2.0 9 | -- 10 | -- Unless required by applicable law or agreed to in writing, software 11 | -- distributed under the License is distributed on an "AS IS" BASIS, 12 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | -- See the License for the specific language governing permissions and 14 | -- limitations under the License. 15 | 16 | insert into AIRLINES values ('AA','Amazonian Airways',0.18,0.03,0.5,1.5,20,10,5) ; 17 | insert into AIRLINES values ('US','Union Standard Airlines',0.19,0.05,0.4,1.6,20,10,5); 18 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/Scan.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | /* 4 | * This interface will be implemented by each query scan. 5 | * There is a Scan class foreach relational algebra operator. 6 | */ 7 | public interface Scan { 8 | 9 | /* 10 | * Position 11 | * A subsequent call to next() will return the first record. 12 | */ 13 | public void beforeFirst(); 14 | 15 | /* 16 | * Move the scan to the next record. 17 | */ 18 | public boolean next(); 19 | 20 | /* 21 | * Return the value of the specified integer field 22 | * in the current record. 23 | */ 24 | public int getInt(String fldname); 25 | 26 | /* 27 | * Return the value of the specified string field 28 | * in the current record. 29 | */ 30 | public String getString(String fldname); 31 | 32 | /* 33 | * Return the value of the specified field in the current record. 34 | */ 35 | public Constant getVal(String fldname); 36 | 37 | public boolean hasField(String fldname); 38 | 39 | /* 40 | * Close the scan and its subscans, if any. 41 | */ 42 | public void close(); 43 | } 44 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/Expression.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | import simpledb.record.Schema; 4 | 5 | /* 6 | * SQL Expression 7 | */ 8 | public class Expression { 9 | private Constant val = null; 10 | private String fldname = null; 11 | 12 | public Expression(Constant val) { 13 | this.val = val; 14 | } 15 | 16 | public Expression(String fldname) { 17 | this.fldname = fldname; 18 | } 19 | 20 | /* 21 | * Get the field value via Scan 22 | */ 23 | public Constant evaluate(Scan s) { 24 | return (val != null) ? val : s.getVal(fldname); 25 | } 26 | 27 | public boolean isFieldName() { 28 | return fldname != null; 29 | } 30 | 31 | public Constant asConstant() { 32 | return val; 33 | } 34 | 35 | public String asFieldName() { 36 | return fldname; 37 | } 38 | 39 | /* 40 | * Check if the schema has the field 41 | */ 42 | public boolean appliesTo(Schema sch) { 43 | return (val != null) || sch.hasField(fldname); 44 | } 45 | 46 | public String toString() { 47 | return (val != null) ? val.toString() : fldname; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/parse/QueryDataTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.Arrays; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import simpledb.query.Expression; 10 | import simpledb.query.Predicate; 11 | import simpledb.query.Term; 12 | 13 | public class QueryDataTest { 14 | @Test 15 | void testToStringWithFieldsAndTables() { 16 | Predicate pred = new Predicate(); 17 | QueryData qd = new QueryData(Arrays.asList("f1", "f2", "f3"), Arrays.asList("tbl1", "tbl2"), pred); 18 | String expectedString = "select f1, f2, f3 from tbl1, tbl2"; 19 | assertEquals(expectedString, qd.toString()); 20 | } 21 | 22 | @Test 23 | void testToStringWithPredicate() { 24 | Term t = new Term(new Expression("f1"), new Expression("f2")); 25 | Predicate pred = new Predicate(t); 26 | QueryData qd = new QueryData(Arrays.asList("f1", "f2", "f3"), Arrays.asList("tbl1", "tbl2"), pred); 27 | String expectedString = "select f1, f2, f3 from tbl1, tbl2 where f1=f2"; 28 | assertEquals(expectedString, qd.toString()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/01-database-systems/04-simpledb/README.md: -------------------------------------------------------------------------------- 1 | # SimpleDB 2 | 3 | Download from http://cs.bc.edu/~sciore/simpledb/ 4 | 5 | ## Setup 6 | 7 | Open the `SimpleDB_3.4` folder with vscode. 8 | 9 | ### Server 10 | 11 | 1. Open `simpledb/server/StartServer.java` 12 | 1. Run 13 | ![](startserver.png) 14 | 15 | You'll see the following message: 16 | ``` 17 | creating new database 18 | transaction 1 committed 19 | database server ready 20 | ``` 21 | `studentdb` directory will be created at the root directly of `SimpleDB_3.4`. 22 | 23 | ### Client 24 | 25 | 1. Open `simpleclient/SimpleIJ.java` 26 | 1. Run 27 | ![](simpleij.png) 28 | You'll see the following message and you can connect with **embedded mode**: 29 | ``` 30 | Connect> 31 | jdbc:simpledb:test_db 32 | creating new database 33 | transaction 1 committed 34 | 35 | SQL> 36 | ``` 37 | 38 | or you can connect with **network mode**: (never tried) 39 | 40 | ``` 41 | jdbc:simpledb://localhost/test_db 42 | ``` 43 | 44 | ## References 45 | 46 | 1. https://github.com/SixingYan/SimpleDB_3.00 47 | 1. https://github.com/chaoyangnz/simpledb 48 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/recovery/LogRecord.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.recovery; 2 | 3 | import simpledb.file.Page; 4 | import simpledb.tx.Transaction; 5 | 6 | public interface LogRecord { 7 | static final int CHECKPOINT = 0; 8 | static final int START = 1; 9 | static final int COMMIT = 2; 10 | static final int ROLLBACK = 3; 11 | static final int SETINT = 4; 12 | static final int SETSTRING = 5; 13 | 14 | int op(); 15 | 16 | int txNumber(); 17 | 18 | void undo(Transaction tx); 19 | 20 | static LogRecord createLogRecord(byte[] bytes) { 21 | Page p = new Page(bytes); 22 | switch (p.getInt(0)) { 23 | case CHECKPOINT: 24 | return new CheckpointRecord(); 25 | case COMMIT: 26 | return new CommitRecord(p); 27 | case ROLLBACK: 28 | return new RollbackRecord(p); 29 | case SETINT: 30 | return new SetIntRecord(p); 31 | case SETSTRING: 32 | return new SetStringRecord(p); 33 | case START: 34 | return new StartRecord(p); 35 | default: 36 | System.out.println("LogRecord p.getInt: " + p.getInt(0)); 37 | return null; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/embedded/EmbeddedConnection.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.embedded; 2 | 3 | import java.sql.SQLException; 4 | 5 | import simpledb.jdbc.ConnectionAdapter; 6 | import simpledb.plan.Planner; 7 | import simpledb.server.SimpleDB; 8 | import simpledb.tx.Transaction; 9 | 10 | public class EmbeddedConnection extends ConnectionAdapter { 11 | private SimpleDB db; 12 | private Transaction currentTx; 13 | private Planner planner; 14 | 15 | public EmbeddedConnection(SimpleDB db) { 16 | this.db = db; 17 | currentTx = db.newTx(); 18 | planner = db.planner(); 19 | } 20 | 21 | public EmbeddedStatement createStatement() throws SQLException { 22 | return new EmbeddedStatement(this, planner); 23 | } 24 | 25 | public void close() throws SQLException { 26 | currentTx.commit(); 27 | } 28 | 29 | public void commit() throws SQLException { 30 | currentTx.commit(); 31 | currentTx = db.newTx(); 32 | } 33 | 34 | public void rollback() throws SQLException { 35 | currentTx.rollback(); 36 | currentTx = db.newTx(); 37 | } 38 | 39 | Transaction getTransaction() { 40 | return currentTx; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /docs/01-database-systems/02-derbydatabase-system/loadTables.sql: -------------------------------------------------------------------------------- 1 | -- Licensed to the Apache Software Foundation (ASF) under one or more 2 | -- contributor license agreements. See the NOTICE file distributed with 3 | -- this work for additional information regarding copyright ownership. 4 | -- The ASF licenses this file to You under the Apache License, Version 2.0 5 | -- (the "License"); you may not use this file except in compliance with 6 | -- the License. You may obtain a copy of the License at 7 | -- 8 | -- http://www.apache.org/licenses/LICENSE-2.0 9 | -- 10 | -- Unless required by applicable law or agreed to in writing, software 11 | -- distributed under the License is distributed on an "AS IS" BASIS, 12 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | -- See the License for the specific language governing permissions and 14 | -- limitations under the License. 15 | 16 | run 'loadCOUNTRIES.sql'; 17 | 18 | run 'loadCITIES.sql'; 19 | 20 | run 'loadAIRLINES.sql'; 21 | 22 | run 'loadFLIGHTS1.sql'; 23 | 24 | run 'loadFLIGHTS2.sql'; 25 | 26 | run 'loadFLIGHTAVAILABILITY1.sql'; 27 | 28 | run 'loadFLIGHTAVAILABILITY2.sql'; 29 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/BufferList.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | import simpledb.buffer.Buffer; 9 | import simpledb.buffer.BufferMgr; 10 | import simpledb.file.BlockId; 11 | 12 | public class BufferList { 13 | private Map buffers = new HashMap<>(); 14 | private List pins = new ArrayList<>(); 15 | private BufferMgr bm; 16 | 17 | public BufferList(BufferMgr bm) { 18 | this.bm = bm; 19 | } 20 | 21 | Buffer getBuffer(BlockId blk) { 22 | return buffers.get(blk); 23 | } 24 | 25 | void pin(BlockId blk) { 26 | Buffer buff = bm.pin(blk); 27 | buffers.put(blk, buff); 28 | pins.add(blk); 29 | } 30 | 31 | void unpin(BlockId blk) { 32 | Buffer buff = buffers.get(blk); 33 | bm.unpin(buff); 34 | pins.remove(blk); 35 | if (!pins.contains(blk)) 36 | buffers.remove(blk); 37 | } 38 | 39 | void unpinAll() { 40 | for (BlockId blk : pins) { 41 | Buffer buff = buffers.get(blk); 42 | bm.unpin(buff); 43 | } 44 | buffers.clear(); 45 | pins.clear(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docs/01-database-systems/02-derbydatabase-system/README.md: -------------------------------------------------------------------------------- 1 | # [Derby](https://db.apache.org/derby/) 2 | 3 | 1. Download from https://db.apache.org/derby/releases/release-10_16_1_1.cgi 4 | 1. Set DERBY_HOME 5 | ``` 6 | DERBY_HOME=~/Downloads/db-derby-10.16.1.1-bin 7 | ``` 8 | 1. Copy sql 9 | ``` 10 | cp ~/Downloads/db-derby-10.16.1.1-bin/demo/programs/toursdb/*.sql . 11 | ``` 12 | 1. Run `ij`. 13 | ``` 14 | java -jar $DERBY_HOME/lib/derbyrun.jar ij 15 | ``` 16 | 1. Connect to the database (embeded) 17 | ``` 18 | ij> CONNECT 'jdbc:derby:firstdb;create=true'; 19 | ``` 20 | 21 | Check `derby.log` and `firstdb` dir: 22 | 23 | ``` 24 | firstdb 25 | ├── README_DO_NOT_TOUCH_FILES.txt 26 | ├── log 27 | ├── seg0 28 | └── service.properties 29 | ``` 30 | 1. Run SQL 31 | 32 | ```sql 33 | CREATE TABLE FIRSTTABLE 34 | (ID INT PRIMARY KEY, 35 | NAME VARCHAR(12)); 36 | INSERT INTO FIRSTTABLE VALUES 37 | (10,'TEN'),(20,'TWENTY'),(30,'THIRTY'); 38 | SELECT * FROM FIRSTTABLE; 39 | SELECT * FROM FIRSTTABLE WHERE ID=20; 40 | ``` 41 | 1. Disconnect 42 | ```sql 43 | disconnect; 44 | exit; 45 | ``` 46 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/QueryData.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | import java.util.Collection; 4 | import java.util.List; 5 | 6 | import simpledb.query.Predicate; 7 | 8 | /* 9 | * Data for the SQL select statement: 10 | * select from where 11 | */ 12 | public class QueryData { 13 | private List fields; 14 | private Collection tables; 15 | private Predicate pred; 16 | 17 | public QueryData(List fields, Collection tables, Predicate pred) { 18 | this.fields = fields; 19 | this.tables = tables; 20 | this.pred = pred; 21 | } 22 | 23 | public List fields() { 24 | return fields; 25 | } 26 | 27 | public Collection tables() { 28 | return tables; 29 | } 30 | 31 | public Predicate predicate() { 32 | return pred; 33 | } 34 | 35 | public String toString() { 36 | String result = "select "; 37 | // fields 38 | result += String.join(", ", fields()); 39 | result += " from "; 40 | // tables 41 | result += String.join(", ", tables()); 42 | // where clause 43 | if (!pred.isEmpty()) 44 | result += " where " + pred.toString(); 45 | return result; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/Plan.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import simpledb.query.Scan; 4 | import simpledb.record.Schema; 5 | 6 | /* 7 | * The interface implemented by each query plan. 8 | * There is a Plan class for each relational algebra operator. 9 | */ 10 | public interface Plan { 11 | /* 12 | * Open a scan corresponding to this plan 13 | */ 14 | public Scan open(); 15 | 16 | /* 17 | * The estimated number of block accesses 18 | * that will occur when the scan is executed. 19 | * This value is used to calculate the estimated cost of the plan 20 | */ 21 | public int blockAccessed(); 22 | 23 | /* 24 | * The estimated number of output records. 25 | * This value is used to calculate the estimated cost of the plan 26 | */ 27 | public int recordsOutput(); 28 | 29 | /* 30 | * The estimated number of distinct records for the specified field 31 | * This value is used to calculate the estimated cost of the plan 32 | */ 33 | public int distinctValues(String fldname); 34 | 35 | /* 36 | * The estimated cost for preprocessing 37 | */ 38 | public int preprocessingCost(); 39 | 40 | /* 41 | * Schema of output table 42 | */ 43 | public Schema schema(); 44 | } 45 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/NetworkMetaData.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.sql.SQLException; 4 | 5 | import simpledb.jdbc.ResultSetMetaDataAdapter; 6 | 7 | public class NetworkMetaData extends ResultSetMetaDataAdapter { 8 | private RemoteMetaData rmd; 9 | 10 | public NetworkMetaData(RemoteMetaData md) { 11 | rmd = md; 12 | } 13 | 14 | public int getColumnCount() throws SQLException { 15 | try { 16 | return rmd.getColumnCount(); 17 | } catch (Exception e) { 18 | throw new SQLException(e); 19 | } 20 | } 21 | 22 | public String getColumnName(int column) throws SQLException { 23 | try { 24 | return rmd.getColumnName(column); 25 | } catch (Exception e) { 26 | throw new SQLException(e); 27 | } 28 | } 29 | 30 | public int getColumnType(int column) throws SQLException { 31 | try { 32 | return rmd.getColumnType(column); 33 | } catch (Exception e) { 34 | throw new SQLException(e); 35 | } 36 | } 37 | 38 | public int getColumnDisplaySize(int column) throws SQLException { 39 | try { 40 | return rmd.getColumnDisplaySize(column); 41 | } catch (Exception e) { 42 | throw new SQLException(e); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/ProjectPlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import java.util.List; 4 | 5 | import simpledb.query.ProjectScan; 6 | import simpledb.query.Scan; 7 | import simpledb.record.Schema; 8 | 9 | /* 10 | * Plan class corresponding to the project 11 | * relational algebra operator 12 | */ 13 | public class ProjectPlan implements Plan { 14 | private Plan p; 15 | private Schema schema = new Schema(); 16 | 17 | public ProjectPlan(Plan p, List fieldlist) { 18 | this.p = p; 19 | for (String fldname : fieldlist) 20 | schema.add(fldname, p.schema()); 21 | } 22 | 23 | @Override 24 | public Scan open() { 25 | Scan s = p.open(); 26 | return new ProjectScan(s, schema.fields()); 27 | } 28 | 29 | @Override 30 | public int blockAccessed() { 31 | return p.blockAccessed(); 32 | } 33 | 34 | @Override 35 | public int recordsOutput() { 36 | return p.recordsOutput(); 37 | } 38 | 39 | @Override 40 | public int distinctValues(String fldname) { 41 | return p.distinctValues(fldname); 42 | } 43 | 44 | @Override 45 | public Schema schema() { 46 | return schema; 47 | } 48 | 49 | @Override 50 | public int preprocessingCost() { 51 | return 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/NetworkDriver.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.registry.LocateRegistry; 4 | import java.rmi.registry.Registry; 5 | import java.sql.Connection; 6 | import java.sql.SQLException; 7 | import java.util.Properties; 8 | 9 | import simpledb.jdbc.DriverAdapter; 10 | 11 | /* 12 | * The SimpleDB database driver 13 | */ 14 | public class NetworkDriver extends DriverAdapter { 15 | 16 | /* 17 | * Connect to the SimpleDB server on the specified host. 18 | * The method retrieves the RemoteDriver stub from 19 | * the RMI registry on the specified host. 20 | * It calls the connect method on the stub, 21 | * which in turn creates a new connection and returns 22 | * its corresponding RemoteConnection stub. 23 | */ 24 | public Connection connect(String url, Properties prop) throws SQLException { 25 | try { 26 | String host = url.replace("jdbc:simpledb://", ""); 27 | Registry reg = LocateRegistry.getRegistry(host, 1099); 28 | RemoteDriver rdvr = (RemoteDriver) reg.lookup("simpledb"); 29 | RemoteConnection rconn = rdvr.connect(); 30 | return new NetworkConnection(rconn); 31 | } catch (Exception e) { 32 | throw new SQLException(e); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteConnectionImpl.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.RemoteException; 4 | import java.rmi.server.UnicastRemoteObject; 5 | 6 | import simpledb.plan.Planner; 7 | import simpledb.server.SimpleDB; 8 | import simpledb.tx.Transaction; 9 | 10 | @SuppressWarnings("serial") 11 | public class RemoteConnectionImpl extends UnicastRemoteObject implements RemoteConnection { 12 | private SimpleDB db; 13 | private Transaction currentTx; 14 | private Planner planner; 15 | 16 | RemoteConnectionImpl(SimpleDB db) throws RemoteException { 17 | this.db = db; 18 | currentTx = db.newTx(); 19 | planner = db.planner(); 20 | } 21 | 22 | public RemoteStatement createStatement() throws RemoteException { 23 | return new RemoteStatementImpl(this, planner); 24 | } 25 | 26 | @Override 27 | public void close() throws RemoteException { 28 | currentTx.commit(); 29 | } 30 | 31 | // following methods are used by the server-side classes 32 | Transaction getTransaction() { 33 | return currentTx; 34 | } 35 | 36 | void commit() { 37 | currentTx.commit(); 38 | currentTx = db.newTx(); 39 | } 40 | 41 | void rollback() { 42 | currentTx.rollback(); 43 | currentTx = db.newTx(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/NetworkResultSet.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.sql.ResultSetMetaData; 4 | import java.sql.SQLException; 5 | 6 | import simpledb.jdbc.ResultSetAdapter; 7 | 8 | public class NetworkResultSet extends ResultSetAdapter { 9 | private RemoteResultSet rrs; 10 | 11 | public NetworkResultSet(RemoteResultSet s) { 12 | rrs = s; 13 | } 14 | 15 | public boolean next() throws SQLException { 16 | try { 17 | return rrs.next(); 18 | } catch (Exception e) { 19 | throw new SQLException(e); 20 | } 21 | } 22 | 23 | public int getInt(String fldname) throws SQLException { 24 | try { 25 | return rrs.getInt(fldname); 26 | } catch (Exception e) { 27 | throw new SQLException(e); 28 | } 29 | } 30 | 31 | public String getString(String fldname) throws SQLException { 32 | try { 33 | return rrs.getString(fldname); 34 | } catch (Exception e) { 35 | throw new SQLException(e); 36 | } 37 | } 38 | 39 | public ResultSetMetaData getMetadata() throws SQLException { 40 | try { 41 | RemoteMetaData rmd = rrs.getMetaData(); 42 | return new NetworkMetaData(rmd); 43 | } catch (Exception e) { 44 | throw new SQLException(e); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/materialize/MergeJoinScanTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertFalse; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | import static org.mockito.Mockito.when; 6 | 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.mockito.Mock; 10 | import org.mockito.junit.jupiter.MockitoExtension; 11 | 12 | import simpledb.query.Constant; 13 | import simpledb.query.Scan; 14 | 15 | @ExtendWith(MockitoExtension.class) 16 | public class MergeJoinScanTest { 17 | @Mock 18 | private Scan s1; 19 | 20 | @Mock 21 | private SortScan s2; 22 | 23 | @Test 24 | public void testMergeJoinScan() { 25 | when(s1.getVal("joinfield")).thenReturn(new Constant(1)); 26 | when(s1.next()).thenReturn(true, false); 27 | when(s2.getVal("joinfield")).thenReturn(new Constant(1), new Constant(1)); 28 | when(s2.next()).thenReturn(true, true, false); 29 | MergeJoinScan scan = new MergeJoinScan(s1, s2, "joinfield", "joinfield"); 30 | scan.beforeFirst(); 31 | assertTrue(scan.next()); // extract first record from s1 and s2 -> joinfield=1 32 | assertTrue(scan.next()); // s2.next -> joinfield=1 33 | assertFalse(scan.next()); // s1.next() is false, s2.next() is false. 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/01-database-systems/02-derbydatabase-system/derby.log: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------- 2 | Mon Nov 14 06:58:49 JST 2022: 3 | Booting Derby version The Apache Software Foundation - Apache Derby - 10.16.1.1 - (1901046): instance a816c00e-0184-7300-9699-00000f62aef8 4 | on database directory /Users/m.naka/repos/nakamasato/database-design-and-implementation/firstdb with class loader jdk.internal.loader.ClassLoaders$AppClassLoader@5b37e0d2 5 | Loaded from file:/Users/m.naka/Downloads/db-derby-10.16.1.1-bin/lib/derby.jar 6 | java.vendor=Homebrew 7 | java.runtime.version=19.0.1 8 | user.dir=/Users/m.naka/repos/nakamasato/database-design-and-implementation 9 | os.name=Mac OS X 10 | os.arch=aarch64 11 | os.version=12.6 12 | derby.system.home=null 13 | Database Class Loader started - derby.database.classpath='' 14 | ---------------------------------------------------------------- 15 | Mon Nov 14 07:01:28 JST 2022: Shutting down Derby engine 16 | ---------------------------------------------------------------- 17 | Mon Nov 14 07:01:28 JST 2022: 18 | Shutting down instance a816c00e-0184-7300-9699-00000f62aef8 on database directory /Users/m.naka/repos/nakamasato/database-design-and-implementation/firstdb with class loader jdk.internal.loader.ClassLoaders$AppClassLoader@5b37e0d2 19 | ---------------------------------------------------------------- 20 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/TablePlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import simpledb.metadata.MetadataMgr; 4 | import simpledb.metadata.StatInfo; 5 | import simpledb.query.Scan; 6 | import simpledb.record.Layout; 7 | import simpledb.record.Schema; 8 | import simpledb.record.TableScan; 9 | import simpledb.tx.Transaction; 10 | 11 | public class TablePlan implements Plan { 12 | private String tblname; 13 | private Transaction tx; 14 | private Layout layout; 15 | private StatInfo si; 16 | 17 | public TablePlan(Transaction tx, String tblname, MetadataMgr md) { 18 | this.tblname = tblname; 19 | this.tx = tx; 20 | layout = md.getLayout(tblname, tx); 21 | si = md.getStatInfo(tblname, layout, tx); 22 | } 23 | 24 | @Override 25 | public Scan open() { 26 | return new TableScan(tx, tblname, layout); 27 | } 28 | 29 | @Override 30 | public int blockAccessed() { 31 | return si.blocksAccessed(); 32 | } 33 | 34 | @Override 35 | public int recordsOutput() { 36 | return si.recordsOutput(); 37 | } 38 | 39 | @Override 40 | public int distinctValues(String fldname) { 41 | return si.distinctValues(fldname); 42 | } 43 | 44 | @Override 45 | public Schema schema() { 46 | return layout.schema(); 47 | } 48 | 49 | @Override 50 | public int preprocessingCost() { 51 | return 0; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/embedded/EmbeddedStatement.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.embedded; 2 | 3 | import java.sql.SQLException; 4 | 5 | import simpledb.jdbc.StatementAdapter; 6 | import simpledb.plan.Plan; 7 | import simpledb.plan.Planner; 8 | import simpledb.tx.Transaction; 9 | 10 | public class EmbeddedStatement extends StatementAdapter { 11 | private EmbeddedConnection conn; 12 | private Planner planner; 13 | 14 | public EmbeddedStatement(EmbeddedConnection conn, Planner planner) { 15 | this.conn = conn; 16 | this.planner = planner; 17 | } 18 | 19 | public EmbeddedResultSet executeQuery(String qry) throws SQLException { 20 | try { 21 | Transaction tx = conn.getTransaction(); 22 | Plan pln = planner.createQueryPlan(qry, tx); 23 | return new EmbeddedResultSet(pln, conn); 24 | } catch (RuntimeException e) { 25 | conn.rollback(); 26 | throw new SQLException(e); 27 | } 28 | } 29 | 30 | public int executeUpdate(String cmd) throws SQLException { 31 | try { 32 | Transaction tx = conn.getTransaction(); 33 | int result = planner.executeUpdate(cmd, tx); 34 | conn.commit(); 35 | return result; 36 | } catch (RuntimeException e) { 37 | conn.rollback(); 38 | throw new SQLException(); 39 | } 40 | } 41 | 42 | public void close() throws SQLException { 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/index/planner/IndexSelectPlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.planner; 2 | 3 | import simpledb.index.Index; 4 | import simpledb.index.query.IndexSelectScan; 5 | import simpledb.metadata.IndexInfo; 6 | import simpledb.plan.Plan; 7 | import simpledb.query.Constant; 8 | import simpledb.query.Scan; 9 | import simpledb.record.Schema; 10 | import simpledb.record.TableScan; 11 | 12 | public class IndexSelectPlan implements Plan { 13 | private Plan p; 14 | private IndexInfo ii; 15 | private Constant val; 16 | 17 | public IndexSelectPlan(Plan p, IndexInfo ii, Constant val) { 18 | this.p = p; 19 | this.ii = ii; 20 | this.val = val; 21 | } 22 | 23 | @Override 24 | public Scan open() { 25 | TableScan ts = (TableScan) p.open(); 26 | Index idx = ii.open(); 27 | return new IndexSelectScan(ts, idx, val); 28 | } 29 | 30 | @Override 31 | public int blockAccessed() { 32 | return ii.blocksAccessed() + recordsOutput(); 33 | } 34 | 35 | @Override 36 | public int recordsOutput() { 37 | return ii.recordsOutput(); 38 | } 39 | 40 | @Override 41 | public int distinctValues(String fldname) { 42 | return ii.distinctValues(fldname); 43 | } 44 | 45 | @Override 46 | public Schema schema() { 47 | return p.schema(); 48 | } 49 | 50 | @Override 51 | public int preprocessingCost() { 52 | return 0; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/GroupValue.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import java.util.HashMap; 4 | import java.util.List; 5 | import java.util.Map; 6 | 7 | import simpledb.query.Constant; 8 | import simpledb.query.Scan; 9 | 10 | /* 11 | * Object to hold the values of the grouping fields for 12 | * the current record of a scan. 13 | */ 14 | public class GroupValue { 15 | private Map vals = new HashMap<>(); 16 | 17 | public GroupValue(Scan s, List fields) { 18 | vals = new HashMap<>(); 19 | for (String fldname : fields) 20 | vals.put(fldname, s.getVal(fldname)); 21 | } 22 | 23 | public Constant getVal(String fldname) { 24 | return vals.get(fldname); 25 | } 26 | 27 | public boolean equals(Object obj) { 28 | if (obj == null) 29 | return false; 30 | GroupValue gv = (GroupValue) obj; 31 | for (Map.Entry e : vals.entrySet()) { 32 | Constant v1 = e.getValue(); 33 | Constant v2 = gv.getVal(e.getKey()); 34 | if (!v1.equals(v2)) 35 | return false; 36 | } 37 | return true; 38 | } 39 | 40 | /* 41 | * The hashcode of a GroupValue object is 42 | * the sum of the hashcodes of its field values. 43 | */ 44 | public int hashCode() { 45 | int hashval = 0; 46 | for (Constant c : vals.values()) 47 | hashval += c.hashCode(); 48 | return hashval; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/ProjectScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | import java.util.List; 4 | 5 | public class ProjectScan implements Scan { 6 | private Scan s; 7 | private List fieldlist; 8 | 9 | public ProjectScan(Scan s, List fieldlist) { 10 | this.s = s; 11 | this.fieldlist = fieldlist; 12 | } 13 | 14 | @Override 15 | public void beforeFirst() { 16 | s.beforeFirst(); 17 | } 18 | 19 | @Override 20 | public boolean next() { 21 | return s.next(); 22 | } 23 | 24 | @Override 25 | public int getInt(String fldname) { 26 | if (hasField(fldname)) 27 | return s.getInt(fldname); 28 | else 29 | throw new RuntimeException("field " + fldname + " not found."); 30 | } 31 | 32 | @Override 33 | public String getString(String fldname) { 34 | if (hasField(fldname)) 35 | return s.getString(fldname); 36 | else 37 | throw new RuntimeException("field " + fldname + " not found."); 38 | } 39 | 40 | @Override 41 | public Constant getVal(String fldname) { 42 | if (hasField(fldname)) 43 | return s.getVal(fldname); 44 | else 45 | throw new RuntimeException("field " + fldname + " not found."); 46 | } 47 | 48 | @Override 49 | public boolean hasField(String fldname) { 50 | return fieldlist.contains(fldname); 51 | } 52 | 53 | @Override 54 | public void close() { 55 | s.close(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/index/query/IndexSelectScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.query; 2 | 3 | import simpledb.index.Index; 4 | import simpledb.query.Constant; 5 | import simpledb.query.Scan; 6 | import simpledb.record.RID; 7 | import simpledb.record.TableScan; 8 | 9 | public class IndexSelectScan implements Scan { 10 | private TableScan ts; 11 | private Index idx; 12 | private Constant val; 13 | 14 | public IndexSelectScan(TableScan ts, Index idx, Constant val) { 15 | this.ts = ts; 16 | this.idx = idx; 17 | this.val = val; 18 | beforeFirst(); 19 | } 20 | 21 | @Override 22 | public void beforeFirst() { 23 | idx.beforeFirst(val); 24 | } 25 | 26 | @Override 27 | public boolean next() { 28 | boolean ok = idx.next(); 29 | if (ok) { 30 | RID rid = idx.getDataRid(); 31 | ts.moveToRid(rid); 32 | } 33 | return ok; 34 | } 35 | 36 | @Override 37 | public int getInt(String fldname) { 38 | return ts.getInt(fldname); 39 | } 40 | 41 | @Override 42 | public String getString(String fldname) { 43 | return ts.getString(fldname); 44 | } 45 | 46 | @Override 47 | public Constant getVal(String fldname) { 48 | return ts.getVal(fldname); 49 | } 50 | 51 | @Override 52 | public boolean hasField(String fldname) { 53 | return ts.hasField(fldname); 54 | } 55 | 56 | @Override 57 | public void close() { 58 | idx.close(); 59 | ts.close(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/multibuffer/BufferNeeds.java: -------------------------------------------------------------------------------- 1 | package simpledb.multibuffer; 2 | 3 | /* 4 | * provide a static methods to estimate 5 | * the optimal number of buffers to allocate for a scan. 6 | */ 7 | public class BufferNeeds { 8 | /* 9 | * return the highest root that 10 | * is less than the number of available buffers 11 | * 13 | * 14 | * @param size the size of the output file 15 | */ 16 | public static int bestRoot(int available, int size) { 17 | int avail = available - 2; // reserve a couple 18 | if (avail <= 1) 19 | return 1; 20 | int k = Integer.MAX_VALUE; 21 | double i = 1.0; 22 | while (k > avail) { 23 | i++; 24 | k = (int) Math.ceil(Math.pow(size, 1 / i)); 25 | } 26 | return k; 27 | } 28 | 29 | /* 30 | * return the heighest factor that 31 | * is less than the available buffers 32 | * 34 | * 35 | * @param size the size of the output file 36 | */ 37 | public static int bestFactor(int available, int size) { 38 | int avail = available - 2; // reserve a couple 39 | if (avail <=1) 40 | return 1; 41 | int k = size; 42 | double i = 1.0; 43 | while (k > avail) { 44 | i++; 45 | k = (int) Math.ceil(size / i); 46 | } 47 | return k; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/multibuffer/BufferNeedsTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.multibuffer; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | public class BufferNeedsTest { 8 | @Test 9 | public void testBestRoot() { 10 | // get the minimal i such that 100 <= k**i & k <= available-2 11 | assertEquals(3, BufferNeeds.bestRoot(5, 100)); // needs 5 times (3**5=243) 12 | assertEquals(5, BufferNeeds.bestRoot(7, 100)); // needs 3 times (5**3=125) 13 | assertEquals(5, BufferNeeds.bestRoot(8, 100)); // needs 3 times (4**3=64<100, 5**3=125, 6**2<100, 6**3=216) 14 | assertEquals(5, BufferNeeds.bestRoot(10, 100)); // needs 3 times (5**3=125, 8*2=64<100) 15 | assertEquals(10, BufferNeeds.bestRoot(12, 100)); // needs 2 times (10**2=100) 16 | assertEquals(10, BufferNeeds.bestRoot(30, 100)); // needs 2 times (10**2=100, 28**1<100) 17 | } 18 | @Test 19 | public void testBestFactor() { 20 | assertEquals(8, BufferNeeds.bestFactor(10, 100)); // 8 * 13 = 104, 7 * 15 = 105 21 | assertEquals(10, BufferNeeds.bestFactor(12, 100)); // 10 * 10 = 100 22 | assertEquals(25, BufferNeeds.bestFactor(30, 100)); // 25 * 4 = 100, 26 * 4 = 104 23 | assertEquals(50, BufferNeeds.bestFactor(100, 100)); // 50 * 2 = 100 24 | assertEquals(100, BufferNeeds.bestFactor(102, 100)); // 100 * 1 = 100 25 | assertEquals(100, BufferNeeds.bestFactor(200, 100)); // 100 * 1 = 100 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteStatementImpl.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.RemoteException; 4 | import java.rmi.server.UnicastRemoteObject; 5 | 6 | import simpledb.plan.Plan; 7 | import simpledb.plan.Planner; 8 | import simpledb.tx.Transaction; 9 | 10 | @SuppressWarnings("serial") 11 | public class RemoteStatementImpl extends UnicastRemoteObject implements RemoteStatement { 12 | private RemoteConnectionImpl rconn; 13 | private Planner planner; 14 | 15 | public RemoteStatementImpl(RemoteConnectionImpl rconn, Planner planner) throws RemoteException { 16 | this.rconn = rconn; 17 | this.planner = planner; 18 | } 19 | 20 | @Override 21 | public RemoteResultSet executeQuery(String qry) throws RemoteException { 22 | try { 23 | Transaction tx = rconn.getTransaction(); 24 | Plan pln = planner.createQueryPlan(qry, tx); 25 | return new RemoteResultSetImpl(pln, rconn); 26 | } catch (RuntimeException e) { 27 | rconn.rollback(); 28 | throw e; 29 | } 30 | } 31 | 32 | @Override 33 | public int executeUpdate(String cmd) throws RemoteException { 34 | try { 35 | Transaction tx = rconn.getTransaction(); 36 | int result = planner.executeUpdate(cmd, tx); 37 | rconn.commit(); 38 | return result; 39 | } catch (RuntimeException e) { 40 | rconn.rollback(); 41 | throw e; 42 | } 43 | } 44 | 45 | @Override 46 | public void close() throws RemoteException { 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteMetaDataImpl.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import static java.sql.Types.INTEGER; 4 | 5 | import java.rmi.RemoteException; 6 | import java.rmi.server.UnicastRemoteObject; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import simpledb.record.Schema; 11 | 12 | public class RemoteMetaDataImpl extends UnicastRemoteObject implements RemoteMetaData { 13 | private Schema sch; 14 | private List fields = new ArrayList<>(); 15 | 16 | public RemoteMetaDataImpl(Schema sch) throws RemoteException { 17 | this.sch = sch; 18 | for (String fld : sch.fields()) 19 | fields.add(fld); 20 | } 21 | 22 | @Override 23 | public int getColumnCount() throws RemoteException { 24 | return fields.size(); 25 | } 26 | 27 | @Override 28 | public String getColumnName(int column) throws RemoteException { 29 | return fields.get(column - 1); 30 | } 31 | 32 | @Override 33 | public int getColumnType(int column) throws RemoteException { 34 | String fldname = getColumnName(column); 35 | return sch.type(fldname); 36 | } 37 | 38 | /* 39 | * Return the number of characters required to display the specified column. 40 | */ 41 | @Override 42 | public int getColumnDisplaySize(int column) throws RemoteException { 43 | String fldname = getColumnName(column); 44 | int fldtype = sch.type(fldname); 45 | int fldlength = (fldtype == INTEGER) ? 6 : sch.length(fldname); 46 | return Math.max(fldname.length(), fldlength) + 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /simpledb/docs/00-initialize-project.md: -------------------------------------------------------------------------------- 1 | ## Initialize a project 2 | 3 | ``` 4 | gradle init 5 | ``` 6 | 7 |
8 | 9 | ``` 10 | Select type of project to generate: 11 | 1: basic 12 | 2: application 13 | 3: library 14 | 4: Gradle plugin 15 | Enter selection (default: basic) [1..4] 2 16 | 17 | Select implementation language: 18 | 1: C++ 19 | 2: Groovy 20 | 3: Java 21 | 4: Kotlin 22 | 5: Scala 23 | 6: Swift 24 | Enter selection (default: Java) [1..6] 3 25 | 26 | Split functionality across multiple subprojects?: 27 | 1: no - only one application project 28 | 2: yes - application and library projects 29 | Enter selection (default: no - only one application project) [1..2] 30 | 31 | Select build script DSL: 32 | 1: Groovy 33 | 2: Kotlin 34 | Enter selection (default: Groovy) [1..2] 2 35 | 36 | Generate build using new APIs and behavior (some features may change in the next minor release)? (default: no) [yes, no] 37 | 38 | Select test framework: 39 | 1: JUnit 4 40 | 2: TestNG 41 | 3: Spock 42 | 4: JUnit Jupiter 43 | Enter selection (default: JUnit Jupiter) [1..4] 44 | 45 | Project name (default: simpledb): 46 | 47 | Source package (default: simpledb): 48 | 49 | 50 | > Task :init 51 | Get more help with your project: https://docs.gradle.org/7.5.1/samples/sample_building_java_applications.html 52 | 53 | BUILD SUCCESSFUL in 24s 54 | 2 actionable tasks: 2 executed 55 | ``` 56 | 57 |
58 | 59 | Run the app 60 | 61 | ``` 62 | ./gradlew run 63 | ``` 64 | 65 | Bundle the app 66 | 67 | ``` 68 | ./gradlew build 69 | ``` 70 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/buffer/Buffer.java: -------------------------------------------------------------------------------- 1 | package simpledb.buffer; 2 | 3 | import simpledb.file.BlockId; 4 | import simpledb.file.FileMgr; 5 | import simpledb.file.Page; 6 | import simpledb.log.LogMgr; 7 | 8 | public class Buffer { 9 | private FileMgr fm; 10 | private LogMgr lm; 11 | private Page contents; 12 | private BlockId blk = null; 13 | private int pins = 0; 14 | private int txnum = -1; 15 | private int lsn = -1; 16 | 17 | public Buffer(FileMgr fm, LogMgr lm) { 18 | this.fm = fm; 19 | this.lm = lm; 20 | contents = new Page(fm.blockSize()); 21 | } 22 | 23 | public Page contents() { 24 | return contents; 25 | } 26 | 27 | /* 28 | * Returns a block allocated to the buffer 29 | */ 30 | public BlockId block() { 31 | return blk; 32 | } 33 | 34 | public void setModified(int txnum, int lsn) { 35 | this.txnum = txnum; 36 | if (lsn >= 0) 37 | this.lsn = lsn; 38 | } 39 | 40 | public boolean isPinned() { 41 | return pins > 0; 42 | } 43 | 44 | public int modifyingTx() { 45 | return txnum; 46 | } 47 | 48 | void assignToBlock(BlockId b) { 49 | flush(); 50 | blk = b; 51 | fm.read(blk, contents); 52 | pins = 0; 53 | } 54 | 55 | /* 56 | * Write the buffer to its disk block if it is dirty. 57 | */ 58 | void flush() { 59 | if (txnum >= 0) { 60 | lm.flush(lsn); 61 | fm.write(blk, contents); 62 | txnum = -1; 63 | } 64 | } 65 | 66 | void pin() { 67 | pins++; 68 | } 69 | 70 | void unpin() { 71 | pins--; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/embedded/EmbeddedResultSet.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.embedded; 2 | 3 | import java.sql.ResultSetMetaData; 4 | import java.sql.SQLException; 5 | 6 | import simpledb.jdbc.ResultSetAdapter; 7 | import simpledb.plan.Plan; 8 | import simpledb.query.Scan; 9 | import simpledb.record.Schema; 10 | 11 | public class EmbeddedResultSet extends ResultSetAdapter { 12 | private Scan s; 13 | private Schema sch; 14 | private EmbeddedConnection conn; 15 | 16 | public EmbeddedResultSet(Plan plan, EmbeddedConnection conn) throws SQLException { 17 | s = plan.open(); 18 | sch = plan.schema(); 19 | this.conn = conn; 20 | } 21 | 22 | public boolean next() throws SQLException { 23 | try { 24 | return s.next(); 25 | } catch (RuntimeException e) { 26 | conn.rollback(); 27 | throw new SQLException(e); 28 | } 29 | } 30 | 31 | public int getInt(String fldname) throws SQLException { 32 | try { 33 | fldname = fldname.toLowerCase(); 34 | return s.getInt(fldname); 35 | } catch (RuntimeException e) { 36 | conn.rollback(); 37 | throw new SQLException(e); 38 | } 39 | } 40 | 41 | public String getString(String fldname) throws SQLException { 42 | try { 43 | fldname = fldname.toLowerCase(); 44 | return s.getString(fldname); 45 | } catch (RuntimeException e) { 46 | conn.rollback(); 47 | throw new SQLException(e); 48 | } 49 | } 50 | 51 | public ResultSetMetaData getMetaData() throws SQLException { 52 | return new EmbeddedMetaData(sch); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Database Design and Implementation 2 | 3 | ## SimpleDB Implementation 4 | 5 | ### Diagram (WIP) 6 | 7 | ![](simpledb/docs/simpledb.drawio.svg) 8 | ### Project Init 9 | 1. [Initialize a project](simpledb/docs/00-initialize-project.md) 10 | ### Step by Step 11 | 1. Chapter 1: Database Systems 12 | 1. Chpater 2: JDBC 13 | 1. [Chapter 3: Disk and File Management](simpledb/docs/03-disk-file-management.md) 14 | 1. [Chapter 4: Memory Management](simpledb/docs/04-memory-management.md) 15 | 1. [Chapter 5: Transaction Management](simpledb/docs/05-transaction-management.md) 16 | 1. [Chapter 6: Record Management](simpledb/docs/06-record-management.md) 17 | 1. [Chapter 7: Metadata Management](simpledb/docs/07-metadata-management.md) 18 | 1. [Chapter 8: Query Processing](simpledb/docs/08-query-processing.md) 19 | 1. [Chapter 9: Parsing](simpledb/docs/09-parsing.md) 20 | 1. [Chapter 10: Planning](simpledb/docs/10-planning.md) 21 | 1. [Chapter 11: JDBC Interfaces](simpledb/docs/11-jdbc-interfaces.md) 22 | 1. [Chapter 12: Indexing](simpledb/docs/12-indexing.md) 23 | 1. [Chapter 13: Materialization and Sorting](simpledb/docs/13-materialization-and-sorting.md) 24 | 1. [Chpater 14: Effective Buffer Utilization](simpledb/docs/14-effective-buffer-utilization.md) 25 | 1. Chapter 15: Query Optimization 26 | 27 | 28 | ## Other 29 | 30 | 1. [Derby Database System](docs/01-database-systems/02-derbydatabase-system) 31 | 1. [SimpleDB](docs/01-database-systems/04-simpledb/README.md) 32 | 1. [RMI](rmi/README.md) 33 | 34 | ## References 35 | 36 | 1. [簡易DBをフルスクラッチで実装して得た学び](https://zenn.dev/kj455/articles/1f058302e6b528) 37 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/materialize/GroupByScanTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import static org.mockito.Mockito.when; 7 | 8 | import java.util.Arrays; 9 | 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.ExtendWith; 12 | import org.mockito.Mock; 13 | import org.mockito.junit.jupiter.MockitoExtension; 14 | 15 | import simpledb.query.Constant; 16 | import simpledb.query.Scan; 17 | 18 | @ExtendWith(MockitoExtension.class) 19 | public class GroupByScanTest { 20 | @Mock 21 | private Scan scan; 22 | 23 | @Mock 24 | private AggregationFn aggfn; 25 | 26 | @Test 27 | public void testGroupByScan() { 28 | when(scan.next()).thenReturn(true, true, true, true, false); // 4 records 29 | when(scan.getVal("gf")).thenReturn(new Constant(1),new Constant(1) , new Constant(2), new Constant(2)); 30 | when(aggfn.fieldName()).thenReturn("countoffld"); 31 | when(aggfn.value()).thenReturn(new Constant(10), new Constant(20)); 32 | 33 | GroupByScan gbs = new GroupByScan(scan, Arrays.asList("gf"), Arrays.asList(aggfn)); 34 | 35 | assertTrue(gbs.next()); // gf=1 36 | assertEquals(1, gbs.getInt("gf")); 37 | assertEquals(new Constant(10), gbs.getVal("countoffld")); 38 | assertTrue(gbs.next()); // gf=2 39 | assertEquals(2, gbs.getInt("gf")); 40 | assertEquals(new Constant(20), gbs.getVal("countoffld")); 41 | assertFalse(gbs.next()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/materialize/RecordComparatorTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.mockito.Mockito.when; 5 | 6 | import java.util.Arrays; 7 | 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | import simpledb.query.Constant; 14 | import simpledb.query.Scan; 15 | 16 | @ExtendWith(MockitoExtension.class) 17 | public class RecordComparatorTest { 18 | @Mock 19 | private Scan s1; 20 | 21 | @Mock 22 | private Scan s2; 23 | 24 | @Test 25 | public void testCompareForEqualCase() { 26 | when(s1.getVal("fld1")).thenReturn(new Constant(1)); 27 | when(s2.getVal("fld1")).thenReturn(new Constant(1)); 28 | RecordComparator comp = new RecordComparator(Arrays.asList("fld1")); 29 | assertEquals(0, comp.compare(s1, s2)); 30 | } 31 | 32 | @Test 33 | public void testCompareForLargerCase() { 34 | when(s1.getVal("fld1")).thenReturn(new Constant(10)); 35 | when(s2.getVal("fld1")).thenReturn(new Constant(1)); 36 | RecordComparator comp = new RecordComparator(Arrays.asList("fld1")); 37 | assertEquals(1, comp.compare(s1, s2)); 38 | } 39 | 40 | @Test 41 | public void testCompareForSmallerCase() { 42 | when(s1.getVal("fld1")).thenReturn(new Constant(0)); 43 | when(s2.getVal("fld1")).thenReturn(new Constant(1)); 44 | RecordComparator comp = new RecordComparator(Arrays.asList("fld1")); 45 | assertEquals(-1, comp.compare(s1, s2)); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/record/Layout.java: -------------------------------------------------------------------------------- 1 | package simpledb.record; 2 | 3 | import static java.sql.Types.INTEGER; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import simpledb.file.Page; 9 | 10 | /* 11 | * Description of the structure of a record. 12 | * It contains the name, type, length and offset of 13 | * each field of the table. 14 | */ 15 | public class Layout { 16 | private Schema schema; 17 | /* 18 | * Offset of each field 19 | */ 20 | private Map offsets; 21 | private int slotsize; 22 | 23 | public Layout(Schema schema) { 24 | this.schema = schema; 25 | offsets = new HashMap<>(); 26 | int pos = Integer.BYTES; // leave space for the empty/inuse flag 27 | for (String fldname : schema.fields()) { 28 | offsets.put(fldname, pos); 29 | pos += lengthInBytes(fldname); 30 | } 31 | slotsize = pos; 32 | } 33 | 34 | public Layout(Schema schema, Map offsets, int slotsize) { 35 | this.schema = schema; 36 | this.offsets = offsets; 37 | this.slotsize = slotsize; 38 | } 39 | 40 | public Schema schema() { 41 | return schema; 42 | } 43 | 44 | public int offset(String fldname) { 45 | return offsets.get(fldname); 46 | } 47 | 48 | public int slotSize() { 49 | return slotsize; 50 | } 51 | 52 | private int lengthInBytes(String fldname) { 53 | int fldtype = schema.type(fldname); 54 | if (fldtype == INTEGER) 55 | return Integer.BYTES; 56 | else 57 | return Page.maxLength(schema.length(fldname)); // 4 bytes + length of bytes (= strlen if using ASCII) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/SelectPlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import simpledb.query.Predicate; 4 | import simpledb.query.Scan; 5 | import simpledb.query.SelectScan; 6 | import simpledb.record.Schema; 7 | 8 | /* 9 | * The Plan class corresponding to the select 10 | * relational algebra operator 11 | */ 12 | public class SelectPlan implements Plan { 13 | private Plan p; 14 | private Predicate pred; 15 | 16 | public SelectPlan(Plan p, Predicate pred) { 17 | this.p = p; 18 | this.pred = pred; 19 | } 20 | 21 | @Override 22 | public Scan open() { 23 | Scan s = p.open(); 24 | return new SelectScan(s, pred); 25 | } 26 | 27 | @Override 28 | public int blockAccessed() { 29 | return p.blockAccessed(); 30 | } 31 | 32 | /* 33 | * Estimate the number of output records in the selectiion, 34 | * which is determined by the reduction factor of the predicate. 35 | */ 36 | @Override 37 | public int recordsOutput() { 38 | return p.recordsOutput() / pred.reductionFactor(p); 39 | } 40 | 41 | @Override 42 | public int distinctValues(String fldname) { 43 | if (pred.equatesWithConstant(fldname) != null) 44 | return 1; 45 | else { 46 | String fldname2 = pred.equatesWithField(fldname); 47 | if (fldname2 != null) 48 | return Math.min(p.distinctValues(fldname), p.distinctValues(fldname2)); 49 | else 50 | return p.distinctValues(fldname); 51 | } 52 | } 53 | 54 | @Override 55 | public Schema schema() { 56 | return p.schema(); 57 | } 58 | 59 | @Override 60 | public int preprocessingCost() { 61 | return 0; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/BasicQueryPlanner.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import simpledb.metadata.MetadataMgr; 7 | import simpledb.parse.Parser; 8 | import simpledb.parse.QueryData; 9 | import simpledb.tx.Transaction; 10 | 11 | /* 12 | * Simplest and most naive query planner 13 | */ 14 | public class BasicQueryPlanner implements QueryPlanner { 15 | private MetadataMgr mdm; 16 | 17 | public BasicQueryPlanner(MetadataMgr mdm) { 18 | this.mdm = mdm; 19 | } 20 | 21 | @Override 22 | public Plan createPlan(QueryData data, Transaction tx) { 23 | // Step 1: Create a plan for each mentioned table or view. 24 | List plans = new ArrayList<>(); 25 | for (String tblname : data.tables()) { 26 | String viewdef = mdm.getViewDef(tblname, tx); 27 | if (viewdef != null) { 28 | Parser parser = new Parser(viewdef); 29 | QueryData viewData = parser.query(); 30 | plans.add(createPlan(viewData, tx)); 31 | } else 32 | plans.add(new TablePlan(tx, tblname, mdm)); 33 | } 34 | 35 | // Step 2: Create product of all table plans 36 | // ProductPlan(...ProductPlan(ProductPlan(p0, p1), p2, p3,...) 37 | // The order is arbitrary as tables() returns Collection 38 | Plan p = plans.remove(0); 39 | for (Plan nextplan : plans) 40 | p = new ProductPlan(p, nextplan); 41 | 42 | // Step 3: Add a select plan for the predicate 43 | p = new SelectPlan(p, data.predicate()); 44 | 45 | // Step 4: Project on the field names 46 | p = new ProjectPlan(p, data.fields()); 47 | return p; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/ProductPlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import simpledb.query.ProductScan; 4 | import simpledb.query.Scan; 5 | import simpledb.record.Schema; 6 | 7 | public class ProductPlan implements Plan { 8 | private Plan p1; 9 | private Plan p2; 10 | private Schema schema = new Schema(); 11 | 12 | public ProductPlan(Plan p1, Plan p2) { 13 | this.p1 = p1; 14 | this.p2 = p2; 15 | schema.addAll(p1.schema()); 16 | schema.addAll(p2.schema()); 17 | } 18 | 19 | @Override 20 | public Scan open() { 21 | Scan s1 = p1.open(); 22 | Scan s2 = p2.open(); 23 | return new ProductScan(s1, s2); 24 | } 25 | 26 | /* 27 | * Estimate the required block access 28 | * B(product(p1, p2)) = B(p1) + R(p1)*B(p2) 29 | */ 30 | @Override 31 | public int blockAccessed() { 32 | return p1.blockAccessed() + p1.recordsOutput() * p2.blockAccessed(); 33 | } 34 | 35 | /* 36 | * Estimate the number of output records 37 | * R(product(p1, p2)) = R(p1)*R(p2) 38 | */ 39 | @Override 40 | public int recordsOutput() { 41 | return p1.recordsOutput() * p2.recordsOutput(); 42 | } 43 | 44 | /* 45 | * Estimate the distinct number of field values. 46 | * The distinct value is same as the underlying query. 47 | */ 48 | @Override 49 | public int distinctValues(String fldname) { 50 | if (p1.schema().hasField(fldname)) 51 | return p1.distinctValues(fldname); 52 | else 53 | return p2.distinctValues(fldname); 54 | } 55 | 56 | @Override 57 | public Schema schema() { 58 | return schema; 59 | } 60 | 61 | @Override 62 | public int preprocessingCost() { 63 | return 0; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/DriverAdapter.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc; 2 | 3 | import java.sql.Connection; 4 | import java.sql.Driver; 5 | import java.sql.DriverPropertyInfo; 6 | import java.sql.SQLException; 7 | import java.sql.SQLFeatureNotSupportedException; 8 | import java.util.Properties; 9 | import java.util.logging.Logger; 10 | 11 | /* 12 | * This class implements all of the methods of the Driver interface, 13 | * by throwing an exception for each one. 14 | * Subclasses (such as SimpleDriver) can override them. 15 | */ 16 | public abstract class DriverAdapter implements Driver { 17 | 18 | @Override 19 | public boolean acceptsURL(String url) throws SQLException { 20 | throw new SQLException("operation not implemented"); 21 | } 22 | 23 | @Override 24 | public Connection connect(String url, Properties info) throws SQLException { 25 | throw new SQLException("operation not implemented"); 26 | } 27 | 28 | @Override 29 | public int getMajorVersion() { 30 | // TODO Auto-generated method stub 31 | return 0; 32 | } 33 | 34 | @Override 35 | public int getMinorVersion() { 36 | // TODO Auto-generated method stub 37 | return 0; 38 | } 39 | 40 | @Override 41 | public Logger getParentLogger() throws SQLFeatureNotSupportedException { 42 | throw new SQLFeatureNotSupportedException("operation not implemented"); 43 | } 44 | 45 | @Override 46 | public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException { 47 | // TODO Auto-generated method stub 48 | return null; 49 | } 50 | 51 | @Override 52 | public boolean jdbcCompliant() { 53 | // TODO Auto-generated method stub 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/log/LogIterator.java: -------------------------------------------------------------------------------- 1 | package simpledb.log; 2 | 3 | import java.util.Iterator; 4 | 5 | import simpledb.file.BlockId; 6 | import simpledb.file.FileMgr; 7 | import simpledb.file.Page; 8 | 9 | public class LogIterator implements Iterator { 10 | private FileMgr fm; 11 | private BlockId blk; 12 | private Page p; 13 | private int currentpos; 14 | private int boundary; // what is the boundary? 15 | 16 | public LogIterator(FileMgr fm, BlockId blk) { 17 | this.fm = fm; 18 | this.blk = blk; 19 | byte[] b = new byte[fm.blockSize()]; 20 | p = new Page(b); 21 | moveToBlock(blk); 22 | } 23 | 24 | public boolean hasNext() { 25 | return currentpos < fm.blockSize() || blk.number() > 0; 26 | } 27 | 28 | /* 29 | * Read logs from new to old (New Block to old block) 30 | * Inside a block, contents will be read from left to right. 31 | * The logs are written from right to left, so the reading order is descendent. 32 | */ 33 | public byte[] next() { 34 | if (currentpos == fm.blockSize()) { 35 | blk = new BlockId(blk.fileName(), blk.number() - 1); // decrement block number to move to next block 36 | moveToBlock(blk); 37 | } 38 | byte[] rec = p.getBytes(currentpos); 39 | currentpos += Integer.BYTES + rec.length; 40 | return rec; 41 | } 42 | 43 | /* 44 | * Read block contents to the page 45 | * Set the boundary to the number stored in the first four bytes 46 | * which indicates the boundary. 47 | * Set the current position to the obtained boundary. 48 | */ 49 | private void moveToBlock(BlockId blk) { 50 | fm.read(blk, p); 51 | boundary = p.getInt(0); 52 | currentpos = boundary; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/client/network/JdbcEmbeddedDriverExample.java: -------------------------------------------------------------------------------- 1 | package simpledb.client.network; 2 | 3 | import java.sql.Connection; 4 | import java.sql.Driver; 5 | import java.sql.ResultSet; 6 | import java.sql.SQLException; 7 | import java.sql.Statement; 8 | 9 | import simpledb.jdbc.embedded.EmbeddedDriver; 10 | 11 | public class JdbcEmbeddedDriverExample { 12 | public static void main(String[] args) { 13 | Driver d = new EmbeddedDriver(); 14 | String url = "jdbc:simpledb:datadir"; 15 | try (Connection conn = d.connect(url, null); 16 | Statement stmt = conn.createStatement()) { 17 | // 1. create table student 18 | String sql = "create table STUDENT (Sid int, SName varchar(10), MajorId int, GradYear int)"; 19 | stmt.executeUpdate(sql); 20 | 21 | // 2. select tables 22 | sql = "select tblname, slotsize from tblcat"; 23 | ResultSet rs = stmt.executeQuery(sql); 24 | while (rs.next()) 25 | System.out.println(String.format("table: %s, slotsize: %d", rs.getString("tblname"), rs.getInt("slotsize"))); 26 | 27 | // 3. insert record to student table 28 | sql = "insert into student(Sid, SName, MajorId, GradYear) values (1, 'John', 10, 2020)"; 29 | stmt.executeUpdate(sql); 30 | 31 | // 4. select records from student table 32 | sql = "select Sid, SName, MajorId, GradYear from student"; 33 | rs = stmt.executeQuery(sql); 34 | while (rs.next()) 35 | System.out.println(String.format("Sid: %d, Sname: %s, MajorId: %d, GradYear: %d", rs.getInt("Sid"), 36 | rs.getString("SName"), rs.getInt("MajorId"), rs.getInt("GradYear"))); 37 | } catch (SQLException e) { 38 | e.printStackTrace(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/jdbc/network/RemoteResultSetImpl.java: -------------------------------------------------------------------------------- 1 | package simpledb.jdbc.network; 2 | 3 | import java.rmi.RemoteException; 4 | import java.rmi.server.UnicastRemoteObject; 5 | 6 | import simpledb.plan.Plan; 7 | import simpledb.query.Scan; 8 | import simpledb.record.Schema; 9 | 10 | public class RemoteResultSetImpl extends UnicastRemoteObject implements RemoteResultSet { 11 | private Scan s; 12 | private Schema sch; 13 | private RemoteConnectionImpl rconn; 14 | 15 | public RemoteResultSetImpl(Plan plan, RemoteConnectionImpl rconn) throws RemoteException { 16 | s = plan.open(); 17 | sch = plan.schema(); 18 | this.rconn = rconn; 19 | } 20 | 21 | @Override 22 | public boolean next() throws RemoteException { 23 | try { 24 | return s.next(); 25 | } catch (RuntimeException e) { 26 | rconn.rollback(); 27 | throw e; 28 | } 29 | } 30 | 31 | @Override 32 | public int getInt(String fldname) throws RemoteException { 33 | try { 34 | fldname = fldname.toLowerCase(); 35 | return s.getInt(fldname); 36 | } catch (RuntimeException e) { 37 | rconn.rollback(); 38 | throw e; 39 | } 40 | } 41 | 42 | @Override 43 | public String getString(String fldname) throws RemoteException { 44 | try { 45 | fldname = fldname.toLowerCase(); 46 | return s.getString(fldname); 47 | } catch (RuntimeException e) { 48 | rconn.rollback(); 49 | throw e; 50 | } 51 | } 52 | 53 | @Override 54 | public RemoteMetaData getMetaData() throws RemoteException { 55 | return new RemoteMetaDataImpl(sch); 56 | } 57 | 58 | @Override 59 | public void close() throws RemoteException { 60 | s.close(); 61 | rconn.commit(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/metadata/ViewMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.metadata; 2 | 3 | import simpledb.record.Layout; 4 | import simpledb.record.Schema; 5 | import simpledb.record.TableScan; 6 | import simpledb.tx.Transaction; 7 | 8 | public class ViewMgr { 9 | private static final int MAX_VIEW_DEF = 100; // unrealistic for real case. clob(9999) would be better 10 | private static final String VIEW_CAT_TABLE = "viewcat"; 11 | private static final String VIEW_CAT_FIELD_NAME = "viewname"; 12 | private static final String VIEW_CAT_FIELD_DEF = "viewdef"; 13 | 14 | TableMgr tblMgr; 15 | 16 | public ViewMgr(boolean isNew, TableMgr tblMgr, Transaction tx) { 17 | this.tblMgr = tblMgr; 18 | if (isNew) { 19 | Schema sch = new Schema(); 20 | sch.addStringField(VIEW_CAT_FIELD_NAME, TableMgr.MAX_NAME); 21 | sch.addStringField(VIEW_CAT_FIELD_DEF, MAX_VIEW_DEF); 22 | tblMgr.createTable(VIEW_CAT_TABLE, sch, tx); 23 | } 24 | } 25 | 26 | public void createView(String vname, String vdef, Transaction tx) { 27 | Layout layout = tblMgr.getLayout(VIEW_CAT_TABLE, tx); 28 | TableScan ts = new TableScan(tx, VIEW_CAT_TABLE, layout); 29 | ts.insert(); 30 | ts.setString(VIEW_CAT_FIELD_NAME, vname); 31 | ts.setString(VIEW_CAT_FIELD_DEF, vdef); 32 | ts.close(); 33 | } 34 | 35 | public String getViewDef(String vname, Transaction tx) { 36 | String result = null; 37 | Layout layout = tblMgr.getLayout(VIEW_CAT_TABLE, tx); 38 | TableScan ts = new TableScan(tx, VIEW_CAT_TABLE, layout); 39 | while (ts.next()) { 40 | if (ts.getString(VIEW_CAT_FIELD_NAME).equals(vname)) { 41 | result = ts.getString(VIEW_CAT_FIELD_DEF); 42 | break; 43 | } 44 | } 45 | ts.close(); 46 | return result; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/ProductScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | /* 4 | * The product relational algebra operator 5 | */ 6 | public class ProductScan implements Scan { 7 | private Scan s1; 8 | private Scan s2; 9 | 10 | public ProductScan(Scan s1, Scan s2) { 11 | this.s1 = s1; 12 | this.s2 = s2; 13 | } 14 | 15 | /* 16 | * The LHS scan is positioned at its first record, and 17 | * the RHS scan is positioned before its first record. 18 | */ 19 | @Override 20 | public void beforeFirst() { 21 | s1.beforeFirst(); 22 | s1.next(); 23 | s2.beforeFirst(); 24 | } 25 | 26 | /* 27 | * Move RHS if there's next record in the inner loop, 28 | * otherwise, move the RHS to the first record and 29 | * increment the LHS position (outer loop) 30 | */ 31 | @Override 32 | public boolean next() { 33 | if (s2.next()) 34 | return true; 35 | else { 36 | s2.beforeFirst(); 37 | return s2.next() && s1.next(); 38 | } 39 | } 40 | 41 | @Override 42 | public int getInt(String fldname) { 43 | if (s1.hasField(fldname)) 44 | return s1.getInt(fldname); 45 | else 46 | return s2.getInt(fldname); 47 | } 48 | 49 | @Override 50 | public String getString(String fldname) { 51 | if (s1.hasField(fldname)) 52 | return s1.getString(fldname); 53 | else 54 | return s2.getString(fldname); 55 | } 56 | 57 | @Override 58 | public Constant getVal(String fldname) { 59 | if (s1.hasField(fldname)) 60 | return s1.getVal(fldname); 61 | else 62 | return s2.getVal(fldname); 63 | } 64 | 65 | @Override 66 | public boolean hasField(String fldname) { 67 | return s1.hasField(fldname) || s2.hasField(fldname); 68 | } 69 | 70 | @Override 71 | public void close() { 72 | s1.close(); 73 | s2.close(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/index/query/IndexSelectScanTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.query; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.mockito.Mockito.times; 5 | import static org.mockito.Mockito.verify; 6 | import static org.mockito.Mockito.when; 7 | 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | import simpledb.index.Index; 14 | import simpledb.query.Constant; 15 | import simpledb.record.RID; 16 | import simpledb.record.TableScan; 17 | 18 | @ExtendWith(MockitoExtension.class) 19 | public class IndexSelectScanTest { 20 | @Mock 21 | private TableScan ts; 22 | 23 | @Mock 24 | private Index idx; 25 | 26 | @Test 27 | public void testIndexSelectScan() { 28 | Constant val = new Constant("test"); 29 | when(ts.getInt("fldint")).thenReturn(1); 30 | when(ts.getString("fldstr")).thenReturn("test"); 31 | when(ts.getVal("fldval")).thenReturn(val); 32 | when(ts.hasField("fld")).thenReturn(true); 33 | when(idx.next()).thenReturn(true, true, false); 34 | when(idx.getDataRid()).thenReturn(new RID(1, 1), new RID(1, 2)); 35 | 36 | IndexSelectScan idxSelectScan = new IndexSelectScan(ts, idx, val); 37 | 38 | verify(idx).beforeFirst(val); 39 | 40 | while (idxSelectScan.next()) 41 | idxSelectScan.getInt("fldint"); 42 | 43 | verify(idx, times(3)).next(); 44 | verify(idx, times(2)).getDataRid(); 45 | 46 | assertEquals(1, idxSelectScan.getInt("fldint")); 47 | assertEquals("test", idxSelectScan.getString("fldstr")); 48 | assertEquals(val, idxSelectScan.getVal("fldval")); 49 | assertEquals(true, idxSelectScan.hasField("fld")); 50 | 51 | idxSelectScan.close(); 52 | verify(idx).close(); 53 | verify(ts).close(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/record/Schema.java: -------------------------------------------------------------------------------- 1 | package simpledb.record; 2 | 3 | import static java.sql.Types.INTEGER; 4 | import static java.sql.Types.VARCHAR; 5 | 6 | import java.util.ArrayList; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | /* 12 | * The record schema of a table. 13 | * A schema contains the name, type, and the length of varchar field 14 | */ 15 | public class Schema { 16 | private List fields = new ArrayList<>(); 17 | private Map info = new HashMap<>(); 18 | 19 | public void addField(String fldname, int type, int length) { 20 | fields.add(fldname); 21 | info.put(fldname, new FieldInfo(type, length)); 22 | } 23 | 24 | public void addIntField(String fldname) { 25 | addField(fldname, INTEGER, 0); 26 | } 27 | 28 | public void addStringField(String fldname, int length) { 29 | addField(fldname, VARCHAR, length); 30 | } 31 | 32 | /* 33 | * add existing schema's field to this shema 34 | */ 35 | public void add(String fldname, Schema schema) { 36 | int type = schema.type(fldname); 37 | int length = schema.length(fldname); 38 | addField(fldname, type, length); 39 | } 40 | 41 | public void addAll(Schema schema) { 42 | for (String fldname : schema.fields()) 43 | add(fldname, schema); 44 | } 45 | 46 | public List fields() { 47 | return fields; 48 | } 49 | 50 | public boolean hasField(String fldname) { 51 | return fields.contains(fldname); 52 | } 53 | 54 | public int type(String fldname) { 55 | return info.get(fldname).type; 56 | } 57 | 58 | public int length(String fldname) { 59 | return info.get(fldname).length; 60 | } 61 | 62 | class FieldInfo { 63 | int type; 64 | int length; 65 | 66 | public FieldInfo(int type, int length) { 67 | this.type = type; 68 | this.length = length; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/recovery/SetIntRecord.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.recovery; 2 | 3 | import simpledb.file.BlockId; 4 | import simpledb.file.Page; 5 | import simpledb.log.LogMgr; 6 | import simpledb.tx.Transaction; 7 | 8 | public class SetIntRecord implements LogRecord { 9 | private int txnum; 10 | private int offset; 11 | private int val; 12 | private BlockId blk; 13 | 14 | public SetIntRecord(Page p) { 15 | int tpos = Integer.BYTES; 16 | txnum = p.getInt(tpos); 17 | int fpos = tpos + Integer.BYTES; 18 | String filename = p.getString(fpos); 19 | int bpos = fpos + Page.maxLength(filename.length()); 20 | int blknum = p.getInt(bpos); 21 | blk = new BlockId(filename, blknum); 22 | int opos = bpos + Integer.BYTES; 23 | offset = p.getInt(opos); 24 | int vpos = opos + Integer.BYTES; 25 | val = p.getInt(vpos); 26 | } 27 | 28 | public int op() { 29 | return SETINT; 30 | } 31 | 32 | public int txNumber() { 33 | return txnum; 34 | } 35 | 36 | public String toString() { 37 | return ""; 38 | } 39 | 40 | public void undo(Transaction tx) { 41 | tx.pin(blk); 42 | tx.setInt(blk, offset, val, false); 43 | tx.unpin(blk); 44 | } 45 | 46 | public static int writeToLog(LogMgr lm, int txnum, BlockId blk, int offset, int val) { 47 | int tpos = Integer.BYTES; 48 | int fpos = tpos + Integer.BYTES; 49 | int bpos = fpos + Page.maxLength(blk.fileName().length()); 50 | int opos = bpos + Integer.BYTES; 51 | int vpos = opos + Integer.BYTES; 52 | byte[] rec = new byte[vpos + Integer.BYTES]; 53 | Page p = new Page(rec); 54 | p.setInt(0, SETINT); 55 | p.setInt(tpos, txnum); 56 | p.setString(fpos, blk.fileName()); 57 | p.setInt(bpos, blk.number()); 58 | p.setInt(opos, offset); 59 | p.setInt(vpos, val); 60 | return lm.append(rec); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /simpledb/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * This generated file contains a sample Java application project to get you started. 5 | * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle 6 | * User Manual available at https://docs.gradle.org/7.5.1/userguide/building_java_projects.html 7 | */ 8 | 9 | plugins { 10 | // Apply the application plugin to add support for building a CLI application in Java. 11 | application 12 | } 13 | 14 | repositories { 15 | // Use Maven Central for resolving dependencies. 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | // Use JUnit Jupiter for testing. 21 | testImplementation("org.junit.jupiter:junit-jupiter:5.8.2") 22 | 23 | // mockito 24 | testImplementation("org.mockito:mockito-core:3.6.0") 25 | 26 | // mockito JUnit 5 Extension 27 | testImplementation("org.mockito:mockito-junit-jupiter:3.6.0") 28 | 29 | // This dependency is used by the application. 30 | implementation("com.google.guava:guava:31.0.1-jre") 31 | } 32 | 33 | application { 34 | // Define the main class for the application. 35 | mainClass.set("simpledb.App") 36 | } 37 | 38 | tasks.named("test") { 39 | // Use JUnit Platform for unit tests. 40 | useJUnitPlatform() 41 | } 42 | 43 | task("startServer", JavaExec::class) { 44 | group = "jdbc" 45 | mainClass.value("simpledb.server.StartServer") 46 | classpath = sourceSets["main"].runtimeClasspath 47 | } 48 | 49 | task("networkclient", JavaExec::class) { 50 | group = "jdbc" 51 | mainClass.value("simpledb.client.network.JdbcNetworkDriverExample") 52 | classpath = sourceSets["main"].runtimeClasspath 53 | } 54 | 55 | task("embeddedclient", JavaExec::class) { 56 | group = "jdbc" 57 | mainClass.value("simpledb.client.network.JdbcEmbeddedDriverExample") 58 | classpath = sourceSets["main"].runtimeClasspath 59 | } 60 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/metadata/MetadataMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.metadata; 2 | 3 | import java.util.Map; 4 | 5 | import simpledb.record.Layout; 6 | import simpledb.record.Schema; 7 | import simpledb.tx.Transaction; 8 | 9 | /* 10 | * Metadata Manager holds the four managers 11 | * 1. table manager 12 | * 2. view manager 13 | * 3. stat manager 14 | * 4. index manager 15 | */ 16 | public class MetadataMgr { 17 | private static TableMgr tblmgr; 18 | private static ViewMgr viewmgr; 19 | private static StatMgr statmgr; 20 | private static IndexMgr idxmgr; 21 | 22 | public MetadataMgr(boolean isnew, Transaction tx) { 23 | tblmgr = new TableMgr(isnew, tx); 24 | viewmgr = new ViewMgr(isnew, tblmgr, tx); 25 | statmgr = new StatMgr(tblmgr, tx); 26 | idxmgr = new IndexMgr(isnew, tblmgr, statmgr, tx); 27 | } 28 | 29 | public void createTable(String tblname, Schema sch, Transaction tx) { 30 | tblmgr.createTable(tblname, sch, tx); 31 | } 32 | 33 | public Layout getLayout(String tblname, Transaction tx) { 34 | return tblmgr.getLayout(tblname, tx); 35 | } 36 | 37 | public void createView(String viewname, String viewdef, Transaction tx) { 38 | viewmgr.createView(viewname, viewdef, tx); 39 | } 40 | 41 | public String getViewDef(String viewname, Transaction tx) { 42 | return viewmgr.getViewDef(viewname, tx); 43 | } 44 | 45 | public void createIndex(String idxname, String tblname, String fldname, Transaction tx) { 46 | idxmgr.creatIndex(idxname, tblname, fldname, tx); 47 | } 48 | 49 | /* 50 | * Return Map with field name as key and IndexInfo as value 51 | * for the given table. 52 | */ 53 | public Map getIndexInfo(String tblname, Transaction tx) { 54 | return idxmgr.getIndexInfo(tblname, tx); 55 | } 56 | 57 | public StatInfo getStatInfo(String tblname, Layout layout, Transaction tx) { 58 | return statmgr.getStatInfo(tblname, layout, tx); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/GroupByPlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import java.util.List; 4 | 5 | import simpledb.plan.Plan; 6 | import simpledb.query.Scan; 7 | import simpledb.record.Schema; 8 | import simpledb.tx.Transaction; 9 | 10 | public class GroupByPlan implements Plan { 11 | private Plan p; 12 | private List groupfields; 13 | private List aggfns; 14 | private Schema sch = new Schema(); // contains groupfields & aggregation fields 15 | 16 | public GroupByPlan(Transaction tx, Plan p, List groupfields, List aggfns) { 17 | this.p = new SortPlan(tx, p, groupfields); 18 | this.groupfields = groupfields; 19 | this.aggfns = aggfns; 20 | for (String fldname : groupfields) 21 | sch.add(fldname, p.schema()); 22 | for (AggregationFn fn : aggfns) 23 | sch.addIntField(fn.fieldName()); 24 | } 25 | 26 | @Override 27 | public Scan open() { 28 | Scan s = p.open(); 29 | return new GroupByScan(s, groupfields, aggfns); 30 | } 31 | 32 | @Override 33 | public int blockAccessed() { 34 | return p.blockAccessed(); 35 | } 36 | 37 | @Override 38 | public int recordsOutput() { 39 | int numgroups = 1; 40 | for (String fldname : groupfields) 41 | numgroups += p.distinctValues(fldname); 42 | return numgroups; 43 | } 44 | 45 | @Override 46 | public int distinctValues(String fldname) { 47 | if (p.schema().hasField(fldname)) 48 | return p.distinctValues(fldname); 49 | else 50 | return recordsOutput(); 51 | } 52 | 53 | /* 54 | * Return the schema of the output table. 55 | * The schema consists of the group fields and 56 | * aggregation result fields. 57 | */ 58 | @Override 59 | public Schema schema() { 60 | return sch; 61 | } 62 | 63 | /* 64 | * No cost in preprocessing 65 | */ 66 | @Override 67 | public int preprocessingCost() { 68 | return 0; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/index/planner/IndexJoinPlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.planner; 2 | 3 | import simpledb.index.Index; 4 | import simpledb.index.query.IndexJoinScan; 5 | import simpledb.metadata.IndexInfo; 6 | import simpledb.plan.Plan; 7 | import simpledb.query.Scan; 8 | import simpledb.record.Schema; 9 | import simpledb.record.TableScan; 10 | 11 | public class IndexJoinPlan implements Plan { 12 | private Plan p1; 13 | private Plan p2; 14 | private IndexInfo ii; 15 | private String joinfield; 16 | private Schema sch = new Schema(); 17 | 18 | public IndexJoinPlan(Plan p1, Plan p2, IndexInfo ii, String joinfield) { 19 | this.p1 = p1; 20 | this.p2 = p2; 21 | this.ii = ii; 22 | this.joinfield = joinfield; 23 | sch.addAll(p1.schema()); 24 | sch.addAll(p2.schema()); 25 | } 26 | 27 | /* 28 | * RHS: TableScan 29 | * LHS: index 30 | */ 31 | @Override 32 | public Scan open() { 33 | Scan s = p1.open(); 34 | TableScan ts = (TableScan) p2.open(); 35 | Index idx = ii.open(); 36 | return new IndexJoinScan(s, idx, joinfield, ts); 37 | } 38 | 39 | /* 40 | * B(indexjoin(p1,p2,idx)) = B(p1) + R(p1)*B(idx) + R(idexjoin(p1,p2,idx)) 41 | */ 42 | @Override 43 | public int blockAccessed() { 44 | return p1.blockAccessed() 45 | + (p1.recordsOutput() * ii.blocksAccessed()) 46 | + recordsOutput(); 47 | } 48 | 49 | /* 50 | * R(indexjoin(p1,p2,idx)) = R(p1)*R(idx) 51 | */ 52 | @Override 53 | public int recordsOutput() { 54 | return p1.recordsOutput() * ii.recordsOutput(); 55 | } 56 | 57 | @Override 58 | public int distinctValues(String fldname) { 59 | if (p1.schema().hasField(fldname)) 60 | return p1.distinctValues(fldname); 61 | else 62 | return p2.distinctValues(fldname); 63 | } 64 | 65 | @Override 66 | public Schema schema() { 67 | return sch; 68 | } 69 | 70 | @Override 71 | public int preprocessingCost() { 72 | return 0; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/Planner.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import simpledb.parse.CreateIndexData; 4 | import simpledb.parse.CreateTableData; 5 | import simpledb.parse.CreateViewData; 6 | import simpledb.parse.DeleteData; 7 | import simpledb.parse.InsertData; 8 | import simpledb.parse.ModifyData; 9 | import simpledb.parse.Parser; 10 | import simpledb.parse.QueryData; 11 | import simpledb.tx.Transaction; 12 | 13 | public class Planner { 14 | private QueryPlanner qplanner; 15 | private UpdatePlanner uplanner; 16 | 17 | public Planner(QueryPlanner qplanner, UpdatePlanner uplanner) { 18 | this.qplanner = qplanner; 19 | this.uplanner = uplanner; 20 | } 21 | 22 | public Plan createQueryPlan(String qry, Transaction tx) { 23 | Parser parser = new Parser(qry); 24 | QueryData data = parser.query(); 25 | verifyQuery(data); 26 | return qplanner.createPlan(data, tx); 27 | } 28 | 29 | public int executeUpdate(String cmd, Transaction tx) { 30 | Parser parser = new Parser(cmd); 31 | Object data = parser.updateCmd(); 32 | verifyUpdate(data); 33 | if (data instanceof InsertData) 34 | return uplanner.executeInsert((InsertData) data, tx); 35 | else if (data instanceof DeleteData) 36 | return uplanner.executeDelete((DeleteData) data, tx); 37 | else if (data instanceof ModifyData) 38 | return uplanner.executeModify((ModifyData) data, tx); 39 | else if (data instanceof CreateTableData) 40 | return uplanner.executeCreateTable((CreateTableData) data, tx); 41 | else if (data instanceof CreateViewData) 42 | return uplanner.executeCreateView((CreateViewData) data, tx); 43 | else if (data instanceof CreateIndexData) 44 | return uplanner.executeCreateIndex((CreateIndexData) data, tx); 45 | else 46 | return 0; 47 | } 48 | 49 | private void verifyQuery(QueryData data) { 50 | // TODO 51 | } 52 | 53 | private void verifyUpdate(Object data) { 54 | // TODO 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/file/Page.java: -------------------------------------------------------------------------------- 1 | package simpledb.file; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.charset.Charset; 5 | import java.nio.charset.StandardCharsets; 6 | 7 | public class Page { 8 | private ByteBuffer bb; 9 | public static Charset CHARSET = StandardCharsets.US_ASCII; 10 | 11 | // for data buffer 12 | public Page(int blocksize) { 13 | bb = ByteBuffer.allocateDirect(blocksize); 14 | } 15 | 16 | // for log pages 17 | public Page(byte[] b) { 18 | bb = ByteBuffer.wrap(b); 19 | } 20 | 21 | public int getInt(int offset) { 22 | return bb.getInt(offset); 23 | } 24 | 25 | public void setInt(int offset, int n) { 26 | bb.putInt(offset, n); 27 | } 28 | 29 | public byte[] getBytes(int offset) { 30 | bb.position(offset); 31 | int length = bb.getInt(); 32 | byte[] b = new byte[length]; 33 | bb.get(b); 34 | return b; 35 | } 36 | 37 | /* 38 | * Save blob as two values: the length and the bytes themselves 39 | */ 40 | public void setBytes(int offset, byte[] b) { 41 | bb.position(offset); 42 | bb.putInt(b.length); 43 | bb.put(b); 44 | } 45 | 46 | public String getString(int offset) { 47 | byte[] b = getBytes(offset); 48 | return new String(b, CHARSET); 49 | } 50 | 51 | /* 52 | * Save string as blob 53 | */ 54 | public void setString(int offset, String s) { 55 | byte[] b = s.getBytes(CHARSET); 56 | setBytes(offset, b); 57 | } 58 | 59 | /* 60 | * The byte representation of a string depends on character encoding. 61 | * Multiply the maximum number of bytes per char by the number of characters 62 | * and add the first 4 bytes for an integer to store the length of the bytes. 63 | */ 64 | public static int maxLength(int strlen) { 65 | float bytesPerChar = CHARSET.newEncoder().maxBytesPerChar(); 66 | return Integer.BYTES + (strlen * (int) bytesPerChar); 67 | } 68 | 69 | ByteBuffer contents() { 70 | bb.position(0); 71 | return bb; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/MergeJoinPlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import simpledb.plan.Plan; 7 | import simpledb.query.Scan; 8 | import simpledb.record.Schema; 9 | import simpledb.tx.Transaction; 10 | 11 | public class MergeJoinPlan implements Plan { 12 | private Plan p1, p2; 13 | private String fldname1; 14 | private String fldname2; 15 | private Schema sch = new Schema(); 16 | 17 | public MergeJoinPlan(Transaction tx, Plan p1, Plan p2, String fldname1, String fldname2) { 18 | this.fldname1 = fldname1; 19 | List sortlist1 = Arrays.asList(fldname1); 20 | this.p1 = new SortPlan(tx, p1, sortlist1); 21 | 22 | this.fldname2 = fldname2; 23 | List sortlist2 = Arrays.asList(fldname2); 24 | this.p2 = new SortPlan(tx, p2, sortlist2); 25 | 26 | sch.addAll(p1.schema()); 27 | sch.addAll(p2.schema()); 28 | } 29 | 30 | @Override 31 | public Scan open() { 32 | Scan s1 = p1.open(); 33 | SortScan s2 = (SortScan) p2.open(); 34 | return new MergeJoinScan(s1, s2, fldname1, fldname2); 35 | } 36 | 37 | @Override 38 | public int blockAccessed() { 39 | return p1.blockAccessed() + p2.blockAccessed(); 40 | } 41 | 42 | @Override 43 | public int recordsOutput() { 44 | int maxvals = Math.max(p1.distinctValues(fldname1), 45 | p2.distinctValues(fldname2)); 46 | return (p1.recordsOutput() * p2.recordsOutput()) / maxvals; 47 | } 48 | 49 | @Override 50 | public int distinctValues(String fldname) { 51 | if (p1.schema().hasField(fldname)) 52 | return p1.distinctValues(fldname); 53 | else 54 | return p2.distinctValues(fldname); 55 | } 56 | 57 | @Override 58 | public Schema schema() { 59 | return sch; 60 | } 61 | 62 | /* 63 | * Ref. 13.6.1 (p388) 64 | * sort the two plans 65 | */ 66 | @Override 67 | public int preprocessingCost() { 68 | return p1.preprocessingCost() + p2.preprocessingCost(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/recovery/SetStringRecord.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.recovery; 2 | 3 | import simpledb.file.BlockId; 4 | import simpledb.file.Page; 5 | import simpledb.log.LogMgr; 6 | import simpledb.tx.Transaction; 7 | 8 | public class SetStringRecord implements LogRecord { 9 | private int txnum; 10 | private int offset; 11 | private String val; 12 | private BlockId blk; 13 | 14 | public SetStringRecord(Page p) { 15 | int tpos = Integer.BYTES; // Transaction Position 16 | txnum = p.getInt(tpos); 17 | int fpos = tpos + Integer.BYTES; // Filename position 18 | String filename = p.getString(fpos); 19 | int bpos = fpos + Page.maxLength(filename.length()); // Block Position 20 | int blknum = p.getInt(bpos); 21 | blk = new BlockId(filename, blknum); 22 | int opos = bpos + Integer.BYTES; // offset position 23 | offset = p.getInt(opos); 24 | int vpos = opos + Integer.BYTES; // value position 25 | val = p.getString(vpos); 26 | } 27 | 28 | public int op() { 29 | return SETSTRING; 30 | } 31 | 32 | public int txNumber() { 33 | return txnum; 34 | } 35 | 36 | public String toString() { 37 | return ""; 38 | } 39 | 40 | public void undo(Transaction tx) { 41 | tx.pin(blk); 42 | tx.setString(blk, offset, val, false); 43 | tx.unpin(blk); 44 | } 45 | 46 | public static int writeToLog(LogMgr lm, int txnum, BlockId blk, int offset, String val) { 47 | int tpos = Integer.BYTES; 48 | int fpos = tpos + Integer.BYTES; 49 | int bpos = fpos + Page.maxLength(blk.fileName().length()); 50 | int opos = bpos + Integer.BYTES; 51 | int vpos = opos + Integer.BYTES; 52 | int reclen = vpos + Page.maxLength(val.length()); 53 | byte[] rec = new byte[reclen]; 54 | Page p = new Page(rec); 55 | p.setInt(0, SETSTRING); 56 | p.setInt(tpos, txnum); 57 | p.setString(fpos, blk.fileName()); 58 | p.setInt(bpos, blk.number()); 59 | p.setInt(opos, offset); 60 | p.setString(vpos, val); 61 | return lm.append(rec); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/concurrency/ConcurrencyMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.concurrency; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import simpledb.file.BlockId; 7 | 8 | /* 9 | * Concurrency Manager implements lock protocol using block-level granularity. 10 | * and created for a transaction. The same lock table needs to be shared. 11 | */ 12 | public class ConcurrencyMgr { 13 | /* 14 | * The lock table is shared among all Concurrency Manager as it's a static 15 | * variable 16 | */ 17 | private static LockTable locktbl = new LockTable(); 18 | /* 19 | * The lock state of block: 20 | * S if THE transaction holds slock on the block 21 | * X if THE transaction holds xlock on the block 22 | */ 23 | private Map locks = new HashMap<>(); 24 | 25 | /* 26 | * Shared Lock 27 | */ 28 | public void sLock(BlockId blk) { 29 | locks.computeIfAbsent(blk, k -> { 30 | System.out 31 | .println("[ConcurrencyMgr] starting new sLock on file: " + blk.fileName() + ", blk: " + blk.number() + ". (" 32 | + toString() + ")"); 33 | locktbl.sLock(k); 34 | return "S"; 35 | }); 36 | } 37 | 38 | /* 39 | * Exclusive Lock 40 | * If the block doesn't have xlock, firstly get sLock and them promote to xlock 41 | */ 42 | public void xLock(BlockId blk) { 43 | if (!hasXLock(blk)) { 44 | System.out.println("[ConcurrencyMgr] starting new xLock on " + blk.fileName() + ", blk: " + blk.number() + ". (" 45 | + toString() + ")"); 46 | sLock(blk); 47 | locktbl.xLock(blk); 48 | locks.put(blk, "X"); 49 | } 50 | } 51 | 52 | /* 53 | * Release all locks 54 | */ 55 | public void release() { 56 | for (BlockId blk : locks.keySet()) 57 | locktbl.unlock(blk); 58 | locks.clear(); 59 | System.out.println("[ConcurrencyMgr] completed release: " + toString()); 60 | } 61 | 62 | private boolean hasXLock(BlockId blk) { 63 | String locktype = locks.get(blk); 64 | return locktype != null && locktype.equals("X"); 65 | } 66 | 67 | public String toString() { 68 | return locks.toString(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/metadata/StatMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.metadata; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import simpledb.record.Layout; 7 | import simpledb.record.TableScan; 8 | import simpledb.tx.Transaction; 9 | 10 | /* 11 | * Stat manager stores statistical information of each table 12 | * in tablestats (in memory) not in the database. 13 | * It calculates the stats on system startup and every 100 retrievals. 14 | */ 15 | public class StatMgr { 16 | private TableMgr tblMgr; 17 | // Store stats for each table 18 | private Map tablestats; 19 | // used to determine if stats should be updated 20 | private int numcalls; 21 | 22 | public StatMgr(TableMgr tblMgr, Transaction tx) { 23 | this.tblMgr = tblMgr; 24 | refreshStats(tx); 25 | } 26 | 27 | public synchronized StatInfo getStatInfo(String tblname, Layout layout, Transaction tx) { 28 | numcalls++; 29 | if (numcalls > 100) 30 | refreshStats(tx); 31 | 32 | StatInfo si = tablestats.get(tblname); 33 | if (si == null) { 34 | si = calcTableStats(tblname, layout, tx); 35 | tablestats.put(tblname, si); 36 | } 37 | return si; 38 | } 39 | 40 | private synchronized void refreshStats(Transaction tx) { 41 | tablestats = new HashMap<>(); 42 | numcalls = 0; 43 | Layout tcatlayout = tblMgr.getLayout(TableMgr.TBL_CAT_TABLE, tx); 44 | TableScan tcat = new TableScan(tx, TableMgr.TBL_CAT_TABLE, tcatlayout); 45 | while (tcat.next()) { 46 | String tblname = tcat.getString(TableMgr.TBL_CAT_FIELD_TABLE_NAME); 47 | Layout layout = tblMgr.getLayout(tblname, tx); 48 | StatInfo si = calcTableStats(tblname, layout, tx); 49 | tablestats.put(tblname, si); 50 | } 51 | tcat.close(); 52 | } 53 | 54 | private synchronized StatInfo calcTableStats(String tblname, Layout layout, Transaction tx) { 55 | int numRecs = 0; 56 | int numBlocks = 0; 57 | TableScan ts = new TableScan(tx, tblname, layout); 58 | while (ts.next()) { 59 | numRecs++; 60 | numBlocks = ts.getRid().blockNumber() + 1; 61 | } 62 | ts.close(); 63 | return new StatInfo(numBlocks, numRecs); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/SelectScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | import simpledb.record.RID; 4 | 5 | /* 6 | * Select ralational algebra operator. 7 | * All methods except nect delegate their work to the underlying scan. 8 | */ 9 | public class SelectScan implements UpdateScan { 10 | private Scan s; 11 | private Predicate pred; 12 | 13 | public SelectScan(Scan s, Predicate pred) { 14 | this.s = s; 15 | this.pred = pred; 16 | } 17 | 18 | @Override 19 | public void beforeFirst() { 20 | s.beforeFirst(); 21 | } 22 | 23 | @Override 24 | public boolean next() { 25 | while (s.next()) 26 | if (pred.isSatisfied(s)) 27 | return true; 28 | return false; 29 | } 30 | 31 | @Override 32 | public int getInt(String fldname) { 33 | return s.getInt(fldname); 34 | } 35 | 36 | @Override 37 | public String getString(String fldname) { 38 | return s.getString(fldname); 39 | } 40 | 41 | @Override 42 | public Constant getVal(String fldname) { 43 | return s.getVal(fldname); 44 | } 45 | 46 | @Override 47 | public boolean hasField(String fldname) { 48 | return s.hasField(fldname); 49 | } 50 | 51 | @Override 52 | public void close() { 53 | s.close(); 54 | } 55 | 56 | @Override 57 | public void setVal(String fldname, Constant val) { 58 | UpdateScan us = (UpdateScan) s; 59 | us.setVal(fldname, val); 60 | } 61 | 62 | @Override 63 | public void setInt(String fldname, int val) { 64 | UpdateScan us = (UpdateScan) s; 65 | us.setInt(fldname, val); 66 | } 67 | 68 | @Override 69 | public void setString(String fldname, String val) { 70 | UpdateScan us = (UpdateScan) s; 71 | us.setString(fldname, val); 72 | } 73 | 74 | @Override 75 | public void insert() { 76 | UpdateScan us = (UpdateScan) s; 77 | us.insert(); 78 | } 79 | 80 | @Override 81 | public void delete() { 82 | UpdateScan us = (UpdateScan) s; 83 | us.delete(); 84 | } 85 | 86 | @Override 87 | public RID getRid() { 88 | UpdateScan us = (UpdateScan) s; 89 | return us.getRid(); 90 | } 91 | 92 | @Override 93 | public void moveToRid(RID rid) { 94 | UpdateScan us = (UpdateScan) s; 95 | us.moveToRid(rid); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/server/SimpleDB.java: -------------------------------------------------------------------------------- 1 | package simpledb.server; 2 | 3 | import java.io.File; 4 | 5 | import simpledb.buffer.BufferMgr; 6 | import simpledb.file.FileMgr; 7 | import simpledb.index.planner.IndexUpdatePlanner; 8 | import simpledb.log.LogMgr; 9 | import simpledb.metadata.MetadataMgr; 10 | import simpledb.plan.BasicQueryPlanner; 11 | import simpledb.plan.Planner; 12 | import simpledb.plan.QueryPlanner; 13 | import simpledb.plan.UpdatePlanner; 14 | import simpledb.tx.Transaction; 15 | 16 | public class SimpleDB { 17 | public static int BLOCK_SIZE = 400; 18 | public static int BUFFER_SIZE = 8; 19 | public static String LOG_FILE = "simpledb.log"; 20 | 21 | private FileMgr fm; 22 | private BufferMgr bm; 23 | private LogMgr lm; 24 | private MetadataMgr mdm; 25 | private Planner planner; 26 | 27 | /* 28 | * A constructor useful for debugging 29 | */ 30 | public SimpleDB(String dirname, int blocksize, int buffsize) { 31 | File dbDirectory = new File(dirname); 32 | fm = new FileMgr(dbDirectory, blocksize); 33 | lm = new LogMgr(fm, LOG_FILE); 34 | bm = new BufferMgr(fm, lm, buffsize); 35 | } 36 | 37 | /* 38 | * Simple constructor 39 | */ 40 | public SimpleDB(String dirname) { 41 | this(dirname, BLOCK_SIZE, BUFFER_SIZE); 42 | Transaction tx = newTx(); 43 | boolean isnew = fm.isNew(); 44 | if (isnew) 45 | System.out.println("creating new database"); 46 | else { 47 | System.out.println("recovering existing database"); 48 | tx.recover(); 49 | } 50 | mdm = new MetadataMgr(isnew, tx); 51 | QueryPlanner qp = new BasicQueryPlanner(mdm); 52 | UpdatePlanner up = new IndexUpdatePlanner(mdm); 53 | planner = new Planner(qp, up); 54 | tx.commit(); 55 | } 56 | 57 | public Transaction newTx() { 58 | return new Transaction(fm, lm, bm); 59 | } 60 | 61 | public MetadataMgr mdMgr() { 62 | return mdm; 63 | } 64 | 65 | public Planner planner() { 66 | return planner; 67 | } 68 | 69 | // These methods are for debugging 70 | public FileMgr fileMgr() { 71 | return fm; 72 | } 73 | 74 | public LogMgr logMgr() { 75 | return lm; 76 | } 77 | 78 | public BufferMgr bufferMgr() { 79 | return bm; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/index/query/IndexJoinScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.query; 2 | 3 | import simpledb.index.Index; 4 | import simpledb.query.Constant; 5 | import simpledb.query.Scan; 6 | import simpledb.record.TableScan; 7 | 8 | /* 9 | * LHS: Scan 10 | * RHS: TableScan + index 11 | */ 12 | public class IndexJoinScan implements Scan { 13 | private Scan lhs; 14 | private Index idx; 15 | private String joinfield; 16 | private TableScan rhs; 17 | 18 | public IndexJoinScan(Scan s, Index idx, String joinfield, TableScan ts) { 19 | this.lhs = s; 20 | this.idx = idx; 21 | this.joinfield = joinfield; 22 | this.rhs = ts; 23 | beforeFirst(); 24 | } 25 | 26 | @Override 27 | public void beforeFirst() { 28 | lhs.beforeFirst(); 29 | lhs.next(); 30 | resetIndex(); 31 | } 32 | 33 | /* 34 | * move to the next index record if possible 35 | * otherwise move to the next LHS record and the first index record 36 | */ 37 | @Override 38 | public boolean next() { 39 | while (true) { 40 | if (idx.next()) { 41 | rhs.moveToRid(idx.getDataRid()); 42 | return true; 43 | } 44 | if (!lhs.next()) 45 | return false; 46 | resetIndex(); 47 | } 48 | } 49 | 50 | @Override 51 | public int getInt(String fldname) { 52 | if (rhs.hasField(fldname)) 53 | return rhs.getInt(fldname); 54 | else 55 | return lhs.getInt(fldname); 56 | } 57 | 58 | @Override 59 | public String getString(String fldname) { 60 | if (rhs.hasField(fldname)) 61 | return rhs.getString(fldname); 62 | else 63 | return lhs.getString(fldname); 64 | } 65 | 66 | @Override 67 | public Constant getVal(String fldname) { 68 | if (rhs.hasField(fldname)) 69 | return rhs.getVal(fldname); 70 | else 71 | return lhs.getVal(fldname); 72 | } 73 | 74 | @Override 75 | public boolean hasField(String fldname) { 76 | return rhs.hasField(fldname) || lhs.hasField(fldname); 77 | } 78 | 79 | @Override 80 | public void close() { 81 | lhs.close(); 82 | idx.close(); 83 | rhs.close(); 84 | } 85 | 86 | private void resetIndex() { 87 | Constant searchkey = lhs.getVal(joinfield); 88 | idx.beforeFirst(searchkey); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/metadata/IndexInfo.java: -------------------------------------------------------------------------------- 1 | package simpledb.metadata; 2 | 3 | import static java.sql.Types.INTEGER; 4 | 5 | import simpledb.index.Index; 6 | import simpledb.index.btree.BTreeIndex; 7 | import simpledb.record.Layout; 8 | import simpledb.record.Schema; 9 | import simpledb.tx.Transaction; 10 | 11 | public class IndexInfo { 12 | private String idxname; 13 | private String fldname; 14 | private Transaction tx; 15 | private Schema tblSchema; 16 | private Layout idxLayout; 17 | private StatInfo si; 18 | 19 | public IndexInfo(String idxname, String fldname, Schema tblSchema, Transaction tx, StatInfo si) { 20 | this.idxname = idxname; 21 | this.fldname = fldname; 22 | this.tx = tx; 23 | this.tblSchema = tblSchema; 24 | this.idxLayout = createIdxLayout(); 25 | this.si = si; 26 | } 27 | 28 | public Index open() { 29 | return new BTreeIndex(tx, idxname, idxLayout); 30 | } 31 | 32 | public int blocksAccessed() { 33 | int rpb = tx.blockSize() / idxLayout.slotSize(); 34 | int numBlocks = si.recordsOutput() / rpb; 35 | return BTreeIndex.searchCost(numBlocks, rpb); 36 | } 37 | 38 | /* 39 | * Return the estimated number of records having a search key. 40 | * The number of distinct values of the indexed fields. 41 | * This estimate will be very poor if not evenly distributed. 42 | */ 43 | public int recordsOutput() { 44 | return si.recordsOutput() / si.distinctValues(fldname); 45 | } 46 | 47 | /* 48 | * Return the distinct values for a specified field or 1 for the indexed field 49 | */ 50 | public int distinctValues(String fname) { 51 | return fldname.equals(fname) ? 1 : si.distinctValues(fldname); 52 | } 53 | 54 | /* 55 | * Return the layout of the index records. 56 | * The schema consists of 57 | * 1. RID (the block number and record id) 58 | * 2. dataval: the type is determined based on the fldname 59 | */ 60 | private Layout createIdxLayout() { 61 | Schema sch = new Schema(); 62 | sch.addIntField("block"); 63 | sch.addIntField("id"); 64 | if (tblSchema.type(fldname) == INTEGER) 65 | sch.addIntField("dataval"); 66 | else { 67 | int fldlen = tblSchema.length(fldname); 68 | sch.addStringField("dataval", fldlen); 69 | } 70 | return new Layout(sch); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/GroupByScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import java.util.List; 4 | 5 | import simpledb.query.Constant; 6 | import simpledb.query.Scan; 7 | 8 | public class GroupByScan implements Scan { 9 | private Scan s; 10 | private List groupfields; 11 | private List aggfns; 12 | private GroupValue groupval; 13 | private boolean moregroups; // boolean to indicates if the underlying scan has records to read 14 | 15 | public GroupByScan(Scan s, List groupfields, List aggfns) { 16 | this.s = s; 17 | this.groupfields = groupfields; 18 | this.aggfns = aggfns; 19 | beforeFirst(); 20 | } 21 | 22 | @Override 23 | public void beforeFirst() { 24 | s.beforeFirst(); 25 | moregroups = s.next(); 26 | } 27 | 28 | /* 29 | * read until a new group value appears. 30 | * moregroups is always true until the underlying scan finishes scanning 31 | */ 32 | @Override 33 | public boolean next() { 34 | if (!moregroups) 35 | return false; 36 | for (AggregationFn fn : aggfns) 37 | fn.processFirst(s); 38 | groupval = new GroupValue(s, groupfields); 39 | while (moregroups = s.next()) { 40 | GroupValue gv = new GroupValue(s, groupfields); 41 | if (!groupval.equals(gv)) 42 | break; 43 | for (AggregationFn fn : aggfns) 44 | fn.processNext(s); 45 | } 46 | return true; 47 | } 48 | 49 | @Override 50 | public int getInt(String fldname) { 51 | return getVal(fldname).asInt(); 52 | } 53 | 54 | @Override 55 | public String getString(String fldname) { 56 | return getVal(fldname).asString(); 57 | } 58 | 59 | @Override 60 | public Constant getVal(String fldname) { 61 | if (groupfields.contains(fldname)) 62 | return groupval.getVal(fldname); 63 | for (AggregationFn fn : aggfns) 64 | if (fn.fieldName().equals(fldname)) 65 | return fn.value(); 66 | throw new RuntimeException("field: " + fldname + " not found."); 67 | } 68 | 69 | @Override 70 | public boolean hasField(String fldname) { 71 | if (groupfields.contains(fldname)) 72 | return true; 73 | for (AggregationFn fn : aggfns) 74 | if (fn.fieldName().equals(fldname)) 75 | return true; 76 | return false; 77 | } 78 | 79 | @Override 80 | public void close() { 81 | s.close(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/metadata/IndexMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.metadata; 2 | 3 | import static simpledb.metadata.TableMgr.MAX_NAME; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import simpledb.record.Layout; 9 | import simpledb.record.Schema; 10 | import simpledb.record.TableScan; 11 | import simpledb.tx.Transaction; 12 | 13 | public class IndexMgr { 14 | private Layout layout; 15 | private TableMgr tblmgr; 16 | private StatMgr statmgr; 17 | private static final String IDX_CAT_TABLE_NAME = "idxcat"; 18 | private static final String IDX_CAT_FIELD_INDEX_NAME = "indexname"; 19 | private static final String IDX_CAT_FIELD_TABLE_NAME = "tablename"; 20 | private static final String IDX_CAT_FIELD_FIELD_NAME = "fieldname"; 21 | 22 | public IndexMgr(boolean isnew, TableMgr tblmgr, StatMgr statmgr, Transaction tx) { 23 | if (isnew) { 24 | Schema sch = new Schema(); 25 | sch.addStringField(IDX_CAT_FIELD_INDEX_NAME, MAX_NAME); 26 | sch.addStringField(IDX_CAT_FIELD_TABLE_NAME, MAX_NAME); 27 | sch.addStringField(IDX_CAT_FIELD_FIELD_NAME, MAX_NAME); 28 | tblmgr.createTable(IDX_CAT_TABLE_NAME, sch, tx); 29 | } 30 | this.tblmgr = tblmgr; 31 | this.statmgr = statmgr; 32 | layout = tblmgr.getLayout(IDX_CAT_TABLE_NAME, tx); 33 | } 34 | 35 | public void creatIndex(String idxname, String tblname, String fldname, Transaction tx) { 36 | TableScan ts = new TableScan(tx, IDX_CAT_TABLE_NAME, layout); 37 | ts.insert(); 38 | ts.setString(IDX_CAT_FIELD_INDEX_NAME, idxname); 39 | ts.setString(IDX_CAT_FIELD_TABLE_NAME, tblname); 40 | ts.setString(IDX_CAT_FIELD_FIELD_NAME, fldname); 41 | ts.close(); 42 | } 43 | 44 | public Map getIndexInfo(String tblname, Transaction tx) { 45 | Map result = new HashMap<>(); 46 | TableScan ts = new TableScan(tx, IDX_CAT_TABLE_NAME, layout); 47 | while (ts.next()) { 48 | if (ts.getString(IDX_CAT_FIELD_TABLE_NAME).equals(tblname)) { 49 | String idxname = ts.getString(IDX_CAT_FIELD_INDEX_NAME); 50 | String fldname = ts.getString(IDX_CAT_FIELD_FIELD_NAME); 51 | Layout tblLayout = tblmgr.getLayout(tblname, tx); 52 | StatInfo tblsi = statmgr.getStatInfo(tblname, tblLayout, tx); 53 | IndexInfo ii = new IndexInfo(idxname, fldname, tblLayout.schema(), tx, tblsi); 54 | result.put(fldname, ii); 55 | } 56 | } 57 | return result; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/buffer/BufferMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.buffer; 2 | 3 | import simpledb.file.BlockId; 4 | import simpledb.file.FileMgr; 5 | import simpledb.log.LogMgr; 6 | 7 | public class BufferMgr { 8 | private Buffer[] bufferpool; 9 | private int numAvailable; 10 | private static final long MAX_TIME = 10000; // 10 seconds 11 | 12 | public BufferMgr(FileMgr fm, LogMgr lm, int numbuffs) { 13 | bufferpool = new Buffer[numbuffs]; 14 | numAvailable = numbuffs; 15 | for (int i = 0; i < numbuffs; i++) 16 | bufferpool[i] = new Buffer(fm, lm); 17 | } 18 | 19 | public synchronized int available() { 20 | return numAvailable; 21 | } 22 | 23 | public synchronized void flushAll(int txnum) { 24 | for (Buffer buff : bufferpool) { 25 | if (buff.modifyingTx() == txnum) 26 | buff.flush(); 27 | } 28 | } 29 | 30 | public synchronized void unpin(Buffer buff) { 31 | buff.unpin(); 32 | if (!buff.isPinned()) { 33 | numAvailable++; 34 | notifyAll(); 35 | } 36 | } 37 | 38 | public synchronized Buffer pin(BlockId blk) { 39 | try { 40 | long timestamp = System.currentTimeMillis(); 41 | Buffer buff = tryToPin(blk); 42 | while (buff == null && !waitingTooLong(timestamp)) { 43 | wait(MAX_TIME); 44 | buff = tryToPin(blk); 45 | } 46 | if (buff == null) 47 | throw new BufferAbortException(); 48 | return buff; 49 | } catch (InterruptedException e) { 50 | throw new BufferAbortException(); 51 | } 52 | } 53 | 54 | private boolean waitingTooLong(long starttime) { 55 | return System.currentTimeMillis() - starttime > MAX_TIME; 56 | } 57 | 58 | private Buffer tryToPin(BlockId blk) { 59 | Buffer buff = findExistingBuffer(blk); 60 | if (buff == null) { 61 | buff = chooseUnpinnedBuffer(); 62 | if (buff == null) 63 | return null; 64 | buff.assignToBlock(blk); 65 | } 66 | if (!buff.isPinned()) 67 | numAvailable--; 68 | buff.pin(); 69 | return buff; 70 | } 71 | 72 | private Buffer findExistingBuffer(BlockId blk) { 73 | for (Buffer buff : bufferpool) { 74 | BlockId b = buff.block(); 75 | if (b != null && b.equals(blk)) 76 | return buff; 77 | } 78 | return null; 79 | } 80 | 81 | private Buffer chooseUnpinnedBuffer() { 82 | for (Buffer buff : bufferpool) 83 | if (!buff.isPinned()) 84 | return buff; 85 | return null; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/Predicate.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Iterator; 5 | import java.util.List; 6 | 7 | import simpledb.plan.Plan; 8 | 9 | /* 10 | * Predicate is a Boolean combination of terms. 11 | */ 12 | public class Predicate { 13 | private List terms = new ArrayList<>(); 14 | 15 | /* 16 | * Create an empty predicate, corresponding to "true". 17 | */ 18 | public Predicate() { 19 | } 20 | 21 | /* 22 | * Create a predicate containing a single term 23 | */ 24 | public Predicate(Term t) { 25 | terms.add(t); 26 | } 27 | 28 | public void conjoinWith(Predicate pred) { 29 | terms.addAll(pred.terms); 30 | } 31 | 32 | public boolean isEmpty() { 33 | return terms.isEmpty(); 34 | } 35 | 36 | /* 37 | * Return true if all the terms are satisfied 38 | * with the specified scan. 39 | */ 40 | public boolean isSatisfied(Scan s) { 41 | for (Term t : terms) 42 | if (!t.isSatisfied(s)) 43 | return false; 44 | return true; 45 | } 46 | 47 | public String toString() { 48 | Iterator iter = terms.iterator(); 49 | if (!iter.hasNext()) 50 | return ""; 51 | String result = iter.next().toString(); 52 | while (iter.hasNext()) 53 | result += " and " + iter.next().toString(); 54 | return result; 55 | } 56 | 57 | /* 58 | * The product of all the term's reduction factors 59 | */ 60 | public int reductionFactor(Plan p) { 61 | int factor = 1; 62 | for (Term t : terms) 63 | factor *= t.reductionFactor(p); 64 | return factor; 65 | } 66 | 67 | /* 68 | * Determine if there is a term of the form "F=c" 69 | * where F is the specified field and c is some constant. 70 | * If true, return the constant, otherwise return null. 71 | */ 72 | public Object equatesWithConstant(String fldname) { 73 | for (Term t : terms) { 74 | Constant c = t.equatesWithConstant(fldname); 75 | if (c != null) 76 | return c; 77 | } 78 | return null; 79 | } 80 | 81 | /* 82 | * Determine if there is a term of the form "F1=F2" 83 | * where F1 is the specified field and F2 is another. 84 | * If true, return the F2 field name, otherwise return null. 85 | */ 86 | public String equatesWithField(String fldname) { 87 | for (Term t : terms) { 88 | String s = t.equatesWithField(fldname); 89 | if (s != null) 90 | return s; 91 | } 92 | return null; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/MaterializePlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import simpledb.plan.Plan; 4 | import simpledb.query.Scan; 5 | import simpledb.query.UpdateScan; 6 | import simpledb.record.Layout; 7 | import simpledb.record.Schema; 8 | import simpledb.tx.Transaction; 9 | 10 | public class MaterializePlan implements Plan { 11 | private Plan srcplan; 12 | private Transaction tx; 13 | 14 | public MaterializePlan(Transaction tx, Plan srcplan) { 15 | this.srcplan = srcplan; 16 | this.tx = tx; 17 | } 18 | 19 | @Override 20 | public Scan open() { 21 | Schema sch = srcplan.schema(); 22 | TempTable temp = new TempTable(tx, sch); 23 | Scan src = srcplan.open(); 24 | UpdateScan dest = temp.open(); 25 | int cnt = 0; 26 | while (src.next()) { 27 | dest.insert(); 28 | for (String fldname : sch.fields()) 29 | dest.setVal(fldname, src.getVal(fldname)); 30 | cnt++; 31 | } 32 | System.out.println("[MaterializePlan] inserted " + cnt + " records to TempTable[" + temp.tableName() + "]"); 33 | src.close(); 34 | dest.beforeFirst(); 35 | return dest; 36 | } 37 | 38 | /* 39 | * Return the estimated number of block access, which 40 | * doesn't include the one-time cost of materializing the records 41 | * (preprocessing cost). 42 | * The estimated value is calculated as the number of source output records divided by 43 | * the number of records per block, which means how many block accesses are required 44 | * to write the source output records to the temporary table. 45 | */ 46 | @Override 47 | public int blockAccessed() { 48 | Layout layout = new Layout(srcplan.schema()); 49 | double rpb = (double) tx.blockSize() / layout.slotSize(); 50 | return (int) Math.ceil(srcplan.recordsOutput() / rpb); 51 | } 52 | 53 | @Override 54 | public int recordsOutput() { 55 | return srcplan.recordsOutput(); 56 | } 57 | 58 | @Override 59 | public int distinctValues(String fldname) { 60 | return srcplan.distinctValues(fldname); 61 | } 62 | 63 | @Override 64 | public Schema schema() { 65 | return srcplan.schema(); 66 | } 67 | 68 | /* 69 | * 1. The cost of the input 70 | * 2. The cost of writing the records 71 | * (Not yet sure how to use preprocessingCost with blockAccessed) 72 | * Ref: 13.3.2. The Cost of Materialization 73 | */ 74 | @Override 75 | public int preprocessingCost() { 76 | return srcplan.blockAccessed() + blockAccessed(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/index/query/IndexJoinScanTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.query; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import static org.mockito.Mockito.when; 7 | 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.jupiter.api.extension.ExtendWith; 10 | import org.mockito.Mock; 11 | import org.mockito.junit.jupiter.MockitoExtension; 12 | 13 | import simpledb.index.Index; 14 | import simpledb.query.Scan; 15 | import simpledb.record.RID; 16 | import simpledb.record.TableScan; 17 | 18 | @ExtendWith(MockitoExtension.class) 19 | public class IndexJoinScanTest { 20 | @Mock 21 | private TableScan lhs; 22 | @Mock 23 | private TableScan rhs; 24 | @Mock 25 | private Index idx; 26 | 27 | /* 28 | * T1: RHS 29 | * fld1 | 30 | * ---| 31 | * 1 | 32 | * 2 | 33 | * T2: LHS 34 | * fld2| 35 | * test1| 36 | * test2| 37 | * join(T1, T2) 38 | * fld1|fld2 39 | * 1 | test1 40 | * 2 | test1 41 | * 1 | test2 42 | * 2 | test2 43 | */ 44 | @Test 45 | public void testIndexJoinScan() { 46 | RID rid1 = new RID(0, 0); 47 | RID rid2 = new RID(0, 1); 48 | when(idx.next()).thenReturn(true, true, true, true, false); // two records twice for index 49 | when(idx.getDataRid()).thenReturn(rid1, rid2); 50 | when(rhs.hasField("T1_fld1")).thenReturn(true); 51 | when(rhs.getInt("T1_fld1")).thenReturn(1, 2, 1, 2); 52 | when(lhs.next()).thenReturn(true, true, false); // two record 53 | when(lhs.getString("T2_fld2")).thenReturn("test1", "test1", "test2", "test2"); 54 | String joinfield = "joinfield"; 55 | Scan s = new IndexJoinScan(lhs, idx, joinfield, rhs); 56 | assertTrue(s.next()); // first lhs record x first index record 57 | assertEquals(1, s.getInt("T1_fld1")); 58 | assertEquals("test1", s.getString("T2_fld2")); 59 | assertTrue(s.next()); // first lhs record x second index record 60 | assertEquals(2, s.getInt("T1_fld1")); 61 | assertEquals("test1", s.getString("T2_fld2")); 62 | assertTrue(s.next()); // second lhs record x first index record 63 | assertEquals(1, s.getInt("T1_fld1")); 64 | assertEquals("test2", s.getString("T2_fld2")); 65 | assertTrue(s.next()); // second lhs record x second index record 66 | assertEquals(2, s.getInt("T1_fld1")); 67 | assertEquals("test2", s.getString("T2_fld2")); 68 | assertFalse(s.next()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/multibuffer/ChunkScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.multibuffer; 2 | 3 | import static java.sql.Types.INTEGER; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | import simpledb.file.BlockId; 9 | import simpledb.query.Constant; 10 | import simpledb.query.Scan; 11 | import simpledb.record.Layout; 12 | import simpledb.record.RecordPage; 13 | import simpledb.tx.Transaction; 14 | 15 | public class ChunkScan implements Scan { 16 | private List buffs = new ArrayList<>(); 17 | private Transaction tx; 18 | private String filename; 19 | private Layout layout; 20 | private int startbnum; 21 | private int endbnum; 22 | private int currentbnum; 23 | private RecordPage rp; 24 | private int currentslot; 25 | 26 | public ChunkScan(Transaction tx, String filename, Layout layout, int startbnum, int endbnum) { 27 | this.tx = tx; 28 | this.filename = filename; 29 | this.layout = layout; 30 | this.startbnum = startbnum; 31 | this.endbnum = endbnum; 32 | for (int i = startbnum; i <= endbnum; i++) { 33 | BlockId blk = new BlockId(filename, i); 34 | buffs.add(new RecordPage(tx, blk, layout)); 35 | } 36 | moveToBlock(startbnum); 37 | } 38 | 39 | public void close() { 40 | for (int i = 0; i < buffs.size(); i++) { 41 | BlockId blk = new BlockId(filename, startbnum + i); 42 | tx.unpin(blk); 43 | } 44 | } 45 | 46 | public void beforeFirst() { 47 | moveToBlock(startbnum); 48 | } 49 | 50 | public boolean next() { 51 | currentslot = rp.nextUsedSlot(currentslot); 52 | while (currentslot < 0) { 53 | if (currentbnum == endbnum) 54 | return false; 55 | 56 | moveToBlock(rp.block().number() + 1); 57 | currentslot = rp.nextUsedSlot(currentslot); 58 | } 59 | return true; 60 | } 61 | 62 | public int getInt(String fldname) { 63 | return rp.getInt(currentslot, fldname); 64 | } 65 | 66 | public String getString(String fldname) { 67 | return rp.getString(currentslot, fldname); 68 | } 69 | 70 | public Constant getVal(String fldname) { 71 | if (layout.schema().type(fldname) == INTEGER) 72 | return new Constant(getInt(fldname)); 73 | else 74 | return new Constant(getString(fldname)); 75 | } 76 | 77 | public boolean hasField(String fldname) { 78 | return layout.schema().hasField(fldname); 79 | } 80 | 81 | private void moveToBlock(int blknum) { 82 | currentbnum = blknum; 83 | rp = buffs.get(currentbnum - startbnum); 84 | currentslot = -1; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/plan/BasicUpdatePlanner.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import java.util.Iterator; 4 | 5 | import simpledb.metadata.MetadataMgr; 6 | import simpledb.parse.CreateIndexData; 7 | import simpledb.parse.CreateTableData; 8 | import simpledb.parse.CreateViewData; 9 | import simpledb.parse.DeleteData; 10 | import simpledb.parse.InsertData; 11 | import simpledb.parse.ModifyData; 12 | import simpledb.query.Constant; 13 | import simpledb.query.UpdateScan; 14 | import simpledb.tx.Transaction; 15 | 16 | public class BasicUpdatePlanner implements UpdatePlanner { 17 | private MetadataMgr mdm; 18 | 19 | public BasicUpdatePlanner(MetadataMgr mdm) { 20 | this.mdm = mdm; 21 | } 22 | 23 | @Override 24 | public int executeDelete(DeleteData data, Transaction tx) { 25 | Plan p = new TablePlan(tx, data.tableName(), mdm); 26 | p = new SelectPlan(p, data.pred()); 27 | UpdateScan us = (UpdateScan) p.open(); 28 | int count = 0; 29 | while (us.next()) { 30 | us.delete(); 31 | count++; 32 | } 33 | us.close(); 34 | return count; 35 | } 36 | 37 | @Override 38 | public int executeModify(ModifyData data, Transaction tx) { 39 | Plan p = new TablePlan(tx, data.tableName(), mdm); 40 | p = new SelectPlan(p, data.pred()); 41 | UpdateScan us = (UpdateScan) p.open(); 42 | int count = 0; 43 | while (us.next()) { 44 | Constant val = data.newValue().evaluate(us); 45 | us.setVal(data.targetField(), val); 46 | count++; 47 | } 48 | us.close(); 49 | return count; 50 | } 51 | 52 | @Override 53 | public int executeInsert(InsertData data, Transaction tx) { 54 | Plan p = new TablePlan(tx, data.tableName(), mdm); 55 | UpdateScan us = (UpdateScan) p.open(); 56 | us.insert(); 57 | Iterator iter = data.vals().iterator(); 58 | for (String fldname : data.fields()) { 59 | Constant val = iter.next(); 60 | us.setVal(fldname, val); 61 | } 62 | us.close(); 63 | return 1; 64 | } 65 | 66 | @Override 67 | public int executeCreateTable(CreateTableData data, Transaction tx) { 68 | mdm.createTable(data.tableName(), data.newSchema(), tx); 69 | return 0; 70 | } 71 | 72 | @Override 73 | public int executeCreateView(CreateViewData data, Transaction tx) { 74 | mdm.createView(data.viewName(), data.viewDef(), tx); 75 | return 0; 76 | } 77 | 78 | @Override 79 | public int executeCreateIndex(CreateIndexData data, Transaction tx) { 80 | mdm.createIndex(data.indexName(), data.tableName(), data.fieldName(), tx); 81 | return 0; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/client/network/JdbcNetworkDriverExample.java: -------------------------------------------------------------------------------- 1 | package simpledb.client.network; 2 | 3 | import java.sql.Connection; 4 | import java.sql.Driver; 5 | import java.sql.ResultSet; 6 | import java.sql.SQLException; 7 | import java.sql.Statement; 8 | 9 | import simpledb.jdbc.network.NetworkDriver; 10 | 11 | public class JdbcNetworkDriverExample { 12 | public static void main(String[] args) { 13 | Driver d = new NetworkDriver(); 14 | String url = "jdbc:simpledb://localhost"; 15 | try (Connection conn = d.connect(url, null); 16 | Statement stmt = conn.createStatement()) { 17 | // 1. create table student 18 | String sql = "create table STUDENT (Sid int, SName varchar(10), MajorId int, GradYear int)"; 19 | stmt.executeUpdate(sql); 20 | 21 | // 2. create index 22 | sql = "create index student_sid_idx on student(sid)"; 23 | stmt.executeUpdate(sql); 24 | 25 | // 3. select tables 26 | sql = "select tblname, slotsize from tblcat"; 27 | ResultSet rs = stmt.executeQuery(sql); 28 | while (rs.next()) 29 | System.out.println(String.format("table: %s, slotsize: %d", rs.getString("tblname"), rs.getInt("slotsize"))); 30 | 31 | // 4. insert record to student table 32 | for (int i = 1; i <= 100; i++) { 33 | int gradYear = 100 + i % 7; 34 | int majorId = i % 13; 35 | String name = "name" + i; 36 | sql = String.format("insert into student(Sid, SName, MajorId, GradYear) values (%d, '%s', %d, %d)", 37 | i, name, majorId, gradYear); 38 | stmt.executeUpdate(sql); 39 | } 40 | 41 | // 5. select records from student table 42 | System.out.println("select all records from student table ------"); 43 | sql = "select Sid, SName, MajorId, GradYear from student"; 44 | rs = stmt.executeQuery(sql); 45 | while (rs.next()) 46 | System.out.println(String.format("Sid: %d, Sname: %s, MajorId: %d, GradYear: %d", rs.getInt("Sid"), 47 | rs.getString("SName"), rs.getInt("MajorId"), rs.getInt("GradYear"))); 48 | 49 | // 6. select records from student table with condition 50 | System.out.println("select records (sid =100) from student table ------"); 51 | sql = "select Sid, SName, MajorId, GradYear from student where sid = 100"; 52 | rs = stmt.executeQuery(sql); 53 | while (rs.next()) 54 | System.out.println(String.format("Sid: %d, Sname: %s, MajorId: %d, GradYear: %d", rs.getInt("Sid"), 55 | rs.getString("SName"), rs.getInt("MajorId"), rs.getInt("GradYear"))); 56 | } catch (SQLException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/log/LogMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.log; 2 | 3 | import java.util.Iterator; 4 | 5 | import simpledb.file.BlockId; 6 | import simpledb.file.FileMgr; 7 | import simpledb.file.Page; 8 | 9 | public class LogMgr { 10 | private FileMgr fm; 11 | private String logfile; 12 | private Page logpage; 13 | private BlockId currentblk; 14 | private int latestLSN = 0; 15 | private int lastSavedLSN = 0; 16 | 17 | public LogMgr(FileMgr fm, String logfile) { 18 | this.fm = fm; 19 | this.logfile = logfile; 20 | byte[] b = new byte[fm.blockSize()]; 21 | logpage = new Page(b); 22 | int logsize = fm.length(logfile); 23 | if (logsize == 0) { 24 | currentblk = appendNewBlock(); // append new block if empty 25 | } else { 26 | currentblk = new BlockId(logfile, logsize - 1); // get the last block 27 | fm.read(currentblk, logpage); // read the current block 28 | } 29 | } 30 | 31 | /* 32 | * Flush only if the specified value is 33 | * large than or equals to the lastSavedLSN 34 | */ 35 | public void flush(int lsn) { 36 | if (lsn >= lastSavedLSN) 37 | flush(); 38 | } 39 | 40 | public Iterator iterator() { 41 | flush(); // why flush here? 42 | return new LogIterator(fm, currentblk); 43 | } 44 | 45 | /* 46 | * Append new content to page. 47 | * Get the boundary from the integer in the position 0 in the page. 48 | * If the current page is not enough, flush and add new block, 49 | * and set it to the current block. The boundary is the blocksize. 50 | * Write the log record from the boudary position and update the record position 51 | * |.....| 52 | */ 53 | public synchronized int append(byte[] logrec) { 54 | int boundary = logpage.getInt(0); // the first integer indicates the position before which new content will be written. 55 | int recsize = logrec.length; 56 | int bytesneeded = recsize + Integer.BYTES; 57 | if (boundary - bytesneeded < Integer.BYTES) { 58 | flush(); 59 | currentblk = appendNewBlock(); 60 | boundary = logpage.getInt(0); 61 | } 62 | int recpos = boundary - bytesneeded; 63 | 64 | logpage.setBytes(recpos, logrec); 65 | logpage.setInt(0, recpos); 66 | latestLSN += 1; 67 | return latestLSN; 68 | } 69 | 70 | /* 71 | * Append a new block, 72 | * writes the blocksize in the position 0, 73 | * and save it to the file 74 | */ 75 | private BlockId appendNewBlock() { 76 | BlockId blk = fm.append(logfile); 77 | logpage.setInt(0, fm.blockSize()); 78 | fm.write(blk, logpage); 79 | return blk; 80 | } 81 | 82 | private void flush() { 83 | fm.write(currentblk, logpage); 84 | lastSavedLSN = latestLSN; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/concurrency/LockTable.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.concurrency; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import simpledb.file.BlockId; 7 | 8 | public class LockTable { 9 | private static final long MAX_TIME = 10000; // 10 seconds 10 | 11 | /* 12 | * The lock state of block: 13 | * -1 if a transaction holds xlock on the block 14 | * the number of transactions that hold slock 15 | */ 16 | private Map locks = new HashMap<>(); 17 | 18 | /* 19 | * Shared Lock 20 | * Set the number of transactions that hold shared lock on the block 21 | */ 22 | public synchronized void sLock(BlockId blk) { 23 | try { 24 | long timestamp = System.currentTimeMillis(); 25 | while (hasXLock(blk) && !waitingTooLong(timestamp)) 26 | wait(MAX_TIME); 27 | if (hasXLock(blk)) 28 | throw new LockAbortException(); 29 | 30 | int val = getLockVal(blk); 31 | locks.put(blk, val + 1); 32 | } catch (InterruptedException e) { 33 | throw new LockAbortException(); 34 | } 35 | } 36 | 37 | /* 38 | * Exclusive Lock 39 | * Set -1 for the block 40 | */ 41 | synchronized void xLock(BlockId blk) { 42 | try { 43 | long timestamp = System.currentTimeMillis(); 44 | while (hasOtherSLocks(blk) && !waitingTooLong(timestamp)) 45 | wait(MAX_TIME); 46 | if (hasOtherSLocks(blk)) 47 | throw new LockAbortException(); 48 | locks.put(blk, -1); 49 | } catch (InterruptedException e) { 50 | throw new LockAbortException(); 51 | } 52 | System.out.println("[LockTable] completed xlock on blk: " + blk.number() + ", status: " + lockStatusName(blk)); 53 | } 54 | 55 | synchronized void unlock(BlockId blk) { 56 | int val = getLockVal(blk); 57 | if (val > 1) 58 | locks.put(blk, val - 1); 59 | else { 60 | locks.remove(blk); 61 | notifyAll(); 62 | } 63 | } 64 | 65 | private boolean hasXLock(BlockId blk) { 66 | return getLockVal(blk) < 0; // As it has -1 if a transaction holds xlock 67 | } 68 | 69 | private boolean hasOtherSLocks(BlockId blk) { 70 | return getLockVal(blk) > 1; // As it represents the number of transactions holding slock 71 | } 72 | 73 | private boolean waitingTooLong(long starttime) { 74 | return System.currentTimeMillis() - starttime > MAX_TIME; 75 | } 76 | 77 | private int getLockVal(BlockId blk) { 78 | Integer ival = locks.get(blk); 79 | int val = (ival == null) ? 0 : ival.intValue(); 80 | return val; 81 | } 82 | 83 | private String lockStatusName(BlockId blk) { 84 | Integer ival = locks.get(blk); 85 | if (ival == null) 86 | return "no lock"; 87 | else if (ival > 0) 88 | return "slock"; 89 | else 90 | return "xlock"; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /rmi/README.md: -------------------------------------------------------------------------------- 1 | # [RMI](https://docs.oracle.com/en/java/javase/19/docs/specs/rmi/index.html) 2 | 3 | ## QuickStart 4 | 5 | 1. Check files 6 | ```bash 7 | tree 8 | . 9 | ├── README.md 10 | └── example 11 | └── hello 12 | ├── Client.java 13 | ├── Hello.java 14 | └── Server.java 15 | 16 | 2 directories, 4 files 17 | ``` 18 | 1. Compile java files. 19 | ``` 20 | javac example/hello/*.java 21 | ``` 22 | 23 | 1. Run rmiregistry. 24 | ``` 25 | rmiregistry 26 | ``` 27 | 1. Run RMI Server. 28 | 29 | ``` 30 | java example.hello.Server 31 | ``` 32 | 33 | 1. Run client. 34 | 35 | ``` 36 | java example.hello.Client 37 | response:Hello, World! 38 | ``` 39 | 40 | 1. Run with Jar 41 | 42 | 1. Prepare `META-INF/MANIFEST.MF` 43 | 44 | ``` 45 | Main-Class: example.hello.Server 46 | ``` 47 | 1. Make a jar 48 | 49 | ``` 50 | jar cvfm example-hello.jar META-INF/MANIFEST.MF example/hello/*.class 51 | added manifest 52 | adding: example/hello/Client.class(in = 1484) (out= 797)(deflated 46%) 53 | adding: example/hello/Hello.class(in = 251) (out= 190)(deflated 24%) 54 | adding: example/hello/Server.class(in = 1594) (out= 859)(deflated 46%) 55 | ``` 56 | 1. Run RMI registry (You can skip this step if you use `createRegistry()` instead of `getRegistry()`) 57 | ``` 58 | rmiregistry 59 | ``` 60 | 1. Run server. 61 | ``` 62 | java -jar example-hello.jar 63 | ``` 64 | 1. Run client. 65 | ``` 66 | java -cp example-hello.jar example.hello.Client 67 | response:Hello, World! 68 | ``` 69 | 70 | ## Error: 71 | 72 | 1. `java.lang.ClassNotFoundException: example.hello.Hello`: this is because rmiregistry cannot find `Hello` class. You need to either run rmiregistry in the `rmi` directory or pass the class path to `rmiregistry`. 73 | ``` 74 | java example.hello.Server 75 | Server exception: java.rmi.ServerException: RemoteException occurred in server thread; nested exception is: 76 | java.rmi.UnmarshalException: error unmarshalling arguments; nested exception is: 77 | java.lang.ClassNotFoundException: example.hello.Hello 78 | ``` 79 | 1. `java.rmi.ConnectException: Connection refused to host: 192.168.10.51; nested exception is: java.net.ConnectException: Connection refused`: this is because rmiregistry is not running. You need to either run rmiregistry or change `getRegistry()` to `createRegistry(1099)`. 80 | 81 | ## References 82 | 1. https://www.baeldung.com/java-could-not-find-load-main-class 83 | 1. https://docs.oracle.com/javase/jp/1.5.0/guide/rmi/hello/hello-world.html 84 | 1. https://stackoverflow.com/questions/14071885/java-rmi-unmarshalexception-error-unmarshalling-arguments-nested-exception-is 85 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/plan/PlannerTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.plan; 2 | 3 | import static org.mockito.ArgumentMatchers.any; 4 | import static org.mockito.Mockito.verify; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.mockito.Mock; 9 | import org.mockito.junit.jupiter.MockitoExtension; 10 | 11 | import simpledb.parse.CreateIndexData; 12 | import simpledb.parse.CreateTableData; 13 | import simpledb.parse.CreateViewData; 14 | import simpledb.parse.DeleteData; 15 | import simpledb.parse.InsertData; 16 | import simpledb.parse.ModifyData; 17 | import simpledb.parse.QueryData; 18 | import simpledb.tx.Transaction; 19 | 20 | @ExtendWith(MockitoExtension.class) 21 | public class PlannerTest { 22 | @Mock 23 | QueryPlanner qplanner; 24 | @Mock 25 | UpdatePlanner uplanner; 26 | @Mock 27 | Transaction tx; 28 | 29 | @Test 30 | public void testCreateQueryPlan() { 31 | Planner planner = new Planner(qplanner, uplanner); 32 | planner.createQueryPlan("select fld1 from tbl1", tx); 33 | verify(qplanner).createPlan(any(QueryData.class), any(Transaction.class)); 34 | } 35 | 36 | @Test 37 | public void testExecuteInsert() { 38 | Planner planner = new Planner(qplanner, uplanner); 39 | planner.executeUpdate("insert into tbl1(fld1) values (1)", tx); 40 | verify(uplanner).executeInsert(any(InsertData.class), any(Transaction.class)); 41 | } 42 | 43 | @Test 44 | public void testExecuteDelete() { 45 | Planner planner = new Planner(qplanner, uplanner); 46 | planner.executeUpdate("delete from tbl1 where fld1 = 1", tx); 47 | verify(uplanner).executeDelete(any(DeleteData.class), any(Transaction.class)); 48 | } 49 | 50 | @Test 51 | public void testExecuteModify() { 52 | Planner planner = new Planner(qplanner, uplanner); 53 | planner.executeUpdate("update tbl1 set fld1 = 1", tx); 54 | verify(uplanner).executeModify(any(ModifyData.class), any(Transaction.class)); 55 | } 56 | 57 | @Test 58 | public void testExecuteCreateTable() { 59 | Planner planner = new Planner(qplanner, uplanner); 60 | planner.executeUpdate("create table tbl1 (fld1 int, fld2 varchar(10))", tx); 61 | verify(uplanner).executeCreateTable(any(CreateTableData.class), any(Transaction.class)); 62 | } 63 | 64 | @Test 65 | public void testExecuteCreateView() { 66 | Planner planner = new Planner(qplanner, uplanner); 67 | planner.executeUpdate("create view testview as select fld1 from tbl1", tx); 68 | verify(uplanner).executeCreateView(any(CreateViewData.class), any(Transaction.class)); 69 | } 70 | 71 | @Test 72 | public void testExecuteCreateIndex() { 73 | Planner planner = new Planner(qplanner, uplanner); 74 | planner.executeUpdate("create index test_idx on tbl1(fld1)", tx); 75 | verify(uplanner).executeCreateIndex(any(CreateIndexData.class), any(Transaction.class)); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/multibuffer/MultibufferProductPlan.java: -------------------------------------------------------------------------------- 1 | package simpledb.multibuffer; 2 | 3 | import simpledb.materialize.MaterializePlan; 4 | import simpledb.materialize.TempTable; 5 | import simpledb.plan.Plan; 6 | import simpledb.query.Scan; 7 | import simpledb.query.UpdateScan; 8 | import simpledb.record.Schema; 9 | import simpledb.tx.Transaction; 10 | 11 | public class MultibufferProductPlan implements Plan { 12 | private Transaction tx; 13 | private Plan lhs; 14 | private Plan rhs; 15 | private Schema schema = new Schema(); 16 | 17 | public MultibufferProductPlan(Transaction tx, Plan lhs, Plan rhs) { 18 | this.tx = tx; 19 | this.lhs = new MaterializePlan(tx, lhs); 20 | this.rhs = rhs; 21 | schema.addAll(lhs.schema()); 22 | schema.addAll(rhs.schema()); 23 | } 24 | 25 | /* 26 | * 1. Materialize LHS and RHS 27 | * 2. Determine the optimal chunk size 28 | * 3. Create a chunk plan for each chunk, and save them in a list. 29 | * 4. Create multiscan for the list. 30 | */ 31 | @Override 32 | public Scan open() { 33 | Scan leftscan = lhs.open(); 34 | TempTable tt = copyRecordsFrom(rhs); 35 | return new MultibufferProductScan(tx, leftscan, tt.tableName(), tt.getLayout()); 36 | } 37 | 38 | /* 39 | * Calculate the numchunks by estimating the size of materialized right-side 40 | * table. 41 | * The estimation is provided by MaterializePlan 42 | */ 43 | @Override 44 | public int blockAccessed() { 45 | int avail = tx.availableBuffs(); 46 | int size = new MaterializePlan(tx, rhs).blockAccessed(); 47 | int numchunks = size / avail; 48 | return rhs.blockAccessed() + (lhs.blockAccessed() * numchunks); 49 | } 50 | 51 | @Override 52 | public int recordsOutput() { 53 | return lhs.recordsOutput() * rhs.recordsOutput(); 54 | } 55 | 56 | @Override 57 | public int distinctValues(String fldname) { 58 | if (lhs.schema().hasField(fldname)) 59 | return lhs.distinctValues(fldname); 60 | else 61 | return rhs.distinctValues(fldname); 62 | } 63 | 64 | @Override 65 | public Schema schema() { 66 | return schema; 67 | } 68 | 69 | private TempTable copyRecordsFrom(Plan p) { 70 | Scan src = p.open(); 71 | Schema sch = p.schema(); 72 | TempTable t = new TempTable(tx, sch); 73 | UpdateScan dest = t.open(); 74 | while (src.next()) { 75 | dest.insert(); 76 | for (String fldname : sch.fields()) 77 | dest.setVal(fldname, src.getVal(fldname)); 78 | } 79 | src.close(); 80 | dest.close(); 81 | return t; 82 | } 83 | 84 | /* 85 | * Ref: 14.6. 86 | * 1. Materialize LHS (MaterializePlan) 87 | * 2. Read and write RHS (copyRecordsFrom) 88 | */ 89 | @Override 90 | public int preprocessingCost() { 91 | return (lhs.preprocessingCost() // materialize preprocessing 92 | + rhs.blockAccessed() // read from rhs (input cost) 93 | + blockAccessed()); // write to temp table 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/multibuffer/MultibufferProductScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.multibuffer; 2 | 3 | import simpledb.query.Constant; 4 | import simpledb.query.ProductScan; 5 | import simpledb.query.Scan; 6 | import simpledb.record.Layout; 7 | import simpledb.tx.Transaction; 8 | 9 | /* 10 | * RHS is scanned with ChunkScan. 11 | */ 12 | public class MultibufferProductScan implements Scan { 13 | private Transaction tx; 14 | private Scan lhsscan; 15 | private Scan rhsscan = null; 16 | private Scan prodscan; 17 | private String filename; 18 | private Layout layout; 19 | private int chunksize; // number of blocks processed together 20 | private int nextblknum; 21 | private int filesize; 22 | 23 | public MultibufferProductScan(Transaction tx, Scan lhsscan, String tblname, Layout layout) { 24 | this.tx = tx; 25 | this.lhsscan = lhsscan; 26 | this.filename = tblname + ".tbl"; 27 | this.layout = layout; 28 | filesize = tx.size(filename); 29 | int available = tx.availableBuffs(); 30 | chunksize = BufferNeeds.bestFactor(available, filesize); 31 | beforeFirst(); 32 | } 33 | 34 | /* 35 | * LHS scan is positioned at its first record. 36 | * RHS scan is positioned before the first records of the first record. 37 | */ 38 | @Override 39 | public void beforeFirst() { 40 | nextblknum = 0; 41 | useNextChunk(); 42 | } 43 | 44 | /* 45 | * Move to next chunk until there's no more chunks. 46 | * Repeat it until ProductScan has no more records. 47 | */ 48 | @Override 49 | public boolean next() { 50 | while (!prodscan.next()) 51 | if (!useNextChunk()) 52 | return false; 53 | return true; 54 | } 55 | 56 | @Override 57 | public int getInt(String fldname) { 58 | return prodscan.getInt(fldname); 59 | } 60 | 61 | @Override 62 | public String getString(String fldname) { 63 | return prodscan.getString(fldname); 64 | } 65 | 66 | @Override 67 | public Constant getVal(String fldname) { 68 | return prodscan.getVal(fldname); 69 | } 70 | 71 | @Override 72 | public boolean hasField(String fldname) { 73 | return prodscan.hasField(fldname); 74 | } 75 | 76 | @Override 77 | public void close() { 78 | prodscan.close(); 79 | } 80 | 81 | /* 82 | * Create ChunkScan for star and end block num. 83 | * Move LHS before the first record. 84 | */ 85 | private boolean useNextChunk() { 86 | int startblknum = nextblknum; 87 | if (startblknum >= filesize) 88 | return false; 89 | if (rhsscan != null) 90 | rhsscan.close(); 91 | int endblknum = startblknum + chunksize - 1; 92 | if (endblknum >= filesize) 93 | endblknum = filesize - 1; 94 | rhsscan = new ChunkScan(tx, filename, layout, startblknum, endblknum); 95 | lhsscan.beforeFirst(); 96 | prodscan = new ProductScan(lhsscan, rhsscan); 97 | nextblknum = endblknum + 1; 98 | return true; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/record/TableScanTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.record; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertFalse; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | import static org.mockito.ArgumentMatchers.any; 7 | import static org.mockito.Mockito.times; 8 | import static org.mockito.Mockito.verify; 9 | import static org.mockito.Mockito.when; 10 | 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import org.mockito.Mock; 14 | import org.mockito.junit.jupiter.MockitoExtension; 15 | 16 | import simpledb.file.BlockId; 17 | import simpledb.query.Scan; 18 | import simpledb.tx.Transaction; 19 | 20 | @ExtendWith(MockitoExtension.class) 21 | public class TableScanTest { 22 | @Mock 23 | private Transaction tx; 24 | 25 | /* 26 | * pin/unpin every time the scan reads value 27 | * you can compare with ChunkScanTest 28 | */ 29 | @Test 30 | public void testTableScan() { 31 | String tblname = "test_tbl"; 32 | String filename = tblname + ".tbl"; 33 | Schema sch = new Schema(); 34 | sch.addIntField("intfld"); 35 | Layout layout = new Layout(sch); 36 | when(tx.blockSize()).thenReturn(16); 37 | when(tx.size(filename)).thenReturn(2); 38 | when(tx.getInt(new BlockId(filename, 0), 0)).thenReturn(1); // flag 39 | when(tx.getInt(new BlockId(filename, 0), 4)).thenReturn(1); // intfld 40 | when(tx.getInt(new BlockId(filename, 0), 0 + layout.slotSize())).thenReturn(1); // flag 41 | when(tx.getInt(new BlockId(filename, 0), 4 + layout.slotSize())).thenReturn(2); // intfld 42 | when(tx.getInt(new BlockId(filename, 1), 0)).thenReturn(1); // flag 43 | when(tx.getInt(new BlockId(filename, 1), 4)).thenReturn(3); // intfld 44 | when(tx.getInt(new BlockId(filename, 1), 0 + layout.slotSize())).thenReturn(0); // flag 45 | 46 | Scan scan = new TableScan(tx, tblname, layout); 47 | verify(tx, times(1)).pin(new BlockId(filename, 0)); 48 | scan.beforeFirst(); 49 | 50 | // read first time 51 | assertTrue(scan.next()); 52 | assertEquals(1, scan.getInt("intfld")); 53 | assertTrue(scan.next()); 54 | assertEquals(2, scan.getInt("intfld")); 55 | assertTrue(scan.next()); 56 | assertEquals(3, scan.getInt("intfld")); 57 | assertFalse(scan.next()); 58 | verify(tx, times(3)).pin(any(BlockId.class)); 59 | verify(tx, times(2)).unpin(any(BlockId.class)); 60 | 61 | // read second time 62 | scan.beforeFirst(); 63 | assertTrue(scan.next()); 64 | assertEquals(1, scan.getInt("intfld")); 65 | assertTrue(scan.next()); 66 | assertEquals(2, scan.getInt("intfld")); 67 | assertTrue(scan.next()); 68 | assertEquals(3, scan.getInt("intfld")); 69 | assertFalse(scan.next()); 70 | verify(tx, times(5)).pin(any(BlockId.class)); 71 | verify(tx, times(4)).unpin(any(BlockId.class)); 72 | 73 | scan.close(); 74 | verify(tx, times(5)).unpin(any(BlockId.class)); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/recovery/RecoveryMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx.recovery; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collection; 5 | import java.util.Iterator; 6 | 7 | import simpledb.buffer.Buffer; 8 | import simpledb.buffer.BufferMgr; 9 | import simpledb.file.BlockId; 10 | import simpledb.log.LogMgr; 11 | import simpledb.tx.Transaction; 12 | 13 | public class RecoveryMgr { 14 | private LogMgr lm; 15 | private BufferMgr bm; 16 | private Transaction tx; 17 | private int txnum; 18 | 19 | public RecoveryMgr(Transaction tx, int txnum, LogMgr lm, BufferMgr bm) { 20 | this.tx = tx; 21 | this.txnum = txnum; 22 | this.lm = lm; 23 | this.bm = bm; 24 | StartRecord.writeToLog(lm, txnum); 25 | } 26 | 27 | public void commit() { 28 | bm.flushAll(txnum); 29 | int lsn = CommitRecord.writeToLog(lm, txnum); 30 | lm.flush(lsn); 31 | } 32 | 33 | public void rollback() { 34 | doRollback(); 35 | bm.flushAll(txnum); 36 | int lsn = RollbackRecord.writeToLog(lm, txnum); 37 | lm.flush(lsn); 38 | } 39 | 40 | public void recover() { 41 | doRecover(); 42 | bm.flushAll(txnum); 43 | int lsn = CheckpointRecord.writeToLog(lm); 44 | lm.flush(lsn); 45 | } 46 | 47 | public int setInt(Buffer buff, int offset) { 48 | int oldval = buff.contents().getInt(offset); 49 | BlockId blk = buff.block(); 50 | return SetIntRecord.writeToLog(lm, txnum, blk, offset, oldval); 51 | } 52 | 53 | public int setString(Buffer buff, int offset) { 54 | String oldval = buff.contents().getString(offset); 55 | BlockId blk = buff.block(); 56 | return SetStringRecord.writeToLog(lm, txnum, blk, offset, oldval); 57 | } 58 | 59 | /* 60 | * iterate through the log records from new to old 61 | * if it finds log records in the transaction, it calls undo of the log record 62 | * until the start record of the transaction 63 | */ 64 | private void doRollback() { 65 | Iterator iter = lm.iterator(); 66 | while (iter.hasNext()) { 67 | byte[] bytes = iter.next(); 68 | LogRecord rec = LogRecord.createLogRecord(bytes); 69 | if (rec.txNumber() == txnum) { 70 | if (rec.op() == LogRecord.START) 71 | return; 72 | rec.undo(tx); 73 | } 74 | } 75 | } 76 | 77 | /* 78 | * it reads log records until it hits a quiescent checkpoint or reaches the end of the log 79 | * it undoes uncommited update records 80 | */ 81 | private void doRecover() { 82 | Collection finishedTxs = new ArrayList<>(); 83 | Iterator iter = lm.iterator(); 84 | while (iter.hasNext()) { 85 | byte[] bytes = iter.next(); 86 | LogRecord rec = LogRecord.createLogRecord(bytes); 87 | if (rec.op() == LogRecord.CHECKPOINT) 88 | return; 89 | if (rec.op() == LogRecord.COMMIT || rec.op() == LogRecord.ROLLBACK) 90 | finishedTxs.add(rec.txNumber()); 91 | else if (!finishedTxs.contains(rec.txNumber())) 92 | rec.undo(tx); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/index/btree/BTreeDirTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.btree; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.mockito.Mockito.when; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.mockito.Mock; 9 | import org.mockito.junit.jupiter.MockitoExtension; 10 | 11 | import simpledb.file.BlockId; 12 | import simpledb.query.Constant; 13 | import simpledb.record.Layout; 14 | import simpledb.record.Schema; 15 | import simpledb.tx.Transaction; 16 | 17 | @ExtendWith(MockitoExtension.class) 18 | public class BTreeDirTest { 19 | private static final String filename = "testidxdir"; 20 | @Mock 21 | private Transaction tx; 22 | 23 | /* 24 | * test with level 0 25 | */ 26 | @Test 27 | public void testBTreeDirSearchWithLevelZero() { 28 | BlockId blk = new BlockId(filename, 0); 29 | Schema sch = new Schema(); 30 | sch.addIntField("block"); // blk num 31 | sch.addStringField("dataval", 10); // index on String field 32 | Layout layout = new Layout(sch); 33 | int slotsize = layout.slotSize(); 34 | when(tx.getInt(blk, 0)).thenReturn(0); // flag: directory level 35 | when(tx.getInt(blk, 4)).thenReturn(2); // record num 36 | when(tx.getInt(blk, 12)).thenReturn(10); // leaf block num 37 | when(tx.getString(blk, 16)).thenReturn("test1"); // dataval = "test1" 38 | when(tx.getInt(blk, slotsize + 12)).thenReturn(20); // leaf block num 39 | when(tx.getString(blk, slotsize + 16)).thenReturn("test2"); // dataval = "tes2" 40 | 41 | BTreeDir bDir = new BTreeDir(tx, blk, layout); 42 | assertEquals(10, bDir.search(new Constant("test1"))); 43 | assertEquals(20, bDir.search(new Constant("test2"))); 44 | } 45 | 46 | /* 47 | * test with level 1 48 | */ 49 | @Test 50 | public void testBTreeDirSearchWithLevelOne() { 51 | BlockId blk = new BlockId(filename, 0); 52 | BlockId blk1 = new BlockId(filename, 1); 53 | Schema sch = new Schema(); 54 | sch.addIntField("block"); // blk num 55 | sch.addStringField("dataval", 10); // index on String field 56 | Layout layout = new Layout(sch); 57 | int slotsize = layout.slotSize(); 58 | when(tx.getInt(blk, 0)).thenReturn(1); // flag: directory level 59 | when(tx.getInt(blk, 4)).thenReturn(2); // record num 60 | when(tx.getInt(blk, 12)).thenReturn(1); // leaf block num 61 | when(tx.getString(blk, 16)).thenReturn("test1"); // dataval = "test1" 62 | 63 | // block 1 64 | when(tx.getInt(blk1, 0)).thenReturn(0); // flag: directory level 65 | when(tx.getInt(blk1, 4)).thenReturn(2); // record num 66 | when(tx.getInt(blk1, 12)).thenReturn(10); // leaf block num 67 | when(tx.getString(blk1, 16)).thenReturn("test1"); // dataval = "test1" 68 | when(tx.getInt(blk1, slotsize + 12)).thenReturn(20); // leaf block num 69 | when(tx.getString(blk1, slotsize + 16)).thenReturn("test2"); // dataval = "tes2" 70 | 71 | BTreeDir bDir = new BTreeDir(tx, blk, layout); 72 | assertEquals(10, bDir.search(new Constant("test1"))); 73 | assertEquals(20, bDir.search(new Constant("test2"))); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/query/Term.java: -------------------------------------------------------------------------------- 1 | package simpledb.query; 2 | 3 | import simpledb.plan.Plan; 4 | import simpledb.record.Schema; 5 | 6 | /* 7 | * A term is a comparison between two expressions. 8 | * Now only supports equality 9 | */ 10 | public class Term { 11 | private Expression lhs; 12 | private Expression rhs; 13 | 14 | public Term(Expression lhs, Expression rhs) { 15 | this.lhs = lhs; 16 | this.rhs = rhs; 17 | } 18 | 19 | /* 20 | * Return true if the evaluation of the expressions 21 | * are equal. 22 | * This function is used to determined the result of Predicate. 23 | */ 24 | public boolean isSatisfied(Scan s) { 25 | Constant lhsval = lhs.evaluate(s); 26 | Constant rhsval = rhs.evaluate(s); 27 | return rhsval.equals(lhsval); 28 | } 29 | 30 | /* 31 | * Return true if both of the term's expressions 32 | * apply to the speicified schema. 33 | */ 34 | public boolean appliesTo(Schema sch) { 35 | return lhs.appliesTo(sch) && rhs.appliesTo(sch); 36 | } 37 | 38 | public String toString() { 39 | return lhs.toString() + "=" + rhs.toString(); 40 | } 41 | 42 | /* 43 | * Calculate the extent to which selecting on the term reduces the number of 44 | * records output by a query. 45 | * If reduction factor is 2, the term cuts the size in half. 46 | */ 47 | public int reductionFactor(Plan p) { 48 | String lhsName; 49 | String rhsName; 50 | // max of 1/(distinct values of the field) 51 | if (lhs.isFieldName() && rhs.isFieldName()) { 52 | lhsName = lhs.asFieldName(); 53 | rhsName = rhs.asFieldName(); 54 | return Math.max(p.distinctValues(lhsName), p.distinctValues(rhsName)); 55 | } 56 | if (lhs.isFieldName()) { 57 | lhsName = lhs.asFieldName(); 58 | return p.distinctValues(lhsName); // 1/(distinct values of the field) 59 | } 60 | if (rhs.isFieldName()) { 61 | rhsName = rhs.asFieldName(); 62 | return p.distinctValues(rhsName); // 1/(distinct values of the field) 63 | } 64 | if (lhs.asConstant().equals(rhs.asConstant())) // no change 65 | return 1; 66 | else 67 | return Integer.MAX_VALUE; // not match -> infinite reduction 68 | } 69 | 70 | /* 71 | * If the term is in the form of F=c, return c 72 | * otherwise, return null. 73 | */ 74 | public Constant equatesWithConstant(String fldname) { 75 | if (lhs.isFieldName() && lhs.asFieldName().equals(fldname) && !rhs.isFieldName()) 76 | return rhs.asConstant(); 77 | else if (rhs.isFieldName() && rhs.asFieldName().equals(fldname) && !lhs.isFieldName()) 78 | return lhs.asConstant(); 79 | else 80 | return null; 81 | } 82 | 83 | /* 84 | * If the term is in the form of F1=F2, return the field name 85 | * otherwise, return null 86 | */ 87 | public String equatesWithField(String fldname) { 88 | if (lhs.isFieldName() && lhs.asFieldName().equals(fldname) && rhs.isFieldName()) 89 | return rhs.asFieldName(); 90 | else if (rhs.isFieldName() && rhs.asFieldName().equals(fldname) && lhs.isFieldName()) 91 | return lhs.asFieldName(); 92 | else 93 | return null; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /simpledb/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/MergeJoinScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import simpledb.query.Constant; 4 | import simpledb.query.Scan; 5 | 6 | public class MergeJoinScan implements Scan { 7 | private Scan s1; 8 | private SortScan s2; 9 | private String fldname1; 10 | private String fldname2; 11 | private Constant joinval = null; 12 | 13 | public MergeJoinScan(Scan s1, SortScan s2, String fldname1, String fldname2) { 14 | this.s1 = s1; 15 | this.s2 = s2; 16 | this.fldname1 = fldname1; 17 | this.fldname2 = fldname2; 18 | beforeFirst(); 19 | } 20 | 21 | @Override 22 | public void beforeFirst() { 23 | s1.beforeFirst(); 24 | s2.beforeFirst(); 25 | } 26 | 27 | /* 28 | * 1. If the next RHS record has the same join value, then move it. 29 | * 2. If the next LHS record has the same join value, move the RHS scan 30 | * back to the first record having that join value. 31 | * 3. Otherwise, repeatedly move the scan having the smallest value until 32 | * a common join value is found. 33 | * 4. If there's no more records in both RHS and LHS, return false. 34 | */ 35 | @Override 36 | public boolean next() { 37 | boolean hasmore2 = s2.next(); 38 | if (hasmore2 && s2.getVal(fldname2).equals(joinval)) { 39 | System.out.println("[MergeJoinScan] next increments RHS joinval: " + joinval); 40 | return true; 41 | } 42 | 43 | boolean hasmore1 = s1.next(); 44 | if (hasmore1 && s1.getVal(fldname1).equals(joinval)) { 45 | s2.restorePosition(); 46 | System.out.println( 47 | "[MergeJoinScan] next increments LHS and move RHS back to the starting point of joinval: " + joinval); 48 | return true; 49 | } 50 | 51 | while (hasmore1 && hasmore2) { 52 | Constant v1 = s1.getVal(fldname1); 53 | Constant v2 = s2.getVal(fldname2); 54 | if (v1.compareTo(v2) < 0) 55 | hasmore1 = s1.next(); 56 | else if (v1.compareTo(v2) > 0) 57 | hasmore2 = s2.next(); 58 | else { 59 | s2.savePosition(); 60 | joinval = s2.getVal(fldname2); 61 | System.out.println("[MergeJoinScan] next update joinval: " + joinval); 62 | return true; 63 | } 64 | } 65 | System.out.println("[MergeJoinScan] next no more next: " + joinval); 66 | return false; 67 | } 68 | 69 | @Override 70 | public int getInt(String fldname) { 71 | if (s1.hasField(fldname)) 72 | return s1.getInt(fldname); 73 | else 74 | return s2.getInt(fldname); 75 | } 76 | 77 | @Override 78 | public String getString(String fldname) { 79 | if (s1.hasField(fldname)) 80 | return s1.getString(fldname); 81 | else 82 | return s2.getString(fldname); 83 | } 84 | 85 | @Override 86 | public Constant getVal(String fldname) { 87 | if (s1.hasField(fldname)) 88 | return s1.getVal(fldname); 89 | else 90 | return s2.getVal(fldname); 91 | } 92 | 93 | @Override 94 | public boolean hasField(String fldname) { 95 | return s1.hasField(fldname) || s2.hasField(fldname); 96 | } 97 | 98 | @Override 99 | public void close() { 100 | s1.close(); 101 | s2.close(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/index/btree/BTreeDir.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.btree; 2 | 3 | import simpledb.file.BlockId; 4 | import simpledb.query.Constant; 5 | import simpledb.record.Layout; 6 | import simpledb.tx.Transaction; 7 | 8 | /* 9 | * B-Tree directory block 10 | */ 11 | public class BTreeDir { 12 | private Transaction tx; 13 | private Layout layout; 14 | private BTPage contents; 15 | private String filename; 16 | 17 | BTreeDir(Transaction tx, BlockId blk, Layout layout) { 18 | this.tx = tx; 19 | this.layout = layout; 20 | contents = new BTPage(tx, blk, layout); 21 | filename = blk.fileName(); 22 | } 23 | 24 | public void close() { 25 | contents.close(); 26 | } 27 | 28 | /* 29 | * Move to the leaf node for the search key 30 | */ 31 | public int search(Constant searchkey) { 32 | BlockId childblk = findChildBlock(searchkey); 33 | while (contents.getFlag() > 0) { // until reaching the leaf node 34 | contents.close(); 35 | contents = new BTPage(tx, childblk, layout); 36 | childblk = findChildBlock(searchkey); 37 | } 38 | return childblk.number(); 39 | } 40 | 41 | /* 42 | * Create a new root block. 43 | * The new block will have two children: 44 | * 1. the old root 45 | * 2. the specified block 46 | */ 47 | public void makeNewRoot(DirEntry e) { 48 | Constant firstval = contents.getDataVal(0); 49 | int level = contents.getFlag(); 50 | BlockId newblk = contents.split(0, level); // transfer all records 51 | DirEntry oldroot = new DirEntry(firstval, newblk.number()); 52 | insertEntry(oldroot); 53 | insertEntry(e); 54 | contents.setFlag(level + 1); 55 | } 56 | 57 | /* 58 | * insert a new directory entry into the B-Tree block. 59 | * 1. If block is at level 0, the entry is just inserted there. 60 | * 2. Otherwise, the entry is inserted into the leaf node 61 | * Return the DirEntry if the block split 62 | */ 63 | public DirEntry insert(DirEntry e) { 64 | if (contents.getFlag() == 0) 65 | return insertEntry(e); 66 | BlockId childblk = findChildBlock(e.dataVal()); 67 | BTreeDir child = new BTreeDir(tx, childblk, layout); 68 | DirEntry myentry = child.insert(e); 69 | child.close(); 70 | return (myentry != null) ? insertEntry(myentry) : null; 71 | } 72 | 73 | /* 74 | * Insert a new directory entry 75 | */ 76 | private DirEntry insertEntry(DirEntry e) { 77 | int newslot = 1 + contents.findSlotBefore(e.dataVal()); 78 | contents.insertDir(newslot, e.dataVal(), e.blockNumber()); 79 | if (!contents.isFull()) 80 | return null; 81 | int level = contents.getFlag(); 82 | int splitpos = contents.getNumRecs() / 2; 83 | Constant splitval = contents.getDataVal(splitpos); 84 | BlockId newblk = contents.split(splitpos, level); 85 | return new DirEntry(splitval, newblk.number()); 86 | } 87 | 88 | private BlockId findChildBlock(Constant searchkey) { 89 | int slot = contents.findSlotBefore(searchkey); 90 | if (contents.getDataVal(slot + 1).equals(searchkey)) 91 | slot++; 92 | int blknum = contents.getChildNum(slot); 93 | System.out.println("[BTreeDir] findChildBlock completed. filename: " + 94 | filename + "blk: " + blknum + " slot: " + slot); 95 | return new BlockId(filename, blknum); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/materialize/SortScan.java: -------------------------------------------------------------------------------- 1 | package simpledb.materialize; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | 6 | import simpledb.query.Constant; 7 | import simpledb.query.Scan; 8 | import simpledb.query.UpdateScan; 9 | import simpledb.record.RID; 10 | 11 | /* 12 | * Create a sort scan, given a list of 1 or 2 runs. 13 | * If there is only 1 run, then s2 will be null and 14 | * hasmore2 will be false. 15 | */ 16 | public class SortScan implements Scan { 17 | private UpdateScan s1; 18 | private UpdateScan s2 = null; 19 | private UpdateScan currentscan = null; 20 | private RecordComparator comp; 21 | private boolean hasmore1; 22 | private boolean hasmore2 = false; 23 | private List savedposition; 24 | 25 | public SortScan(List runs, RecordComparator comp) { 26 | this.comp = comp; 27 | if (!runs.isEmpty()) { 28 | s1 = runs.get(0).open(); 29 | hasmore1 = s1.next(); 30 | } 31 | if (runs.size() > 1) { 32 | s2 = runs.get(1).open(); 33 | hasmore2 = s2.next(); 34 | } 35 | } 36 | 37 | /* 38 | * Position the scan before the first record in sorted order. 39 | * Internally, it moves to the first record of each underlying scan. 40 | * The variable currentscan is set to null, indicating that there is 41 | * no current scan. 42 | */ 43 | @Override 44 | public void beforeFirst() { 45 | currentscan = null; 46 | s1.beforeFirst(); 47 | hasmore1 = s1.next(); 48 | if (s2 != null) { 49 | s2.beforeFirst(); 50 | hasmore2 = s2.next(); 51 | } 52 | } 53 | 54 | /* 55 | * Increment currentscan 56 | * Set currentscan after comparing s1 and s2 57 | */ 58 | @Override 59 | public boolean next() { 60 | if (currentscan != null) { 61 | if (currentscan == s1) 62 | hasmore1 = s1.next(); 63 | else if (currentscan == s2) 64 | hasmore2 = s2.next(); 65 | } 66 | 67 | if (!hasmore1 && !hasmore2) // false & false 68 | return false; 69 | else if (hasmore1 && hasmore2) { // true & true 70 | if (comp.compare(s1, s2) < 0) 71 | currentscan = s1; 72 | else 73 | currentscan = s2; 74 | } else if (hasmore1) // true & false 75 | currentscan = s1; 76 | else // false & true 77 | currentscan = s2; 78 | return true; 79 | } 80 | 81 | @Override 82 | public int getInt(String fldname) { 83 | return currentscan.getInt(fldname); 84 | } 85 | 86 | @Override 87 | public String getString(String fldname) { 88 | return currentscan.getString(fldname); 89 | } 90 | 91 | @Override 92 | public Constant getVal(String fldname) { 93 | return currentscan.getVal(fldname); 94 | } 95 | 96 | @Override 97 | public boolean hasField(String fldname) { 98 | return currentscan.hasField(fldname); 99 | } 100 | 101 | @Override 102 | public void close() { 103 | if (s1 != null) 104 | s1.close(); 105 | if (s2 != null) 106 | s2.close(); 107 | } 108 | 109 | public void restorePosition() { 110 | RID rid1 = savedposition.get(0); 111 | RID rid2 = savedposition.get(1); 112 | s1.moveToRid(rid1); 113 | if (rid2 != null) 114 | s2.moveToRid(rid2); 115 | } 116 | 117 | public void savePosition() { 118 | RID rid1 = s1.getRid(); 119 | RID rid2 = (s2 == null) ? null : s2.getRid(); 120 | savedposition = Arrays.asList(rid1, rid2); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/parse/Lexer.java: -------------------------------------------------------------------------------- 1 | package simpledb.parse; 2 | 3 | import java.io.IOException; 4 | import java.io.StreamTokenizer; 5 | import java.io.StringReader; 6 | import java.util.Arrays; 7 | import java.util.Collection; 8 | 9 | /* 10 | * Lexical analyzer encapsulates StreamTokenizer 11 | * that supports the five types of tokens: 12 | * 1. Single-character delimiters 13 | * 2. Integer constants 14 | * 3. String constants 15 | * 4. Keywords 16 | * 5. Identifiers 17 | * eatXXX means extract the value, move to next token, and return the value. 18 | */ 19 | public class Lexer { 20 | private Collection keywords; 21 | private StreamTokenizer tok; 22 | 23 | public Lexer(String s) { 24 | initKeywords(); 25 | tok = new StreamTokenizer(new StringReader(s)); 26 | tok.ordinaryChar('.'); // disallow "." in identifiers 27 | tok.wordChars('_', '_'); // allow "_" in identifiers 28 | tok.lowerCaseMode(true); // ids and keywords are converted 29 | nextToken(); 30 | } 31 | 32 | /* 33 | * Return true if the current token is 34 | * the specified delimiter character 35 | */ 36 | public boolean matchDelim(char d) { 37 | return d == (char) tok.ttype; 38 | } 39 | 40 | /* 41 | * Return true if the current token is an integer 42 | */ 43 | public boolean matchIntConstant() { 44 | return tok.ttype == StreamTokenizer.TT_NUMBER; 45 | } 46 | 47 | /* 48 | * Return true if the current token is a string. 49 | */ 50 | public boolean matchStringConstant() { 51 | return '\'' == (char) tok.ttype; 52 | } 53 | 54 | /* 55 | * Return true if the current token is te speccified keyword. 56 | */ 57 | public boolean matchKeyword(String w) { 58 | return tok.ttype == StreamTokenizer.TT_WORD && tok.sval.equals(w); 59 | } 60 | 61 | /* 62 | * Return true if the current token is a legal identifier. 63 | */ 64 | public boolean matchId() { 65 | return tok.ttype == StreamTokenizer.TT_WORD && !keywords.contains(tok.sval); 66 | } 67 | 68 | // Methods to "eat" the current token 69 | 70 | /* 71 | * Move to next token if the current token is the delimiter. 72 | * Otherwise, throw exception. 73 | */ 74 | public void eatDelim(char d) { 75 | if (!matchDelim(d)) 76 | throw new BadSyntaxException(); 77 | nextToken(); 78 | } 79 | 80 | public int eatIntConstant() { 81 | if (!matchIntConstant()) 82 | throw new BadSyntaxException(); 83 | int i = (int) tok.nval; 84 | nextToken(); 85 | return i; 86 | } 87 | 88 | public String eatStringConstant() { 89 | if (!matchStringConstant()) 90 | throw new BadSyntaxException(); 91 | String s = tok.sval; 92 | nextToken(); 93 | return s; 94 | } 95 | 96 | public void eatKeyword(String w) { 97 | if (!matchKeyword(w)) 98 | throw new BadSyntaxException(); 99 | nextToken(); 100 | } 101 | 102 | public String eatId() { 103 | if (!matchId()) 104 | throw new BadSyntaxException(); 105 | String s = tok.sval; 106 | nextToken(); 107 | return s; 108 | } 109 | 110 | private void nextToken() { 111 | try { 112 | tok.nextToken(); 113 | } catch (IOException e) { 114 | throw new BadSyntaxException(); 115 | } 116 | } 117 | 118 | private void initKeywords() { 119 | keywords = Arrays.asList("select", "from", "where", "and", 120 | "insert", "into", "values", "delete", "update", "set", 121 | "create", "table", "int", "varchar", "view", "as", "index", "on"); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/file/FileMgr.java: -------------------------------------------------------------------------------- 1 | package simpledb.file; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.RandomAccessFile; 6 | import java.nio.charset.Charset; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | public class FileMgr { 12 | private File dbDirectory; 13 | private int blocksize; 14 | private boolean isNew; 15 | private Map openFiles = new HashMap<>(); 16 | public static final Charset CHARSET = StandardCharsets.US_ASCII; 17 | 18 | public FileMgr(File dbDirectory, int blocksize) { 19 | this.dbDirectory = dbDirectory; 20 | this.blocksize = blocksize; 21 | isNew = !dbDirectory.exists(); 22 | 23 | // create the directory if not exists 24 | if (isNew) 25 | dbDirectory.mkdirs(); 26 | 27 | // remove any leftover temporary tables 28 | for (String filename : dbDirectory.list()) 29 | if (filename.startsWith("temp")) 30 | new File(dbDirectory, filename).delete(); 31 | } 32 | 33 | public synchronized void read(BlockId blk, Page p) { 34 | try { 35 | RandomAccessFile f = getFile(blk.fileName()); 36 | f.seek(blk.number() * blocksize); 37 | f.getChannel().read(p.contents()); 38 | } catch (IOException e) { 39 | throw new RuntimeException("cannot read block " + blk); 40 | } 41 | } 42 | 43 | public synchronized void write(BlockId blk, Page page) { 44 | try { 45 | RandomAccessFile f = getFile(blk.fileName()); 46 | f.seek(blk.number() * blocksize); 47 | f.getChannel().write(page.contents()); 48 | } catch (IOException e) { 49 | throw new RuntimeException("cannot write block " + blk); 50 | } 51 | } 52 | 53 | /* 54 | * Initialize a BlockId with filename and the length of the file as blknum 55 | * Write the byte contents with the length of blocksize 56 | * from the position of the block 57 | */ 58 | public synchronized BlockId append(String filename) { 59 | System.out.println("[FileMgr] appending block (size " + blocksize + ") to " + filename); 60 | int newblknum = length(filename); 61 | BlockId blk = new BlockId(filename, newblknum); 62 | byte[] b = new byte[blocksize]; 63 | try { 64 | RandomAccessFile f = getFile(blk.fileName()); 65 | f.seek(blk.number() * blocksize); 66 | f.write(b); 67 | } catch (IOException e) { 68 | throw new RuntimeException("cannot append block " + blk); 69 | } 70 | System.out.println("[FileMgr] finished appending block. blknum: " + newblknum); 71 | return blk; 72 | } 73 | 74 | /* 75 | * Return the number of the blocks of the specified file. 76 | * Ususally used to get the block num to append contents to 77 | * existing file 78 | */ 79 | public int length(String filename) { 80 | try { 81 | RandomAccessFile f = getFile(filename); 82 | return (int) (f.length() / blocksize); 83 | } catch (IOException e) { 84 | throw new RuntimeException("cannot access " + filename); 85 | } 86 | } 87 | 88 | public boolean isNew() { 89 | return isNew; 90 | } 91 | 92 | public int blockSize() { 93 | return blocksize; 94 | } 95 | 96 | private RandomAccessFile getFile(String filename) throws IOException { 97 | RandomAccessFile f = openFiles.get(filename); 98 | if (f == null) { 99 | File dbTable = new File(dbDirectory, filename); 100 | f = new RandomAccessFile(dbTable, "rws"); 101 | openFiles.put(filename, f); 102 | } 103 | return f; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /simpledb/app/src/test/java/simpledb/index/btree/BTPageTest.java: -------------------------------------------------------------------------------- 1 | package simpledb.index.btree; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.mockito.Mockito.when; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.mockito.Mock; 9 | import org.mockito.junit.jupiter.MockitoExtension; 10 | 11 | import simpledb.file.BlockId; 12 | import simpledb.query.Constant; 13 | import simpledb.record.Layout; 14 | import simpledb.record.RID; 15 | import simpledb.record.Schema; 16 | import simpledb.tx.Transaction; 17 | 18 | @ExtendWith(MockitoExtension.class) 19 | public class BTPageTest { 20 | private static final String filename = "testidx"; 21 | @Mock 22 | private Transaction tx; 23 | 24 | /* 25 | * test BTPage methods for BTreeDir with the following data 26 | * |block|dataval| 27 | * |10|"test1"| 28 | * |20|"test2"| 29 | */ 30 | @Test 31 | public void testBTPageForBTreeDirMethods() { 32 | BlockId blk = new BlockId(filename, 0); 33 | Schema sch = new Schema(); 34 | sch.addIntField("block"); // blk num 35 | sch.addStringField("dataval", 10); // index on String field 36 | Layout layout = new Layout(sch); 37 | int slotsize = layout.slotSize(); 38 | when(tx.getInt(blk, 0)).thenReturn(-1); // flag 39 | when(tx.getInt(blk, 4)).thenReturn(2); // record num 40 | when(tx.getInt(blk, 12)).thenReturn(10); // leaf block num 41 | when(tx.getString(blk, 16)).thenReturn("test1"); // dataval = "test1" 42 | when(tx.getInt(blk, slotsize + 12)).thenReturn(20); // leaf block num 43 | when(tx.getString(blk, slotsize + 16)).thenReturn("test2"); // dataval = "tes2" 44 | 45 | BTPage page = new BTPage(tx, blk, layout); 46 | 47 | assertEquals(-1, page.getFlag()); 48 | assertEquals(2, sch.fields().size()); 49 | assertEquals(4 + 4 + 10 + 4, slotsize); // flag, int field, length + varchar(10) 50 | assertEquals(2, page.getNumRecs()); 51 | assertEquals(-1, page.findSlotBefore(new Constant("test1"))); 52 | assertEquals(0, page.findSlotBefore(new Constant("test2"))); 53 | assertEquals(new Constant("test1"), page.getDataVal(0)); 54 | assertEquals(new Constant("test2"), page.getDataVal(1)); 55 | assertEquals(10, page.getChildNum(0)); 56 | assertEquals(20, page.getChildNum(1)); 57 | } 58 | 59 | /* 60 | * test BTPage methods for BTreeLeaf with the following data 61 | * |block|dataval|id| 62 | * |10|"test1"|102| <- record in (blk:10, slot:102) has dataval "test1" 63 | * |20|"test2"|205| <- record in (blk:20, slot:205) has dataval "test2" 64 | */ 65 | @Test 66 | public void testBTPageForBTreeLeafMethods() { 67 | BlockId blk = new BlockId(filename, 0); 68 | Schema sch = new Schema(); 69 | sch.addIntField("block"); // int field 70 | sch.addStringField("dataval", 10); // string field 71 | sch.addIntField("id"); // rid 72 | Layout layout = new Layout(sch); 73 | int slotsize = layout.slotSize(); 74 | when(tx.getInt(blk, 0)).thenReturn(-1); // flag 75 | when(tx.getInt(blk, 4)).thenReturn(2); // record num 76 | when(tx.getInt(blk, 12)).thenReturn(10); // record block num 77 | when(tx.getInt(blk, 30)).thenReturn(102); // record slot id 78 | when(tx.getInt(blk, slotsize + 12)).thenReturn(20); // leaf block num 79 | when(tx.getInt(blk, slotsize + 30)).thenReturn(205); // record slot id 80 | 81 | BTPage page = new BTPage(tx, blk, layout); 82 | 83 | assertEquals(-1, page.getFlag()); 84 | assertEquals(3, sch.fields().size()); 85 | assertEquals(4 + 4 + 10 + 4 + 4, slotsize); // flag, int field, length + varchar(10), int 86 | assertEquals(2, page.getNumRecs()); 87 | assertEquals(new RID(10, 102), page.getDataRid(0)); 88 | assertEquals(new RID(20, 205), page.getDataRid(1)); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /simpledb/app/src/main/java/simpledb/tx/Transaction.java: -------------------------------------------------------------------------------- 1 | package simpledb.tx; 2 | 3 | import simpledb.buffer.Buffer; 4 | import simpledb.buffer.BufferMgr; 5 | import simpledb.file.BlockId; 6 | import simpledb.file.FileMgr; 7 | import simpledb.file.Page; 8 | import simpledb.log.LogMgr; 9 | import simpledb.tx.concurrency.ConcurrencyMgr; 10 | import simpledb.tx.recovery.RecoveryMgr; 11 | 12 | public class Transaction { 13 | private static int nextTxNum = 0; 14 | private static final int END_OF_FILE = -1; 15 | private RecoveryMgr recoveryMgr; 16 | private ConcurrencyMgr concurMgr; 17 | private BufferMgr bm; 18 | private FileMgr fm; 19 | private int txnum; 20 | private BufferList mybuffers; 21 | 22 | public Transaction(FileMgr fm, LogMgr lm, BufferMgr bm) { 23 | this.fm = fm; 24 | this.bm = bm; 25 | txnum = nextTxNumber(); 26 | recoveryMgr = new RecoveryMgr(this, txnum, lm, bm); 27 | concurMgr = new ConcurrencyMgr(); 28 | mybuffers = new BufferList(bm); 29 | } 30 | 31 | public void commit() { 32 | recoveryMgr.commit(); 33 | System.out.println("transaction " + txnum + " committed"); 34 | concurMgr.release(); 35 | mybuffers.unpinAll(); 36 | } 37 | 38 | public void rollback() { 39 | recoveryMgr.rollback(); 40 | System.out.println("transaction " + txnum + " rolled back"); 41 | concurMgr.release(); 42 | mybuffers.unpinAll(); 43 | } 44 | 45 | public void recover() { 46 | bm.flushAll(txnum); 47 | recoveryMgr.recover(); 48 | } 49 | 50 | public void pin(BlockId blk) { 51 | mybuffers.pin(blk); 52 | } 53 | 54 | public void unpin(BlockId blk) { 55 | mybuffers.unpin(blk); 56 | } 57 | 58 | public int getInt(BlockId blk, int offset) { 59 | concurMgr.sLock(blk); 60 | Buffer buff = mybuffers.getBuffer(blk); 61 | return buff.contents().getInt(offset); 62 | } 63 | 64 | public String getString(BlockId blk, int offset) { 65 | concurMgr.sLock(blk); 66 | Buffer buff = mybuffers.getBuffer(blk); 67 | return buff.contents().getString(offset); 68 | } 69 | 70 | public void setInt(BlockId blk, int offset, int val, boolean okToLog) { 71 | concurMgr.xLock(blk); 72 | Buffer buff = mybuffers.getBuffer(blk); 73 | int lsn = -1; 74 | if (okToLog) 75 | lsn = recoveryMgr.setInt(buff, offset); 76 | Page p = buff.contents(); 77 | p.setInt(offset, val); 78 | buff.setModified(txnum, lsn); 79 | } 80 | 81 | public void setString(BlockId blk, int offset, String val, boolean okToLog) { 82 | concurMgr.xLock(blk); 83 | Buffer buff = mybuffers.getBuffer(blk); 84 | int lsn = -1; 85 | if (okToLog) 86 | lsn = recoveryMgr.setString(buff, offset); 87 | 88 | Page p = buff.contents(); 89 | p.setString(offset, val); 90 | buff.setModified(txnum, lsn); 91 | } 92 | 93 | /* 94 | * Append a new block to the specified file. 95 | * Get xlock for END_OF_FILE before appending 96 | */ 97 | public BlockId append(String filename) { 98 | BlockId dummyblk = new BlockId(filename, END_OF_FILE); 99 | concurMgr.xLock(dummyblk); 100 | return fm.append(filename); 101 | } 102 | 103 | public int blockSize() { 104 | return fm.blockSize(); 105 | } 106 | 107 | public int availableBuffs() { 108 | return bm.available(); 109 | } 110 | 111 | private static synchronized int nextTxNumber() { 112 | nextTxNum++; 113 | return nextTxNum; 114 | } 115 | 116 | /* 117 | * Return the number of blocks in the specified file. 118 | * The method first obtains Slock on the "end of file" 119 | * before asking the file manager to return the file size 120 | */ 121 | public int size(String filename) { 122 | BlockId dummyblk = new BlockId(filename, END_OF_FILE); 123 | concurMgr.sLock(dummyblk); 124 | return fm.length(filename); 125 | } 126 | } 127 | --------------------------------------------------------------------------------