├── .gitignore ├── README.md ├── docs ├── architecture.md ├── diagrams │ ├── A_OverallArchitecture.puml │ ├── B_BasicQueryEngine.puml │ ├── C_StorageEngine.puml │ └── D_CalciteQueryEngine.puml ├── frontend_parser.md └── storage_engine_page-heap.md ├── pom.xml └── src └── main ├── java └── com │ └── arjunsk │ └── tiny_db │ ├── TinyDB.java │ ├── cli │ ├── TinyDbCli.java │ └── utils │ │ └── TablePrinter.java │ └── server │ ├── a_frontend │ ├── IParser.java │ ├── common │ │ ├── domain │ │ │ ├── clause │ │ │ │ ├── A_Predicate.java │ │ │ │ ├── B_Term.java │ │ │ │ ├── C_Expression.java │ │ │ │ └── D_Constant.java │ │ │ └── commands │ │ │ │ ├── CreateIndexData.java │ │ │ │ ├── CreateTableData.java │ │ │ │ ├── DeleteData.java │ │ │ │ ├── InsertData.java │ │ │ │ ├── ModifyData.java │ │ │ │ └── QueryData.java │ │ └── exception │ │ │ └── BadSyntaxException.java │ └── impl │ │ ├── derby │ │ ├── DerbyLexer.java │ │ └── DerbyParser.java │ │ └── mysql │ │ ├── AntlrParserTest.java │ │ ├── MySqlParser.java │ │ └── MySqlStatementVisitor.java │ ├── b_query_engine │ ├── IQueryEngine.java │ ├── common │ │ ├── catalog │ │ │ ├── MetadataMgr.java │ │ │ ├── index │ │ │ │ ├── IndexInfo.java │ │ │ │ └── IndexMgr.java │ │ │ ├── stats │ │ │ │ ├── StatMgr.java │ │ │ │ └── domain │ │ │ │ │ └── StatInfo.java │ │ │ └── table │ │ │ │ ├── TableDefinition.java │ │ │ │ ├── TableMgr.java │ │ │ │ └── TablePhysicalLayout.java │ │ └── dto │ │ │ └── TableDto.java │ └── impl │ │ ├── basic │ │ ├── BasicQueryEngine.java │ │ ├── a_query_optimizer │ │ │ ├── BasicPlanner.java │ │ │ ├── README.md │ │ │ ├── plan │ │ │ │ ├── Plan.java │ │ │ │ └── impl │ │ │ │ │ ├── A_TablePlan.java │ │ │ │ │ ├── B_SelectPlan.java │ │ │ │ │ ├── B_SelectWithIndexPlan.java │ │ │ │ │ └── C_ProjectPlan.java │ │ │ └── planner │ │ │ │ ├── QueryPlanner.java │ │ │ │ ├── UpdatePlanner.java │ │ │ │ ├── a_naive │ │ │ │ ├── BasicQueryPlanner.java │ │ │ │ └── BasicUpdatePlanner.java │ │ │ │ ├── b_rule_base │ │ │ │ ├── BetterQueryPlanner.java │ │ │ │ ├── BetterUpdatePlanner.java │ │ │ │ └── README.md │ │ │ │ └── c_cost_based │ │ │ │ └── .gitkeep │ │ └── b_execution_engine │ │ │ ├── A_SelectUsingIndex_RORecordScan.java │ │ │ ├── A_Select_RWRecordScan.java │ │ │ ├── C_Project_RORecordScan.java │ │ │ └── README.md │ │ └── calcite │ │ ├── CalciteQueryEngine.java │ │ └── core │ │ ├── A_Enumerator.java │ │ ├── B_Table.java │ │ ├── C_Schema.java │ │ ├── CalciteTest.java │ │ └── D_JavaSqlTypeToCalciteSqlTypeConversionRules.java │ ├── c_key_value_store │ ├── common │ │ ├── concurrency │ │ │ └── .gitkeep │ │ ├── dist_transaction │ │ │ └── .gitkeep │ │ ├── recovery │ │ │ └── .gitkeep │ │ └── replication │ │ │ └── .gitkeep │ └── impl │ │ ├── keyvalue │ │ └── .gitkeep │ │ └── page │ │ └── buffer_pool │ │ ├── .gitkeep │ │ └── pinner │ │ └── .gitkeep │ └── d_storage_engine │ ├── RORecordScan.java │ ├── RWIndexScan.java │ ├── RWRecordScan.java │ ├── common │ ├── file │ │ ├── BlockId.java │ │ ├── FileMgr.java │ │ └── Page.java │ └── transaction │ │ ├── Transaction.java │ │ ├── a_concurrency │ │ └── .gitkeep │ │ ├── b_page_pinner │ │ └── TxnPagesPinner.java │ │ ├── c_recovery_mgr │ │ └── .gitkeep │ │ └── d_buffer_mgr │ │ └── BufferMgr.java │ └── impl │ ├── data │ └── heap │ │ ├── HeapRWRecordScan.java │ │ └── page │ │ ├── HeapRecordPageImpl.java │ │ └── RecordKey.java │ └── index │ ├── bplustree │ ├── advanced │ │ ├── AdvancedBPlusTreeIndex.java │ │ ├── BPlusTreeTest.java │ │ └── serde │ │ │ ├── ConstantSerializer.java │ │ │ └── RecordKeySerializer.java │ └── basic │ │ ├── BTreeDir.java │ │ ├── BTreeLeaf.java │ │ ├── BasicBPlusTreeIndex.java │ │ └── common │ │ ├── BTPage.java │ │ └── DirEntry.java │ └── hash │ └── .gitkeep └── resources ├── MySqlLexer.g4 └── MySqlParser.g4 /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .classpath 3 | .project 4 | .settings/ 5 | 6 | # Intellij 7 | .idea/ 8 | *.iml 9 | *.iws 10 | 11 | # Mac 12 | .DS_Store 13 | 14 | # Maven 15 | log/ 16 | target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## TinyDB 2 | 3 | A tiny database that supports Btree Index, Planner and Parser. 4 | 5 | ## Sample Queries 6 | 7 | - Without Index 8 | 9 | ```shell 10 | create table T1 ( A int, B varchar(9) ); 11 | insert into T1 (A, B) values (1, 'Alice'); 12 | insert into T1 (A, B) values (2, 'Bob'); 13 | select A,B from T1; 14 | select A,B from T1 where A=1; 15 | ``` 16 | 17 | Output 18 | 19 | ```shell 20 | > 21 | +---+-------+ 22 | | a | b | 23 | +---+-------+ 24 | | 1 | Alice | 25 | | 2 | Bob | 26 | +---+-------+ 27 | > 28 | +---+-------+ 29 | | a | b | 30 | +---+-------+ 31 | | 1 | Alice | 32 | +---+-------+ 33 | ``` 34 | 35 | - With Index 36 | 37 | ```shell 38 | create table T2 ( A int, B varchar(9) ); 39 | create index A_IDX on T2(A); 40 | insert into T2 (A, B) values (1, 'Alice'); 41 | insert into T2 (A, B) values (2, 'Bob'); 42 | select A,B from T2; 43 | select A,B from T2 where A=1; 44 | ``` 45 | 46 | ```shell 47 | > 48 | +---+-------+ 49 | | a | b | 50 | +---+-------+ 51 | | 1 | Alice | 52 | | 2 | Bob | 53 | +---+-------+ 54 | 55 | > index on a used 56 | +---+-------+ 57 | | a | b | 58 | +---+-------+ 59 | | 1 | Alice | 60 | +---+-------+ 61 | ``` 62 | 63 | **NOTE**: Delete the `tinydb` data directory to start fresh. 64 | 65 | ## Features 66 | 67 | - Frontend 68 | - Naive Parser 69 | - ANTLR MySQL Parser ([Apache ShardingSphere](https://shardingsphere.apache.org/) Parser Library) 70 | 71 | - Query Engine 72 | - Basic Query Engine (Supporting Projection, Selection etc) 73 | - Rule Based Planners (use BTree Index if available on that field) 74 | - Calcite backed Query Engine (Currently supports ScannableTable and not ModifiableTable) 75 | - Calcite Optimizer [Todo] 76 | 77 | - Index 78 | - Naive B+Tree Index 79 | - Library backed B+Tree Index (davidmoten bplustree library, Delete not supported by library) 80 | 81 | - Storage Engine 82 | - File Manager, Block, Page 83 | 84 | - CLI interface 85 | 86 | ## TODO 87 | 88 | - Recovery Manager (WAL) 89 | - Transactions 90 | - Concurrency Manager 91 | - Buffer Manager 92 | 93 | ## Notes 94 | 95 | This work is a derived from [SimpleDB](http://cs.bc.edu/~sciore/simpledb/) 96 | 97 | ## Current Limitations 98 | 99 | - Not implemented Primary Key, Unique Key etc. 100 | - If we create index after the data is inserted, there is some anomaly. 101 | - Currently only supports Varchar, int. 102 | -------------------------------------------------------------------------------- /docs/architecture.md: -------------------------------------------------------------------------------- 1 | ## Architecture 2 | 3 | We will be developing TinyDB in a modular structure with the below architecture. 4 | 5 | **Tiny DB**: This will be the parent project embedding all the subsequent modules. 6 | This will be the final executable deliverable that we will run to see the TinyDB in 7 | action. This module can also support JDBC if time permits. 8 | 9 | **Query Front End**: This layer is going to parse SQL, Tokenize the inputs, and sends it to 10 | the next layer, ie Query Engine. The layer is currently going to support the SHOW, 11 | SELECT, CREATE, INSERT, DELETE, UPDATE, and DROP. For this project, we limit our 12 | scope to the MySQL syntax. Later on, we can extend Query Front End to support 13 | Postgres, MySQL, or any other Query Language Layer. We have used ANTRL to do the 14 | SQL Parsing. 15 | 16 | **Query Engine**: This layer is responsible for calculating JOIN cost and finding an optimal 17 | query plan. We used rule base- query optimizer, that will use Index if the field has been 18 | indexed. We also had plans of trying out Calcite as a Query Optimizer layer but didn’t get 19 | enough time. 20 | 21 | **Storage Engine**: This is a key-value store module that can talk to the underlying file 22 | system and perform CRUD(Create Read Update Delete) operations on database files. We are using Heap 23 | Page Organization, instead of the traditional Tree Page Organization. Since 24 | we create a B+Tree index on top of the Heap based data block, we get similar 25 | performance. B+Tree will have ColumnValue as the key and RecordId (Contains BlockID 26 | & Offset within the Block) as Value. 27 | 28 | ## Libraries & References 29 | 30 | - ANTRL (https://www.antlr.org/) 31 | ANTRL is a Parser for parsing the SQL and extracting SQL details for the Query 32 | Planner. We are using the compiled MySQL parser from [Apache Shardingsphere](https://shardingsphere.apache.org/). 33 | - Apache Calcite (Not implemented Fully.) 34 | Calcite is a Production ready Query Planner + Query Parser that can be integrated 35 | with a storage engine to execute the Query. 36 | - B+Tree Library (https://github.com/davidmoten/bplustree) 37 | Btree library to create on-disk Btree index. 38 | - SimpleDB (http://cs.bc.edu/~sciore/simpledb/) 39 | The SimpleDB Project set a base for most of the Project structure. 40 | - Lombok (https://projectlombok.org/) 41 | Used for creating Getters and Setters for Domain Objects. 42 | -------------------------------------------------------------------------------- /docs/diagrams/A_OverallArchitecture.puml: -------------------------------------------------------------------------------- 1 | @startuml Storage Engine Scan Flow 2 | !pragma layout smetana 3 | 4 | 5 | package QueryEngine 6 | package StorageEngine 7 | package CLI 8 | package Parser 9 | 10 | 11 | CLI --> QueryEngine 12 | QueryEngine <..> Parser 13 | QueryEngine --> StorageEngine 14 | 15 | @enduml -------------------------------------------------------------------------------- /docs/diagrams/B_BasicQueryEngine.puml: -------------------------------------------------------------------------------- 1 | @startuml Basic Query Engine Flow 2 | !pragma layout smetana 3 | 4 | package BasicQueryEngine 5 | package BasicPlanner 6 | package Parser 7 | package QueryPlanner 8 | package UpdatePlanner 9 | 10 | 11 | package SelectPlan 12 | package ProjectPlan 13 | package TablePlan 14 | package RW_HeapRecordScan 15 | 16 | package R_ProjectScan 17 | package RW_SelectScan 18 | 19 | BasicQueryEngine --> BasicPlanner 20 | 21 | BasicPlanner <--> Parser 22 | 23 | BasicPlanner --> QueryPlanner 24 | BasicPlanner --> UpdatePlanner 25 | 26 | UpdatePlanner <--> TablePlan 27 | QueryPlanner <--> TablePlan 28 | TablePlan --> SelectPlan 29 | SelectPlan --> ProjectPlan 30 | 31 | SelectPlan <.> RW_SelectScan 32 | ProjectPlan <.> R_ProjectScan 33 | TablePlan <.> RW_HeapRecordScan 34 | 35 | 'RW_SelectScan -> RW_HeapRecordScan 36 | 37 | note right of RW_HeapRecordScan 38 | Storage Engine 39 | end note 40 | 41 | note right of Parser 42 | FrontEnd 43 | end note 44 | 45 | @enduml -------------------------------------------------------------------------------- /docs/diagrams/C_StorageEngine.puml: -------------------------------------------------------------------------------- 1 | @startuml Storage Engine Scan Flow 2 | !pragma layout smetana 3 | 4 | 5 | package HeapRecordScan 6 | package HeapRecordPageImpl 7 | package Transactions 8 | package FileManager 9 | package Page 10 | 11 | HeapRecordScan --> HeapRecordPageImpl 12 | 13 | HeapRecordPageImpl --> Transactions 14 | 15 | Transactions ..> Page 16 | 17 | Page --> FileManager 18 | 19 | @enduml -------------------------------------------------------------------------------- /docs/diagrams/D_CalciteQueryEngine.puml: -------------------------------------------------------------------------------- 1 | @startuml Calcite Query Flow 2 | !pragma layout smetana 3 | 4 | 5 | package CalciteEnumerator 6 | package CalciteTable 7 | package CalciteSchema 8 | package JDBC_Schema 9 | package Calcite_JDBC_Schema_Proxy 10 | package JDBC_Query 11 | package ResultSet 12 | package MetadataMgr_TableCatalog 13 | package RW_HeapRecordScan 14 | 15 | 16 | CalciteEnumerator <..> MetadataMgr_TableCatalog 17 | CalciteEnumerator <..> RW_HeapRecordScan 18 | CalciteEnumerator ---> CalciteTable 19 | 20 | MetadataMgr_TableCatalog -> RW_HeapRecordScan 21 | 22 | CalciteTable --> CalciteSchema 23 | CalciteSchema ..> JDBC_Schema 24 | JDBC_Schema ..> Calcite_JDBC_Schema_Proxy 25 | Calcite_JDBC_Schema_Proxy ..> JDBC_Query 26 | JDBC_Query ..> ResultSet 27 | 28 | note right of RW_HeapRecordScan 29 | Storage Engine 30 | end note 31 | 32 | @enduml -------------------------------------------------------------------------------- /docs/frontend_parser.md: -------------------------------------------------------------------------------- 1 | ## NOTE 2 | 3 | At present, we support DerbyDB & MySQL Dialect. In the future, we can add support for other Dialects 4 | using ANTLR. -------------------------------------------------------------------------------- /docs/storage_engine_page-heap.md: -------------------------------------------------------------------------------- 1 | # Page Storage Architecture 2 | 3 | Different DBMS manage pages in files on disk in different ways. 4 | 5 | ## Types 6 | 7 | - Heap File Organization (Our Approach) 8 | - Tree File Organization (Sorted order tree) 9 | - Sorted File Organization (SST) 10 | - Hashing File Organization 11 | 12 | ## NOTE 13 | 14 | Iterator will be using Page to R/W values into data files. 15 | 16 | ## Flow 17 | 18 | - Start with `HeapRecordScan` (Iterable API) 19 | - If the storage is accessed in pages, we will use `HeapRecordPageImpl` 20 | - HeapRecordPageImpl will invoke `Transactions` to fetch data from buffer cache. 21 | - Transactions will call `FileManager` with `Page` containing ByteBuffer. 22 | - `Page's` ByteBuffer is modified already by the Transaction. FileManager will persist it. 23 | 24 | ## Record Storage 25 | 26 | - We are not using Slotted Page (Indirection). 27 | - We are using Fixed Record Size. Even VARCHAR(len), we use len as fixed size (Based on 28 | maxBytesRequiredForString()) -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.arjunsk 8 | tiny-db 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 11 13 | 11 14 | 11 15 | UTF-8 16 | UTF-8 17 | 18 | 4.11.1 19 | 3.8.1 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.apache.shardingsphere 27 | shardingsphere-sql-parser-mysql 28 | 5.2.1 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 1.18.24 35 | provided 36 | 37 | 38 | 39 | 40 | com.github.davidmoten 41 | bplustree 42 | 0.1.4 43 | 44 | 45 | 46 | org.apache.calcite 47 | calcite-core 48 | 1.32.0 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/TinyDB.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db; 2 | 3 | import com.arjunsk.tiny_db.cli.TinyDbCli; 4 | 5 | /** 6 | * The Doorway to a Tiny Database :) 7 | * 8 | * @author Arjun Sunil Kumar 9 | */ 10 | public class TinyDB { 11 | public static void main(String[] args) { 12 | TinyDbCli.run(args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/cli/TinyDbCli.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package com.arjunsk.tiny_db.cli; 4 | 5 | import com.arjunsk.tiny_db.cli.utils.TablePrinter; 6 | import com.arjunsk.tiny_db.server.b_query_engine.IQueryEngine; 7 | import com.arjunsk.tiny_db.server.b_query_engine.common.dto.TableDto; 8 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.BasicQueryEngine; 9 | import java.util.Scanner; 10 | 11 | /** 12 | * CLI Driver for accessing this database. 13 | * 14 | * @author Arjun Sunil Kumar 15 | */ 16 | public class TinyDbCli { 17 | 18 | public static void run(String[] args) { 19 | // Create Database 20 | String dirname = (args.length == 0) ? "tinydb" : args[0]; 21 | IQueryEngine db = new BasicQueryEngine(dirname); 22 | 23 | // Parse Queries 24 | cliLoop(db); 25 | 26 | // Close Database 27 | db.close(); 28 | } 29 | 30 | private static void cliLoop(IQueryEngine db) { 31 | Scanner scanner = new Scanner(System.in).useDelimiter(";"); 32 | TablePrinter tablePrinter = new TablePrinter(); 33 | while (true) { 34 | System.out.print("tinysql> "); 35 | String sql = scanner.next().replace("\n", " ").replace("\r", "").trim(); 36 | 37 | TableDto result; 38 | if (sql.startsWith("exit")) { 39 | break; 40 | } else if (sql.startsWith("select")) { 41 | result = db.doQuery(sql); 42 | } else { 43 | result = db.doUpdate(sql); 44 | } 45 | 46 | if (result.message.isEmpty()) { 47 | tablePrinter.print(result); 48 | } else { 49 | System.out.println(result.message); 50 | } 51 | } 52 | scanner.close(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/cli/utils/TablePrinter.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.cli.utils; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.dto.TableDto; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * ASCII Table Printer. 10 | * Reference: https://stackoverflow.com/a/39705794/1609570 11 | * 12 | * @author Arjun Sunil Kumar 13 | */ 14 | public class TablePrinter { 15 | 16 | private final List columns = new ArrayList<>(); 17 | private final List data = new ArrayList<>(); 18 | private int maxColumnWidth = Integer.MAX_VALUE; 19 | 20 | public void calculateColumnWidth() { 21 | 22 | for (Column column : columns) { 23 | column.width = column.name.length() + 1; 24 | } 25 | 26 | for (Row row : data) { 27 | int colIdx = 0; 28 | for (String value : row.values) { 29 | Column column = columns.get(colIdx); 30 | if (value == null) continue; 31 | 32 | column.width = Math.max(column.width, value.length() + 1); 33 | colIdx++; 34 | } 35 | } 36 | 37 | for (Column column : columns) { 38 | column.width = Math.min(column.width, maxColumnWidth); 39 | } 40 | } 41 | 42 | public void render() { 43 | StringBuilder sb = new StringBuilder(); 44 | 45 | writeSeparator(columns, sb); 46 | writeColumnNames(columns, sb); 47 | writeSeparator(columns, sb); 48 | 49 | // values 50 | writeValues(columns, data, sb); 51 | 52 | writeSeparator(columns, sb); 53 | 54 | System.out.println(sb.toString()); 55 | } 56 | 57 | private void writeColumnNames(final List columns, final StringBuilder sb) { 58 | sb.append("|"); 59 | for (Column column : columns) { 60 | sb.append(String.format(" %-" + (column.width) + "s", column.name)); 61 | sb.append("|"); 62 | } 63 | sb.append("\n"); 64 | } 65 | 66 | private void writeSeparator(final List columns, final StringBuilder sb) { 67 | sb.append("+"); 68 | for (Column column : columns) { 69 | sb.append(String.format("%-" + (column.width + 1) + "s", "").replace(' ', '-')); 70 | sb.append("+"); 71 | } 72 | sb.append("\n"); 73 | } 74 | 75 | private void writeValues(final List columns, final List rows, final StringBuilder sb) { 76 | for (Row row : rows) { 77 | int columnIdx = 0; 78 | sb.append("|"); 79 | for (String value : row.values) { 80 | 81 | if (value != null && value.length() > maxColumnWidth) value = value.substring(0, maxColumnWidth - 1); 82 | 83 | sb.append(String.format(" %-" + columns.get(columnIdx).width + "s", value)); 84 | sb.append("|"); 85 | 86 | columnIdx++; 87 | } 88 | sb.append("\n"); 89 | } 90 | } 91 | 92 | public List getColumns() { 93 | return columns; 94 | } 95 | 96 | public List getData() { 97 | return data; 98 | } 99 | 100 | public int getMaxColumnWidth() { 101 | return maxColumnWidth; 102 | } 103 | 104 | public void setMaxColumnWidth(final int maxColumnWidth) { 105 | this.maxColumnWidth = maxColumnWidth; 106 | } 107 | 108 | public void print(TableDto table) { 109 | print(table.columnNames, table.rowValues); 110 | } 111 | 112 | public void print(List columnNames, List> rows) { 113 | TablePrinter table = new TablePrinter(); 114 | table.setMaxColumnWidth(45); 115 | 116 | for (String columnName : columnNames) 117 | table.getColumns().add(new TablePrinter.Column(columnName)); 118 | 119 | for (List row : rows) { 120 | 121 | TablePrinter.Row asciiRow = new TablePrinter.Row(); 122 | table.getData().add(asciiRow); 123 | 124 | for (String rowValue : row) { 125 | asciiRow.getValues().add(rowValue); 126 | } 127 | 128 | } 129 | 130 | table.calculateColumnWidth(); 131 | table.render(); 132 | } 133 | 134 | public static class Column { 135 | 136 | private String name; 137 | private int width; 138 | 139 | public Column(final String name) { 140 | this.name = name; 141 | } 142 | 143 | @Override 144 | public String toString() { 145 | return "Column{" + "name='" + name + '\'' + ", width=" + width + '}'; 146 | } 147 | } 148 | 149 | public static class Row { 150 | 151 | private final List values = new ArrayList<>(); 152 | 153 | public List getValues() { 154 | return values; 155 | } 156 | 157 | @Override 158 | public String toString() { 159 | return "Row{" + "values=" + values + '}'; 160 | } 161 | } 162 | 163 | } -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/IParser.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.QueryData; 4 | 5 | /** 6 | * Parser interface to parsing SQL statements to QueryEngine Command Objects. 7 | * 8 | * @author Arjun Sunil Kumar 9 | */ 10 | public interface IParser { 11 | 12 | public QueryData queryCmd(); 13 | 14 | public Object updateCmd(); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/clause/A_Predicate.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.clause; 2 | 3 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Iterator; 7 | import java.util.List; 8 | 9 | /** 10 | * A predicate is a Boolean combination of terms. 11 | * 12 | * @author Edward Sciore 13 | */ 14 | public class A_Predicate { 15 | private List terms = new ArrayList(); 16 | 17 | /** 18 | * Create an empty predicate, corresponding to "true". 19 | */ 20 | public A_Predicate() { 21 | } 22 | 23 | /** 24 | * Create a predicate containing a single term. 25 | * 26 | * @param t the term 27 | */ 28 | public A_Predicate(B_Term t) { 29 | terms.add(t); 30 | } 31 | 32 | /** 33 | * Modifies the predicate to be the conjunction of 34 | * itself and the specified predicate. 35 | * 36 | * @param pred the other predicate 37 | */ 38 | public void conjoinWith(A_Predicate pred) { 39 | terms.addAll(pred.terms); 40 | } 41 | 42 | /** 43 | * Returns true if the predicate evaluates to true 44 | * with respect to the specified scan. 45 | * 46 | * @param s the scan 47 | * @return true if the predicate is true in the scan 48 | */ 49 | public boolean isSatisfied(RORecordScan s) { 50 | for (B_Term t : terms) 51 | if (!t.isSatisfied(s)) return false; 52 | return true; 53 | } 54 | 55 | 56 | public D_Constant equatesWithConstant(String fldname) { 57 | for (B_Term t : terms) { 58 | D_Constant c = t.equatesWithConstant(fldname); 59 | if (c != null) 60 | return c; 61 | } 62 | return null; 63 | } 64 | 65 | public String toString() { 66 | Iterator iter = terms.iterator(); 67 | if (!iter.hasNext()) return ""; 68 | String result = iter.next().toString(); 69 | while (iter.hasNext()) result += " and " + iter.next().toString(); 70 | return result; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/clause/B_Term.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.clause; 2 | 3 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 5 | 6 | /** 7 | * A term is a comparison between two expressions. 8 | * 9 | * @author Edward Sciore 10 | */ 11 | public class B_Term { 12 | private C_Expression lhs, rhs; 13 | 14 | /** 15 | * Create a new term that compares two expressions 16 | * for equality. 17 | * 18 | * @param lhs the LHS expression 19 | * @param rhs the RHS expression 20 | */ 21 | public B_Term(C_Expression lhs, C_Expression rhs) { 22 | this.lhs = lhs; 23 | this.rhs = rhs; 24 | } 25 | 26 | /** 27 | * Return true if both of the term's expressions 28 | * evaluate to the same constant, 29 | * with respect to the specified scan. 30 | * 31 | * @param s the scan 32 | * @return true if both expressions have the same value in the scan 33 | */ 34 | public boolean isSatisfied(RORecordScan s) { 35 | D_Constant lhsval = lhs.evaluate(s); 36 | D_Constant rhsval = rhs.evaluate(s); 37 | return rhsval.equals(lhsval); 38 | } 39 | 40 | /** 41 | * Determine if this term is of the form "F=c" 42 | * where F is the specified field and c is some constant. 43 | * If so, the method returns that constant. 44 | * If not, the method returns null. 45 | * 46 | * @param fldname the name of the field 47 | * @return either the constant or null 48 | */ 49 | public D_Constant equatesWithConstant(String fldname) { 50 | if (lhs.isFieldName() && 51 | lhs.asFieldName().equals(fldname) && 52 | !rhs.isFieldName()) 53 | return rhs.asConstant(); 54 | else if (rhs.isFieldName() && 55 | rhs.asFieldName().equals(fldname) && 56 | !lhs.isFieldName()) 57 | return lhs.asConstant(); 58 | else 59 | return null; 60 | } 61 | 62 | /** 63 | * Determine if this term is of the form "F1=F2" 64 | * where F1 is the specified field and F2 is another field. 65 | * If so, the method returns the name of that field. 66 | * If not, the method returns null. 67 | * 68 | * @param fldname the name of the field 69 | * @return either the name of the other field, or null 70 | */ 71 | public String equatesWithField(String fldname) { 72 | if (lhs.isFieldName() && 73 | lhs.asFieldName().equals(fldname) && 74 | rhs.isFieldName()) 75 | return rhs.asFieldName(); 76 | else if (rhs.isFieldName() && 77 | rhs.asFieldName().equals(fldname) && 78 | lhs.isFieldName()) 79 | return lhs.asFieldName(); 80 | else 81 | return null; 82 | } 83 | 84 | /** 85 | * Return true if both of the term's expressions 86 | * apply to the specified schema. 87 | * 88 | * @param sch the schema 89 | * @return true if both expressions apply to the schema 90 | */ 91 | public boolean appliesTo(TableDefinition sch) { 92 | return lhs.appliesTo(sch) && rhs.appliesTo(sch); 93 | } 94 | 95 | public String toString() { 96 | return lhs.toString() + "=" + rhs.toString(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/clause/C_Expression.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.clause; 2 | 3 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 5 | 6 | /** 7 | * The interface corresponding to SQL expressions. 8 | * 9 | * @author Edward Sciore 10 | */ 11 | public class C_Expression { 12 | private D_Constant val = null; 13 | private String fldname = null; 14 | 15 | public C_Expression(D_Constant val) { 16 | this.val = val; 17 | } 18 | 19 | public C_Expression(String fldname) { 20 | this.fldname = fldname; 21 | } 22 | 23 | /** 24 | * Evaluate the expression with respect to the 25 | * current record of the specified scan. 26 | * 27 | * @param s the scan 28 | * @return the value of the expression, as a Constant 29 | */ 30 | public D_Constant evaluate(RORecordScan s) { 31 | return (val != null) ? val : s.getVal(fldname); 32 | } 33 | 34 | /** 35 | * Return true if the expression is a field reference. 36 | * 37 | * @return true if the expression denotes a field 38 | */ 39 | public boolean isFieldName() { 40 | return fldname != null; 41 | } 42 | 43 | /** 44 | * Return the constant corresponding to a constant expression, 45 | * or null if the expression does not 46 | * denote a constant. 47 | * 48 | * @return the expression as a constant 49 | */ 50 | public D_Constant asConstant() { 51 | return val; 52 | } 53 | 54 | /** 55 | * Return the field name corresponding to a constant expression, 56 | * or null if the expression does not 57 | * denote a field. 58 | * 59 | * @return the expression as a field name 60 | */ 61 | public String asFieldName() { 62 | return fldname; 63 | } 64 | 65 | /** 66 | * Determine if all of the fields mentioned in this expression 67 | * are contained in the specified schema. 68 | * 69 | * @param sch the schema 70 | * @return true if all fields in the expression are in the schema 71 | */ 72 | public boolean appliesTo(TableDefinition sch) { 73 | return (val != null) ? true : sch.hasField(fldname); 74 | } 75 | 76 | public String toString() { 77 | return (val != null) ? val.toString() : fldname; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/clause/D_Constant.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.clause; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * The class that denotes values stored in the database. 7 | * 8 | * @author Edward Sciore 9 | */ 10 | public class D_Constant implements Comparable , Serializable { 11 | private static final long serialVersionUID = 1L; 12 | 13 | private Integer ival = null; 14 | private String sval = null; 15 | 16 | public D_Constant(Integer ival) { 17 | this.ival = ival; 18 | } 19 | 20 | public D_Constant(String sval) { 21 | this.sval = sval; 22 | } 23 | 24 | public Integer asInt() { 25 | return ival; 26 | } 27 | 28 | public String asString() { 29 | return sval; 30 | } 31 | 32 | public boolean equals(Object obj) { 33 | D_Constant c = (D_Constant) obj; 34 | return (ival != null) ? ival.equals(c.ival) : sval.equals(c.sval); 35 | } 36 | 37 | public int compareTo(D_Constant c) { 38 | return (ival != null) ? ival.compareTo(c.ival) : sval.compareTo(c.sval); 39 | } 40 | 41 | public int hashCode() { 42 | return (ival != null) ? ival.hashCode() : sval.hashCode(); 43 | } 44 | 45 | public String toString() { 46 | return (ival != null) ? ival.toString() : sval.toString(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/commands/CreateIndexData.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.commands; 2 | 3 | import lombok.ToString; 4 | 5 | /** 6 | * The parser for the create index statement. 7 | * 8 | * @author Edward Sciore 9 | */ 10 | @ToString 11 | public class CreateIndexData { 12 | private String idxname, tblname, fldname; 13 | 14 | /** 15 | * Saves the table and field names of the specified index. 16 | */ 17 | public CreateIndexData(String idxname, String tblname, String fldname) { 18 | this.idxname = idxname; 19 | this.tblname = tblname; 20 | this.fldname = fldname; 21 | } 22 | 23 | /** 24 | * Returns the name of the index. 25 | * 26 | * @return the name of the index 27 | */ 28 | public String indexName() { 29 | return idxname; 30 | } 31 | 32 | /** 33 | * Returns the name of the indexed table. 34 | * 35 | * @return the name of the indexed table 36 | */ 37 | public String tableName() { 38 | return tblname; 39 | } 40 | 41 | /** 42 | * Returns the name of the indexed field. 43 | * 44 | * @return the name of the indexed field 45 | */ 46 | public String fieldName() { 47 | return fldname; 48 | } 49 | } 50 | //ok 51 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/commands/CreateTableData.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.commands; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 4 | import lombok.ToString; 5 | 6 | /** 7 | * Data for the SQL create table statement. 8 | * 9 | * @author Edward Sciore 10 | */ 11 | @ToString 12 | public class CreateTableData { 13 | private String tblname; 14 | private TableDefinition sch; 15 | 16 | /** 17 | * Saves the table name and schema. 18 | */ 19 | public CreateTableData(String tblname, TableDefinition sch) { 20 | this.tblname = tblname; 21 | this.sch = sch; 22 | } 23 | 24 | /** 25 | * Returns the name of the new table. 26 | * 27 | * @return the name of the new table 28 | */ 29 | public String tableName() { 30 | return tblname; 31 | } 32 | 33 | /** 34 | * Returns the schema of the new table. 35 | * 36 | * @return the schema of the new table 37 | */ 38 | public TableDefinition newSchema() { 39 | return sch; 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/commands/DeleteData.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.commands; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.A_Predicate; 4 | import lombok.ToString; 5 | 6 | /** 7 | * Data for the SQL delete statement. 8 | * 9 | * @author Edward Sciore 10 | */ 11 | @ToString 12 | public class DeleteData { 13 | private String tblname; 14 | private A_Predicate pred; 15 | 16 | /** 17 | * Saves the table name and predicate. 18 | */ 19 | public DeleteData(String tblname, A_Predicate pred) { 20 | this.tblname = tblname; 21 | this.pred = pred; 22 | } 23 | 24 | /** 25 | * Returns the name of the affected table. 26 | * 27 | * @return the name of the affected table 28 | */ 29 | public String tableName() { 30 | return tblname; 31 | } 32 | 33 | /** 34 | * Returns the predicate that describes which 35 | * records should be deleted. 36 | * 37 | * @return the deletion predicate 38 | */ 39 | public A_Predicate pred() { 40 | return pred; 41 | } 42 | } 43 | 44 | //ok -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/commands/InsertData.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.commands; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import lombok.ToString; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Data for the SQL insert statement. 10 | * 11 | * @author Edward Sciore 12 | */ 13 | @ToString 14 | public class InsertData { 15 | private String tblname; 16 | private List flds; 17 | private List vals; 18 | 19 | /** 20 | * Saves the table name and the field and value lists. 21 | */ 22 | public InsertData(String tblname, List flds, List vals) { 23 | this.tblname = tblname; 24 | this.flds = flds; 25 | this.vals = vals; 26 | } 27 | 28 | /** 29 | * Returns the name of the affected table. 30 | * 31 | * @return the name of the affected table 32 | */ 33 | public String tableName() { 34 | return tblname; 35 | } 36 | 37 | /** 38 | * Returns a list of fields for which 39 | * values will be specified in the new record. 40 | * 41 | * @return a list of field names 42 | */ 43 | public List fields() { 44 | return flds; 45 | } 46 | 47 | /** 48 | * Returns a list of values for the specified fields. 49 | * There is a one-one correspondence between this 50 | * list of values and the list of fields. 51 | * 52 | * @return a list of Constant values. 53 | */ 54 | public List vals() { 55 | return vals; 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/commands/ModifyData.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.commands; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.C_Expression; 4 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.A_Predicate; 5 | import lombok.ToString; 6 | 7 | /** 8 | * Data for the SQL update statement. 9 | * 10 | * @author Edward Sciore 11 | */ 12 | @ToString 13 | public class ModifyData { 14 | private String tblname; 15 | private String fldname; 16 | private C_Expression newval; 17 | private A_Predicate pred; 18 | 19 | /** 20 | * Saves the table name, the modified field and its new value, and the predicate. 21 | */ 22 | public ModifyData(String tblname, String fldname, C_Expression newval, A_Predicate pred) { 23 | this.tblname = tblname; 24 | this.fldname = fldname; 25 | this.newval = newval; 26 | this.pred = pred; 27 | } 28 | 29 | /** 30 | * Returns the name of the affected table. 31 | * 32 | * @return the name of the affected table 33 | */ 34 | public String tableName() { 35 | return tblname; 36 | } 37 | 38 | /** 39 | * Returns the field whose values will be modified 40 | * 41 | * @return the name of the target field 42 | */ 43 | public String targetField() { 44 | return fldname; 45 | } 46 | 47 | /** 48 | * Returns an expression. 49 | * Evaluating this expression for a record produces 50 | * the value that will be stored in the record's target field. 51 | * 52 | * @return the target expression 53 | */ 54 | public C_Expression newValue() { 55 | return newval; 56 | } 57 | 58 | /** 59 | * Returns the predicate that describes which 60 | * records should be modified. 61 | * 62 | * @return the modification predicate 63 | */ 64 | public A_Predicate pred() { 65 | return pred; 66 | } 67 | } -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/domain/commands/QueryData.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.domain.commands; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.A_Predicate; 4 | import lombok.ToString; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Data for the SQL select statement. 10 | * 11 | * @author Edward Sciore 12 | */ 13 | @ToString 14 | public class QueryData { 15 | private List fields; 16 | private String table; 17 | private A_Predicate pred; 18 | 19 | /** 20 | * Saves the field and table list and predicate. 21 | */ 22 | public QueryData(List fields, String table, A_Predicate pred) { 23 | this.fields = fields; 24 | this.table = table; 25 | this.pred = pred; 26 | } 27 | 28 | /** 29 | * Returns the fields mentioned in the select clause. 30 | * 31 | * @return a list of field names 32 | */ 33 | public List fields() { 34 | return fields; 35 | } 36 | 37 | /** 38 | * Returns the tables mentioned in the from clause. 39 | * 40 | * @return a collection of table names 41 | */ 42 | public String table() { 43 | return table; 44 | } 45 | 46 | /** 47 | * Returns the predicate that describes which 48 | * records should be in the output table. 49 | * 50 | * @return the query predicate 51 | */ 52 | public A_Predicate pred() { 53 | return pred; 54 | } 55 | 56 | } 57 | //DONE -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/common/exception/BadSyntaxException.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.common.exception; 2 | 3 | /** 4 | * A runtime exception indicating that the submitted query 5 | * has incorrect syntax. 6 | * 7 | * @author Edward Sciore 8 | */ 9 | @SuppressWarnings("serial") 10 | public class BadSyntaxException extends RuntimeException { 11 | public BadSyntaxException() { 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/impl/derby/DerbyLexer.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.impl.derby; 2 | 3 | 4 | import com.arjunsk.tiny_db.server.a_frontend.common.exception.BadSyntaxException; 5 | 6 | import java.io.IOException; 7 | import java.io.StreamTokenizer; 8 | import java.io.StringReader; 9 | import java.util.Arrays; 10 | import java.util.Collection; 11 | 12 | /** 13 | * The SimpleDB lexical analyzer. 14 | * 15 | * @author Edward Sciore 16 | */ 17 | public class DerbyLexer { 18 | private Collection keywords; 19 | private StreamTokenizer tok; 20 | 21 | /** 22 | * Creates a new lexical analyzer for SQL statement s. 23 | * 24 | * @param s the SQL statement 25 | */ 26 | public DerbyLexer(String s) { 27 | initKeywords(); 28 | tok = new StreamTokenizer(new StringReader(s)); 29 | tok.ordinaryChar('.'); //disallow "." in identifiers 30 | tok.wordChars('_', '_'); //allow "_" in identifiers 31 | tok.lowerCaseMode(true); //ids and keywords are converted 32 | nextToken(); 33 | } 34 | 35 | //Methods to check the status of the current token 36 | 37 | /** 38 | * Returns true if the current token is 39 | * the specified delimiter character. 40 | * 41 | * @param d a character denoting the delimiter 42 | * @return true if the delimiter is the current token 43 | */ 44 | public boolean matchDelim(char d) { 45 | return d == (char) tok.ttype; 46 | } 47 | 48 | /** 49 | * Returns true if the current token is an integer. 50 | * 51 | * @return true if the current token is an integer 52 | */ 53 | public boolean matchIntConstant() { 54 | return tok.ttype == StreamTokenizer.TT_NUMBER; 55 | } 56 | 57 | /** 58 | * Returns true if the current token is a string. 59 | * 60 | * @return true if the current token is a string 61 | */ 62 | public boolean matchStringConstant() { 63 | return '\'' == (char) tok.ttype; 64 | } 65 | 66 | /** 67 | * Returns true if the current token is the specified keyword. 68 | * 69 | * @param w the keyword string 70 | * @return true if that keyword is the current token 71 | */ 72 | public boolean matchKeyword(String w) { 73 | return tok.ttype == StreamTokenizer.TT_WORD && tok.sval.equals(w); 74 | } 75 | 76 | /** 77 | * Returns true if the current token is a legal identifier. 78 | * 79 | * @return true if the current token is an identifier 80 | */ 81 | public boolean matchId() { 82 | return tok.ttype == StreamTokenizer.TT_WORD && !keywords.contains(tok.sval); 83 | } 84 | 85 | //Methods to "eat" the current token 86 | 87 | /** 88 | * Throws an exception if the current token is not the 89 | * specified delimiter. 90 | * Otherwise, moves to the next token. 91 | * 92 | * @param d a character denoting the delimiter 93 | */ 94 | public void eatDelim(char d) { 95 | if (!matchDelim(d)) 96 | throw new BadSyntaxException(); 97 | nextToken(); 98 | } 99 | 100 | /** 101 | * Throws an exception if the current token is not 102 | * an integer. 103 | * Otherwise, returns that integer and moves to the next token. 104 | * 105 | * @return the integer value of the current token 106 | */ 107 | public int eatIntConstant() { 108 | if (!matchIntConstant()) 109 | throw new BadSyntaxException(); 110 | int i = (int) tok.nval; 111 | nextToken(); 112 | return i; 113 | } 114 | 115 | /** 116 | * Throws an exception if the current token is not 117 | * a string. 118 | * Otherwise, returns that string and moves to the next token. 119 | * 120 | * @return the string value of the current token 121 | */ 122 | public String eatStringConstant() { 123 | if (!matchStringConstant()) 124 | throw new BadSyntaxException(); 125 | String s = tok.sval; //constants are not converted to lower case 126 | nextToken(); 127 | return s; 128 | } 129 | 130 | /** 131 | * Throws an exception if the current token is not the 132 | * specified keyword. 133 | * Otherwise, moves to the next token. 134 | * 135 | * @param w the keyword string 136 | */ 137 | public void eatKeyword(String w) { 138 | if (!matchKeyword(w)) 139 | throw new BadSyntaxException(); 140 | nextToken(); 141 | } 142 | 143 | /** 144 | * Throws an exception if the current token is not 145 | * an identifier. 146 | * Otherwise, returns the identifier string 147 | * and moves to the next token. 148 | * 149 | * @return the string value of the current token 150 | */ 151 | public String eatId() { 152 | if (!matchId()) 153 | throw new BadSyntaxException(); 154 | String s = tok.sval; 155 | nextToken(); 156 | return s; 157 | } 158 | 159 | private void nextToken() { 160 | try { 161 | tok.nextToken(); 162 | } catch (IOException e) { 163 | throw new BadSyntaxException(); 164 | } 165 | } 166 | 167 | private void initKeywords() { 168 | keywords = Arrays.asList("select", "from", "where", "and", 169 | "insert", "into", "values", "delete", "update", "set", 170 | "create", "table", "int", "varchar", "view", "as", "index", "on"); 171 | } 172 | } -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/impl/derby/DerbyParser.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.impl.derby; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.IParser; 4 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.A_Predicate; 5 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.B_Term; 6 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.C_Expression; 7 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 8 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.*; 9 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * The SimpleDB Parser. 16 | * 17 | * @author Edward Sciore 18 | */ 19 | public class DerbyParser implements IParser { 20 | private DerbyLexer lex; 21 | 22 | public DerbyParser(String s) { 23 | lex = new DerbyLexer(s); 24 | } 25 | 26 | public String field() { 27 | return lex.eatId(); 28 | } 29 | 30 | public D_Constant constant() { 31 | if (lex.matchStringConstant()) return new D_Constant(lex.eatStringConstant()); 32 | else return new D_Constant(lex.eatIntConstant()); 33 | } 34 | 35 | public C_Expression expression() { 36 | if (lex.matchId()) return new C_Expression(field()); 37 | else return new C_Expression(constant()); 38 | } 39 | 40 | public B_Term term() { 41 | C_Expression lhs = expression(); 42 | lex.eatDelim('='); 43 | C_Expression rhs = expression(); 44 | return new B_Term(lhs, rhs); 45 | } 46 | 47 | // OK 48 | public A_Predicate predicate() { 49 | A_Predicate pred = new A_Predicate(term()); 50 | if (lex.matchKeyword("and")) { 51 | lex.eatKeyword("and"); 52 | pred.conjoinWith(predicate()); 53 | } 54 | return pred; 55 | } 56 | 57 | 58 | // OK 59 | public QueryData queryCmd() { 60 | lex.eatKeyword("select"); 61 | List fields = selectList(); 62 | lex.eatKeyword("from"); 63 | String table = lex.eatId(); 64 | A_Predicate pred = new A_Predicate(); 65 | if (lex.matchKeyword("where")) { 66 | lex.eatKeyword("where"); 67 | pred = predicate(); 68 | } 69 | return new QueryData(fields, table, pred); 70 | } 71 | 72 | // OK 73 | private List selectList() { 74 | List L = new ArrayList(); 75 | L.add(field()); 76 | if (lex.matchDelim(',')) { 77 | lex.eatDelim(','); 78 | L.addAll(selectList()); 79 | } 80 | return L; 81 | } 82 | 83 | 84 | // Methods for parsing the various update commands 85 | 86 | public Object updateCmd() { 87 | if (lex.matchKeyword("insert")) return insert(); 88 | else if (lex.matchKeyword("delete")) return delete(); 89 | else if (lex.matchKeyword("update")) return modify(); 90 | else return create(); 91 | } 92 | 93 | private Object create() { 94 | lex.eatKeyword("create"); 95 | if (lex.matchKeyword("table")) return createTable(); 96 | else return createIndex(); 97 | } 98 | 99 | // Method for parsing delete commands 100 | 101 | public DeleteData delete() { 102 | lex.eatKeyword("delete"); 103 | lex.eatKeyword("from"); 104 | String tblname = lex.eatId(); 105 | A_Predicate pred = new A_Predicate(); 106 | if (lex.matchKeyword("where")) { 107 | lex.eatKeyword("where"); 108 | pred = predicate(); 109 | } 110 | return new DeleteData(tblname, pred); 111 | } 112 | 113 | // Methods for parsing insert commands 114 | 115 | public InsertData insert() { 116 | lex.eatKeyword("insert"); 117 | lex.eatKeyword("into"); 118 | String tblname = lex.eatId(); 119 | lex.eatDelim('('); 120 | List flds = fieldList(); 121 | lex.eatDelim(')'); 122 | lex.eatKeyword("values"); 123 | lex.eatDelim('('); 124 | List vals = constList(); 125 | lex.eatDelim(')'); 126 | return new InsertData(tblname, flds, vals); 127 | } 128 | 129 | private List fieldList() { 130 | List L = new ArrayList(); 131 | L.add(field()); 132 | if (lex.matchDelim(',')) { 133 | lex.eatDelim(','); 134 | L.addAll(fieldList()); 135 | } 136 | return L; 137 | } 138 | 139 | private List constList() { 140 | List L = new ArrayList(); 141 | L.add(constant()); 142 | if (lex.matchDelim(',')) { 143 | lex.eatDelim(','); 144 | L.addAll(constList()); 145 | } 146 | return L; 147 | } 148 | 149 | // Method for parsing modify commands 150 | 151 | public ModifyData modify() { 152 | lex.eatKeyword("update"); 153 | String tblname = lex.eatId(); 154 | lex.eatKeyword("set"); 155 | String fldname = field(); 156 | lex.eatDelim('='); 157 | C_Expression newval = expression(); 158 | A_Predicate pred = new A_Predicate(); 159 | if (lex.matchKeyword("where")) { 160 | lex.eatKeyword("where"); 161 | pred = predicate(); 162 | } 163 | return new ModifyData(tblname, fldname, newval, pred); 164 | } 165 | 166 | // Method for parsing create table commands 167 | 168 | public CreateTableData createTable() { 169 | lex.eatKeyword("table"); 170 | String tblname = lex.eatId(); 171 | lex.eatDelim('('); 172 | TableDefinition sch = fieldDefs(); 173 | lex.eatDelim(')'); 174 | return new CreateTableData(tblname, sch); 175 | } 176 | 177 | private TableDefinition fieldDefs() { 178 | TableDefinition tableDefinition = fieldDef(); 179 | if (lex.matchDelim(',')) { 180 | lex.eatDelim(','); 181 | TableDefinition tableDefinition2 = fieldDefs(); 182 | tableDefinition.addAll(tableDefinition2); 183 | } 184 | return tableDefinition; 185 | } 186 | 187 | private TableDefinition fieldDef() { 188 | String fldname = field(); 189 | return fieldType(fldname); 190 | } 191 | 192 | private TableDefinition fieldType(String fldname) { 193 | TableDefinition tableDefinition = new TableDefinition(); 194 | if (lex.matchKeyword("int")) { 195 | lex.eatKeyword("int"); 196 | tableDefinition.addIntField(fldname); 197 | } else { 198 | lex.eatKeyword("varchar"); 199 | lex.eatDelim('('); 200 | int strLen = lex.eatIntConstant(); 201 | lex.eatDelim(')'); 202 | tableDefinition.addStringField(fldname, strLen); 203 | } 204 | return tableDefinition; 205 | } 206 | 207 | 208 | // Method for parsing create index commands 209 | 210 | public CreateIndexData createIndex() { 211 | lex.eatKeyword("index"); 212 | String idxname = lex.eatId(); 213 | lex.eatKeyword("on"); 214 | String tblname = lex.eatId(); 215 | lex.eatDelim('('); 216 | String fldname = field(); 217 | lex.eatDelim(')'); 218 | return new CreateIndexData(idxname, tblname, fldname); 219 | } 220 | } 221 | 222 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/impl/mysql/AntlrParserTest.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.impl.mysql; 2 | 3 | import org.antlr.v4.runtime.CharStreams; 4 | import org.antlr.v4.runtime.CommonTokenStream; 5 | import org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser; 6 | import org.apache.shardingsphere.sql.parser.mysql.parser.MySQLLexer; 7 | 8 | /** 9 | * Test to verify ANTLR is working fine. 10 | * 11 | * @author Arjun Sunil Kumar 12 | */ 13 | public class AntlrParserTest { 14 | 15 | public static void main(String[] args) { 16 | outputCommand("SELECT A,B,C from T1 where A=1;"); 17 | // outputCommand("DELETE from T1 where A=10;"); 18 | // outputCommand("CREATE INDEX A_IDX on T2(A);"); 19 | // outputCommand("insert into T2 (A, B) values (1, 'Alice');"); 20 | // outputCommand("update T1 SET A=1 where A=2;"); 21 | // outputCommand("create table T2 ( A int, B varchar(9) );"); 22 | } 23 | 24 | private static void outputCommand(String sql) { 25 | MySQLLexer lexer = new MySQLLexer(CharStreams.fromString(sql)); 26 | MySQLStatementParser parser = new MySQLStatementParser(new CommonTokenStream(lexer)); 27 | MySQLStatementParser.ExecuteContext execute = parser.execute(); 28 | 29 | MySqlStatementVisitor sqlStatementVisitor = new MySqlStatementVisitor(parser); 30 | System.out.println(execute.toStringTree(parser)); 31 | sqlStatementVisitor.visit(execute); 32 | 33 | System.out.println(sqlStatementVisitor.getValue().toString()); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/impl/mysql/MySqlParser.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.impl.mysql; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.QueryData; 4 | import com.arjunsk.tiny_db.server.a_frontend.IParser; 5 | import org.antlr.v4.runtime.CharStreams; 6 | import org.antlr.v4.runtime.CommonTokenStream; 7 | import org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser; 8 | import org.apache.shardingsphere.sql.parser.mysql.parser.MySQLLexer; 9 | 10 | /** 11 | * The ANTLR parser for SQLite Dialect. 12 | * 13 | * @author Arjun Sunil Kumar 14 | */ 15 | public class MySqlParser implements IParser { 16 | 17 | MySqlStatementVisitor sqlStatementVisitor; 18 | 19 | public MySqlParser(String sql) { 20 | MySQLLexer lexer = new MySQLLexer(CharStreams.fromString(sql)); 21 | MySQLStatementParser parser = new MySQLStatementParser(new CommonTokenStream(lexer)); 22 | 23 | sqlStatementVisitor = new MySqlStatementVisitor(parser); 24 | sqlStatementVisitor.visit(parser.execute()); 25 | } 26 | 27 | @Override 28 | public QueryData queryCmd() { 29 | return (QueryData) sqlStatementVisitor.getValue(); 30 | } 31 | 32 | @Override 33 | public Object updateCmd() { 34 | return sqlStatementVisitor.getValue(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/a_frontend/impl/mysql/MySqlStatementVisitor.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.a_frontend.impl.mysql; 2 | 3 | 4 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.A_Predicate; 5 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.B_Term; 6 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.C_Expression; 7 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 8 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.*; 9 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 10 | import org.apache.shardingsphere.sql.parser.autogen.MySQLStatementBaseVisitor; 11 | import org.apache.shardingsphere.sql.parser.autogen.MySQLStatementParser; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * The ANTLR visitor class for Parsing SQL Statements. 18 | * 19 | * @author Arjun Sunil Kumar 20 | */ 21 | public class MySqlStatementVisitor extends MySQLStatementBaseVisitor { 22 | private final MySQLStatementParser parser; 23 | private COMMAND_TYPE commandType; 24 | 25 | // Common 26 | private String tableName; 27 | private A_Predicate predicate; 28 | 29 | // Select 30 | private final List selectFields; 31 | 32 | // Index 33 | private String indexName; 34 | private String indexFieldName; 35 | 36 | // Insert 37 | private final List insertFields; 38 | private final List insertValues; 39 | 40 | // Modify 41 | private C_Expression updateFieldValue; 42 | private String updateFieldName; 43 | 44 | 45 | // Create Table 46 | private TableDefinition schema; 47 | 48 | public MySqlStatementVisitor(MySQLStatementParser parser) { 49 | this.parser = parser; 50 | 51 | this.tableName = ""; 52 | this.predicate = new A_Predicate(); 53 | 54 | this.selectFields = new ArrayList<>(); 55 | 56 | this.indexName = ""; 57 | this.indexFieldName = ""; 58 | 59 | this.insertFields = new ArrayList<>(); 60 | this.insertValues = new ArrayList<>(); 61 | 62 | this.updateFieldName = ""; 63 | 64 | this.schema = new TableDefinition(); 65 | } 66 | 67 | // COMMAND TYPE 68 | @Override 69 | public Object visitCreateIndex(MySQLStatementParser.CreateIndexContext ctx) { 70 | commandType = COMMAND_TYPE.CREATE_INDEX; 71 | return super.visitCreateIndex(ctx); 72 | } 73 | 74 | @Override 75 | public Object visitCreateTable(MySQLStatementParser.CreateTableContext ctx) { 76 | commandType = COMMAND_TYPE.CREATE_TABLE; 77 | return super.visitCreateTable(ctx); 78 | } 79 | 80 | 81 | @Override 82 | public Object visitDelete(MySQLStatementParser.DeleteContext ctx) { 83 | commandType = COMMAND_TYPE.DELETE; 84 | return super.visitDelete(ctx); 85 | } 86 | 87 | @Override 88 | public Object visitInsert(MySQLStatementParser.InsertContext ctx) { 89 | commandType = COMMAND_TYPE.INSERT; 90 | return super.visitInsert(ctx); 91 | } 92 | 93 | @Override 94 | public Object visitUpdate(MySQLStatementParser.UpdateContext ctx) { 95 | commandType = COMMAND_TYPE.MODIFY; 96 | return super.visitUpdate(ctx); 97 | } 98 | 99 | @Override 100 | public Object visitSelect(MySQLStatementParser.SelectContext ctx) { 101 | commandType = COMMAND_TYPE.QUERY; 102 | return super.visitSelect(ctx); 103 | } 104 | 105 | // Command Attributes for Query & Delete 106 | 107 | @Override 108 | public Object visitTableName(MySQLStatementParser.TableNameContext ctx) { 109 | this.tableName = ctx.name().getText(); 110 | return super.visitTableName(ctx); 111 | } 112 | 113 | 114 | @Override 115 | public Object visitProjection(MySQLStatementParser.ProjectionContext ctx) { 116 | this.selectFields.add(ctx.expr().getText()); 117 | return super.visitProjection(ctx); 118 | } 119 | 120 | 121 | @Override 122 | public Object visitExpr(MySQLStatementParser.ExprContext ctx) { 123 | if (ctx.booleanPrimary() != null && ctx.booleanPrimary().comparisonOperator() != null && ctx.booleanPrimary().comparisonOperator() != null && ctx.booleanPrimary().comparisonOperator().getText().equals("=")) { 124 | B_Term term = getTerm(ctx.booleanPrimary()); 125 | predicate.conjoinWith(new A_Predicate(term)); 126 | } 127 | return super.visitExpr(ctx); 128 | } 129 | 130 | private B_Term getTerm(MySQLStatementParser.BooleanPrimaryContext term) { 131 | MySQLStatementParser.BooleanPrimaryContext lhs = term.booleanPrimary(); 132 | MySQLStatementParser.PredicateContext rhs = term.predicate(); 133 | 134 | C_Expression lhsExp = new C_Expression(lhs.getText()); 135 | C_Expression rhsExp = null; 136 | 137 | if (rhs.bitExpr() != null && rhs.bitExpr(0).simpleExpr() != null && rhs.bitExpr(0).simpleExpr().literals() != null && rhs.bitExpr(0).simpleExpr().literals().numberLiterals() != null && !rhs.bitExpr(0).simpleExpr().literals().numberLiterals().isEmpty()) { 138 | // Number 139 | Integer num = Integer.parseInt(rhs.getText()); 140 | rhsExp = new C_Expression(new D_Constant(num)); 141 | } else if (rhs.bitExpr() != null && rhs.bitExpr(0).simpleExpr() != null && rhs.bitExpr(0).simpleExpr().literals() != null && rhs.bitExpr(0).simpleExpr().literals().stringLiterals() != null && !rhs.bitExpr(0).simpleExpr().literals().stringLiterals().isEmpty()) { 142 | // String 143 | rhsExp = new C_Expression(new D_Constant(rhs.getText())); 144 | } 145 | 146 | return new B_Term(lhsExp, rhsExp); 147 | } 148 | 149 | // Command Attributes for CreateIndex 150 | @Override 151 | public Object visitIndexName(MySQLStatementParser.IndexNameContext ctx) { 152 | this.indexName = ctx.getText(); 153 | return super.visitIndexName(ctx); 154 | } 155 | 156 | @Override 157 | public Object visitKeyPart(MySQLStatementParser.KeyPartContext ctx) { 158 | this.indexFieldName = ctx.getText(); 159 | return super.visitKeyPart(ctx); 160 | } 161 | 162 | // Command Attributes for Insert & Part of Update 163 | @Override 164 | public Object visitInsertIdentifier(MySQLStatementParser.InsertIdentifierContext ctx) { 165 | this.insertFields.add(ctx.getText()); 166 | return super.visitInsertIdentifier(ctx); 167 | } 168 | 169 | @Override 170 | public Object visitNumberLiterals(MySQLStatementParser.NumberLiteralsContext ctx) { 171 | this.insertValues.add(new D_Constant(Integer.parseInt(ctx.getText()))); 172 | this.updateFieldValue = new C_Expression(new D_Constant(Integer.parseInt(ctx.getText()))); 173 | 174 | return super.visitNumberLiterals(ctx); 175 | } 176 | 177 | @Override 178 | public Object visitStringLiterals(MySQLStatementParser.StringLiteralsContext ctx) { 179 | this.insertValues.add(new D_Constant(ctx.getText())); 180 | this.updateFieldValue = new C_Expression(new D_Constant(ctx.getText())); 181 | 182 | return super.visitStringLiterals(ctx); 183 | } 184 | 185 | // Command Attributes for Update 186 | @Override 187 | public Object visitAssignment(MySQLStatementParser.AssignmentContext ctx) { 188 | this.updateFieldName = ctx.columnRef().getText(); 189 | return super.visitAssignment(ctx); 190 | } 191 | 192 | 193 | // Command Create Table 194 | @Override 195 | public Object visitColumnDefinition(MySQLStatementParser.ColumnDefinitionContext ctx) { 196 | String fieldName = ctx.column_name.getText(); 197 | String dataType = ctx.fieldDefinition().dataType().getText(); 198 | if (dataType.equals("int")) { 199 | schema.addIntField(fieldName); 200 | } else if (dataType.startsWith("varchar")) { 201 | dataType = dataType.substring("varchar".length()); 202 | dataType = dataType.replace("(", ""); 203 | dataType = dataType.replace(")", ""); 204 | int length = Integer.parseInt(dataType); 205 | schema.addStringField(fieldName, length); 206 | } else { 207 | throw new RuntimeException("Unsupported Column Type"); 208 | } 209 | return super.visitColumnDefinition(ctx); 210 | } 211 | 212 | public Object getValue() { 213 | switch (commandType) { 214 | case QUERY: 215 | return new QueryData(selectFields, tableName, predicate); 216 | case DELETE: 217 | return new DeleteData(tableName, predicate); 218 | case CREATE_INDEX: 219 | return new CreateIndexData(indexName, tableName, indexFieldName); 220 | case INSERT: 221 | return new InsertData(tableName, insertFields, insertValues); 222 | case MODIFY: 223 | return new ModifyData(tableName, updateFieldName, updateFieldValue, predicate); 224 | case CREATE_TABLE: 225 | return new CreateTableData(tableName, schema); 226 | } 227 | return new QueryData(selectFields, tableName, predicate); 228 | } 229 | 230 | 231 | enum COMMAND_TYPE { 232 | QUERY, MODIFY, INSERT, DELETE, CREATE_TABLE, CREATE_INDEX 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/IQueryEngine.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.dto.TableDto; 4 | 5 | /** 6 | * Query Engine interface. We can implement using Calcite or Custom. 7 | * 8 | * @author Arjun Sunil Kumar 9 | */ 10 | public interface IQueryEngine { 11 | 12 | public TableDto doQuery(String sql); 13 | 14 | public TableDto doUpdate(String sql); 15 | 16 | public void close(); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/catalog/MetadataMgr.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.catalog; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.index.IndexMgr; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats.StatMgr; 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats.domain.StatInfo; 6 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 7 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableMgr; 8 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 10 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.index.IndexInfo; 11 | 12 | import java.util.Map; 13 | 14 | /** 15 | * The Composite Manager for Index and Catalog 16 | * 17 | * @author Edward Sciore 18 | */ 19 | public class MetadataMgr { 20 | private static TableMgr tblmgr; 21 | private static IndexMgr idxmgr; 22 | private static StatMgr statmgr; 23 | 24 | public MetadataMgr(boolean isnew, Transaction tx) { 25 | tblmgr = new TableMgr(isnew, tx); 26 | statmgr = new StatMgr(tblmgr, tx); 27 | idxmgr = new IndexMgr(isnew, tblmgr, statmgr, tx); 28 | } 29 | 30 | public void createTable(String tblname, TableDefinition sch, Transaction tx) { 31 | tblmgr.createTable(tblname, sch, tx); 32 | } 33 | 34 | public TablePhysicalLayout getLayout(String tblname, Transaction tx) { 35 | return tblmgr.getLayout(tblname, tx); 36 | } 37 | 38 | 39 | public void createIndex(String idxname, String tblname, String fldname, Transaction tx) { 40 | idxmgr.createIndex(idxname, tblname, fldname, tx); 41 | } 42 | 43 | public Map getIndexInfo(String tblname, Transaction tx) { 44 | return idxmgr.getIndexInfo(tblname, tx); 45 | } 46 | 47 | public StatInfo getStatInfo(String tblname, TablePhysicalLayout layout, Transaction tx) { 48 | return statmgr.getStatInfo(tblname, layout, tx); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/catalog/index/IndexInfo.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.catalog.index; 2 | 3 | import static java.sql.Types.INTEGER; 4 | 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats.domain.StatInfo; 6 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 7 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.RWIndexScan; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 10 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.BasicBPlusTreeIndex; 11 | 12 | 13 | /** 14 | * The information about an index. This information is used by the query planner in order to 15 | * estimate the costs of using the index, and to obtain the layout of the index records. Its methods 16 | * are essentially the same as those of Plan. 17 | * 18 | * @author Edward Sciore 19 | */ 20 | public class IndexInfo { 21 | 22 | private String idxname, fldname; 23 | private Transaction tx; 24 | private TableDefinition tblTableDefinition; 25 | private TablePhysicalLayout idxRecordValueLayout; 26 | private StatInfo si; 27 | 28 | 29 | public IndexInfo(String idxname, String fldname, TableDefinition tblTableDefinition, 30 | Transaction tx, StatInfo si) { 31 | this.idxname = idxname; 32 | this.fldname = fldname; 33 | this.tx = tx; 34 | this.tblTableDefinition = tblTableDefinition; 35 | this.idxRecordValueLayout = createIdxLayout(); 36 | this.si = si; 37 | } 38 | 39 | 40 | public RWIndexScan open() { 41 | return new BasicBPlusTreeIndex(tx, idxname, idxRecordValueLayout); 42 | } 43 | 44 | 45 | private TablePhysicalLayout createIdxLayout() { 46 | // Schema contains Block, Id, DataValue 47 | TableDefinition sch = new TableDefinition(); 48 | sch.addIntField("block"); 49 | sch.addIntField("id"); 50 | if (tblTableDefinition.type(fldname) == INTEGER) { 51 | sch.addIntField("dataval"); 52 | } else { 53 | int fldlen = tblTableDefinition.length(fldname); 54 | sch.addStringField("dataval", fldlen); 55 | } 56 | return new TablePhysicalLayout(sch); 57 | } 58 | 59 | public int blocksAccessed() { 60 | int rpb = tx.blockSize() / idxRecordValueLayout.slotSize(); 61 | int numblocks = si.recordsOutput() / rpb; 62 | return BasicBPlusTreeIndex.searchCost(numblocks, rpb); 63 | } 64 | 65 | public int recordsOutput() { 66 | return si.recordsOutput() / si.distinctValues(fldname); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/catalog/index/IndexMgr.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.catalog.index; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableMgr; 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.HeapRWRecordScan; 8 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats.StatMgr; 9 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats.domain.StatInfo; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | 14 | /** 15 | * The index manager. The index manager has similar functionality to the table manager. 16 | * 17 | * @author Edward Sciore 18 | */ 19 | public class IndexMgr { 20 | 21 | private TablePhysicalLayout recordValueLayout; 22 | private TableMgr tblmgr; 23 | private StatMgr statmgr; 24 | 25 | /** 26 | * Create the index manager. This constructor is called during system startup. If the database is 27 | * new, then the idxcat table is created. 28 | * 29 | * @param isnew indicates whether this is a new database 30 | * @param tx the system startup transaction 31 | */ 32 | public IndexMgr(boolean isnew, TableMgr tblmgr, StatMgr statmgr, Transaction tx) { 33 | if (isnew) { 34 | TableDefinition sch = new TableDefinition(); 35 | sch.addStringField("indexname", TableMgr.MAX_NAME); 36 | sch.addStringField("tablename", TableMgr.MAX_NAME); 37 | sch.addStringField("fieldname", TableMgr.MAX_NAME); 38 | 39 | tblmgr.createTable("idxcat", sch, tx); 40 | } 41 | this.tblmgr = tblmgr; 42 | this.statmgr = statmgr; 43 | recordValueLayout = tblmgr.getLayout("idxcat", tx); 44 | } 45 | 46 | /** 47 | * Create an index of the specified type for the specified field. A unique ID is assigned to this 48 | * index, and its information is stored in the idxcat table. 49 | * 50 | * @param idxname the name of the index 51 | * @param tblname the name of the indexed table 52 | * @param fldname the name of the indexed field 53 | * @param tx the calling transaction 54 | */ 55 | public void createIndex(String idxname, String tblname, String fldname, Transaction tx) { 56 | HeapRWRecordScan ts = new HeapRWRecordScan(tx, "idxcat", recordValueLayout); 57 | ts.seekToInsertStart(); 58 | ts.setString("indexname", idxname); 59 | ts.setString("tablename", tblname); 60 | ts.setString("fieldname", fldname); 61 | ts.close(); 62 | } 63 | 64 | /** 65 | * Return a map containing the index info for all indexes on the specified table. 66 | * 67 | * @param tblname the name of the table 68 | * @param tx the calling transaction 69 | * @return a map of IndexInfo objects, keyed by their field names 70 | */ 71 | public Map getIndexInfo(String tblname, Transaction tx) { 72 | Map result = new HashMap(); 73 | HeapRWRecordScan ts = new HeapRWRecordScan(tx, "idxcat", recordValueLayout); 74 | while (ts.next()) { 75 | if (ts.getString("tablename").equals(tblname)) { 76 | String idxname = ts.getString("indexname"); 77 | String fldname = ts.getString("fieldname"); 78 | TablePhysicalLayout tblRecordValueLayout = tblmgr.getLayout(tblname, tx); 79 | StatInfo tblsi = statmgr.getStatInfo(tblname, tblRecordValueLayout, tx); 80 | 81 | IndexInfo ii = new IndexInfo(idxname, fldname, tblRecordValueLayout.schema(), tx, tblsi); 82 | result.put(fldname, ii); 83 | } 84 | } 85 | ts.close(); 86 | return result; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/catalog/stats/StatMgr.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats.domain.StatInfo; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableMgr; 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * State Manager for cost based query planner. 13 | * 14 | * @author Edward Sciore 15 | */ 16 | public class StatMgr { 17 | private int numcalls; 18 | private Map tablestats; 19 | private TableMgr tblMgr; 20 | 21 | public StatMgr(TableMgr tblMgr, Transaction tx) { 22 | this.tblMgr = tblMgr; 23 | refreshStatistics(tx); 24 | } 25 | 26 | public synchronized StatInfo getStatInfo(String tblname, TablePhysicalLayout layout, Transaction tx) { 27 | numcalls++; 28 | if (numcalls > 100) refreshStatistics(tx); 29 | StatInfo si = tablestats.get(tblname); 30 | if (si == null) { 31 | si = calcTableStats(tblname, layout, tx); 32 | tablestats.put(tblname, si); 33 | } 34 | return si; 35 | } 36 | 37 | private synchronized void refreshStatistics(Transaction tx) { 38 | tablestats = new HashMap(); 39 | } 40 | 41 | private synchronized StatInfo calcTableStats(String tblname, TablePhysicalLayout layout, Transaction tx) { 42 | return null; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/catalog/stats/domain/StatInfo.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats.domain; 2 | 3 | /** 4 | * A StatInfo object holds three pieces of 5 | * statistical information about a table: 6 | * the number of blocks, the number of records, 7 | * and the number of distinct values for each field. 8 | * 9 | * @author Edward Sciore 10 | */ 11 | public class StatInfo { 12 | private int numBlocks; 13 | private int numRecs; 14 | 15 | /** 16 | * Create a StatInfo object. 17 | * Note that the number of distinct values is not 18 | * passed into the constructor. 19 | * The object fakes this value. 20 | * 21 | * @param numblocks the number of blocks in the table 22 | * @param numrecs the number of records in the table 23 | */ 24 | public StatInfo(int numblocks, int numrecs) { 25 | this.numBlocks = numblocks; 26 | this.numRecs = numrecs; 27 | } 28 | 29 | /** 30 | * Return the estimated number of blocks in the table. 31 | * 32 | * @return the estimated number of blocks in the table 33 | */ 34 | public int blocksAccessed() { 35 | return numBlocks; 36 | } 37 | 38 | /** 39 | * Return the estimated number of records in the table. 40 | * 41 | * @return the estimated number of records in the table 42 | */ 43 | public int recordsOutput() { 44 | return numRecs; 45 | } 46 | 47 | /** 48 | * Return the estimated number of distinct values 49 | * for the specified field. 50 | * This estimate is a complete guess, because doing something 51 | * reasonable is beyond the scope of this system. 52 | * 53 | * @param fldname the name of the field 54 | * @return a guess as to the number of distinct field values 55 | */ 56 | public int distinctValues(String fldname) { 57 | return 1 + (numRecs / 3); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/catalog/table/TableDefinition.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table; 2 | 3 | import lombok.ToString; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import static java.sql.Types.INTEGER; 11 | import static java.sql.Types.VARCHAR; 12 | 13 | /** 14 | * The record schema of a table. 15 | * A schema contains the name and type of 16 | * each field of the table, as well as the length 17 | * of each varchar field. 18 | * 19 | * @author Edward Sciore, Arjun Sunil Kumar 20 | */ 21 | @ToString 22 | public class TableDefinition { 23 | private final List fields = new ArrayList<>(); 24 | private final Map info = new HashMap<>(); 25 | 26 | /** 27 | * Add a field to the schema having a specified 28 | * name, type, and length. 29 | * If the field type is "integer", then the length 30 | * value is irrelevant. 31 | * 32 | * @param fldname the name of the field 33 | * @param type the type of the field, according to the constants in simpledb.sql.types 34 | * @param length the conceptual length of a string field. 35 | */ 36 | public void addField(String fldname, int type, int length) { 37 | fields.add(fldname); 38 | info.put(fldname, new FieldInfo(type, length)); 39 | } 40 | 41 | /** 42 | * Add an integer field to the schema. 43 | * 44 | * @param fldname the name of the field 45 | */ 46 | public void addIntField(String fldname) { 47 | addField(fldname, INTEGER, 0); 48 | } 49 | 50 | /** 51 | * Add a string field to the schema. 52 | * The length is the conceptual length of the field. 53 | * For example, if the field is defined as varchar(8), 54 | * then its length is 8. 55 | * 56 | * @param fldname the name of the field 57 | * @param length the number of chars in the varchar definition 58 | */ 59 | public void addStringField(String fldname, int length) { 60 | addField(fldname, VARCHAR, length); 61 | } 62 | 63 | /** 64 | * Add a field to the schema having the same 65 | * type and length as the corresponding field 66 | * in another schema. 67 | * 68 | * @param fldname the name of the field 69 | * @param sch the other schema 70 | */ 71 | public void add(String fldname, TableDefinition sch) { 72 | int type = sch.type(fldname); 73 | int length = sch.length(fldname); 74 | addField(fldname, type, length); 75 | } 76 | 77 | /** 78 | * Add all of the fields in the specified schema 79 | * to the current schema. 80 | * 81 | * @param sch the other schema 82 | */ 83 | public void addAll(TableDefinition sch) { 84 | for (String fldname : sch.fields()) 85 | add(fldname, sch); 86 | } 87 | 88 | /** 89 | * Return a collection containing the name of 90 | * each field in the schema. 91 | * 92 | * @return the collection of the schema's field names 93 | */ 94 | public List fields() { 95 | return fields; 96 | } 97 | 98 | /** 99 | * Return true if the specified field 100 | * is in the schema 101 | * 102 | * @param fldname the name of the field 103 | * @return true if the field is in the schema 104 | */ 105 | public boolean hasField(String fldname) { 106 | return fields.contains(fldname); 107 | } 108 | 109 | /** 110 | * Return the type of the specified field, using the 111 | * constants in {@link java.sql.Types}. 112 | * 113 | * @param fldname the name of the field 114 | * @return the integer type of the field 115 | */ 116 | public int type(String fldname) { 117 | return info.get(fldname).type; 118 | } 119 | 120 | /** 121 | * Return the conceptual length of the specified field. 122 | * If the field is not a string field, then 123 | * the return value is undefined. 124 | * 125 | * @param fldname the name of the field 126 | * @return the conceptual length of the field 127 | */ 128 | public int length(String fldname) { 129 | return info.get(fldname).length; 130 | } 131 | 132 | @ToString 133 | class FieldInfo { 134 | int type, length; 135 | 136 | public FieldInfo(int type, int length) { 137 | this.type = type; 138 | this.length = length; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/catalog/table/TableMgr.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table; 2 | 3 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.HeapRWRecordScan; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | /** 10 | * The table manager. There are methods to create a table, save the metadata in the catalog, and 11 | * obtain the metadata of a previously-created table. 12 | * 13 | * @author Edward Sciore 14 | */ 15 | public class TableMgr { 16 | 17 | // The max characters a tablename or fieldname can have. 18 | public static final int MAX_NAME = 16; 19 | private TablePhysicalLayout tcatRecordValueLayout, fcatRecordValueLayout; 20 | 21 | /** 22 | * Create a new catalog manager for the database system. If the database is new, the two catalog 23 | * tables are created. 24 | * 25 | * @param isNew has the value true if the database is new 26 | * @param tx the startup transaction 27 | */ 28 | public TableMgr(boolean isNew, Transaction tx) { 29 | TableDefinition tcatTableDefinition = new TableDefinition(); 30 | tcatTableDefinition.addStringField("tblname", MAX_NAME); 31 | tcatTableDefinition.addIntField("slotsize"); 32 | tcatRecordValueLayout = new TablePhysicalLayout(tcatTableDefinition); 33 | 34 | TableDefinition fcatTableDefinition = new TableDefinition(); 35 | fcatTableDefinition.addStringField("tblname", MAX_NAME); 36 | fcatTableDefinition.addStringField("fldname", MAX_NAME); 37 | fcatTableDefinition.addIntField("type"); 38 | fcatTableDefinition.addIntField("length"); 39 | fcatTableDefinition.addIntField("offset"); 40 | fcatRecordValueLayout = new TablePhysicalLayout(fcatTableDefinition); 41 | 42 | if (isNew) { 43 | createTable("tinydb_tables", tcatTableDefinition, tx); 44 | createTable("tinydb_columns", fcatTableDefinition, tx); 45 | } 46 | } 47 | 48 | /** 49 | * Create a new table having the specified name and schema. 50 | * 51 | * @param tblname the name of the new table 52 | * @param sch the table's schema 53 | * @param tx the transaction creating the table 54 | */ 55 | public void createTable(String tblname, TableDefinition sch, Transaction tx) { 56 | TablePhysicalLayout recordValueLayout = new TablePhysicalLayout(sch); 57 | 58 | HeapRWRecordScan tcat = new HeapRWRecordScan(tx, "tinydb_tables", tcatRecordValueLayout); 59 | tcat.seekToInsertStart(); 60 | tcat.setString("tblname", tblname); 61 | tcat.setInt("slotsize", recordValueLayout.slotSize()); 62 | tcat.close(); 63 | 64 | HeapRWRecordScan fcat = new HeapRWRecordScan(tx, "tinydb_columns", fcatRecordValueLayout); 65 | for (String fldname : sch.fields()) { 66 | fcat.seekToInsertStart(); 67 | fcat.setString("tblname", tblname); 68 | fcat.setString("fldname", fldname); 69 | fcat.setInt("type", sch.type(fldname)); 70 | fcat.setInt("length", sch.length(fldname)); 71 | fcat.setInt("offset", recordValueLayout.offset(fldname)); 72 | } 73 | fcat.close(); 74 | } 75 | 76 | /** 77 | * Retrieve the layout of the specified table from the catalog. 78 | * 79 | * @param tblname the name of the table 80 | * @param tx the transaction 81 | * @return the table's stored metadata 82 | */ 83 | public TablePhysicalLayout getLayout(String tblname, Transaction tx) { 84 | int size = -1; 85 | HeapRWRecordScan tcat = new HeapRWRecordScan(tx, "tinydb_tables", tcatRecordValueLayout); 86 | // [tableName1, slotsize1] | [tableName2, slotsize2] | [tableName3, slotsize3] 87 | while (tcat.next()) { 88 | if (tcat.getString("tblname").equals(tblname)) { 89 | size = tcat.getInt("slotsize"); 90 | break; 91 | } 92 | } 93 | tcat.close(); 94 | 95 | TableDefinition sch = new TableDefinition(); 96 | Map offsets = new HashMap(); 97 | HeapRWRecordScan fcat = new HeapRWRecordScan(tx, "tinydb_columns", fcatRecordValueLayout); 98 | // [tableName1, A, int, 4, offset] | [tableName1, B, varchar, 9, offset] | [tableName3, fldname, type, length offset] 99 | while (fcat.next()) { 100 | if (fcat.getString("tblname").equals(tblname)) { 101 | String fldname = fcat.getString("fldname"); 102 | int fldtype = fcat.getInt("type"); 103 | int fldlen = fcat.getInt("length"); 104 | int offset = fcat.getInt("offset"); 105 | offsets.put(fldname, offset); 106 | sch.addField(fldname, fldtype, fldlen); 107 | } 108 | } 109 | fcat.close(); 110 | return new TablePhysicalLayout(sch, offsets, size); 111 | } 112 | } -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/catalog/table/TablePhysicalLayout.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table; 2 | 3 | 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.Page; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static java.sql.Types.INTEGER; 10 | 11 | /** 12 | * Description of the structure of a record. 13 | * It contains the name, type, length and offset of 14 | * each field of the table. 15 | * 16 | * @author Edward Sciore, Arjun Sunil Kumar 17 | */ 18 | public class TablePhysicalLayout { 19 | private TableDefinition tableDefinition; 20 | private Map offsets; 21 | private int slotsize; 22 | 23 | /** 24 | * This constructor creates a Layout object from a schema. 25 | * This constructor is used when a table 26 | * is created. It determines the physical offset of 27 | * each field within the record. 28 | * 29 | * @param tblname the name of the table 30 | * @param tableDefinition the schema of the table's records 31 | */ 32 | public TablePhysicalLayout(TableDefinition tableDefinition) { 33 | this.tableDefinition = tableDefinition; 34 | offsets = new HashMap<>(); 35 | int pos = Integer.BYTES; // leave space for the empty/inuse flag 36 | // [Inuse [0], A [1 Byte], B [5 Byte] 37 | for (String fldname : tableDefinition.fields()) { 38 | offsets.put(fldname, pos); 39 | pos += lengthInBytes(fldname); 40 | } 41 | slotsize = pos; 42 | } 43 | 44 | /** 45 | * Create a Layout object from the specified metadata. 46 | * This constructor is used when the metadata 47 | * is retrieved from the catalog. 48 | * 49 | * @param tblname the name of the table 50 | * @param tableDefinition the schema of the table's records 51 | * @param offsets the already-calculated offsets of the fields within a record 52 | * @param recordlen the already-calculated length of each record 53 | */ 54 | public TablePhysicalLayout(TableDefinition tableDefinition, Map offsets, int slotsize) { 55 | this.tableDefinition = tableDefinition; 56 | this.offsets = offsets; 57 | this.slotsize = slotsize; 58 | } 59 | 60 | /** 61 | * Return the schema of the table's records 62 | * 63 | * @return the table's record schema 64 | */ 65 | public TableDefinition schema() { 66 | return tableDefinition; 67 | } 68 | 69 | /** 70 | * Return the offset of a specified field within a record 71 | * 72 | * @param fldname the name of the field 73 | * @return the offset of that field within a record 74 | */ 75 | public int offset(String fldname) { 76 | return offsets.get(fldname); 77 | } 78 | 79 | /** 80 | * Return the size of a slot, in bytes. 81 | * 82 | * @return the size of a slot 83 | */ 84 | public int slotSize() { 85 | return slotsize; 86 | } 87 | 88 | private int lengthInBytes(String fldname) { 89 | int fldtype = tableDefinition.type(fldname); 90 | if (fldtype == INTEGER) 91 | return Integer.BYTES; // 4 92 | else // VARCHAR(-->9) 93 | return Page.maxBytesRequiredForString(tableDefinition.length(fldname)); 94 | } 95 | } 96 | 97 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/common/dto/TableDto.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.common.dto; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * The DTO that is returned to the CLI Driver. 7 | * 8 | * @author Arjun Sunil Kumar 9 | */ 10 | public class TableDto { 11 | 12 | public List columnNames; 13 | 14 | public List> rowValues; 15 | 16 | public String message; 17 | 18 | public TableDto(List columnNames, List> rowValues) { 19 | this.columnNames = columnNames; 20 | this.rowValues = rowValues; 21 | this.message = ""; 22 | } 23 | 24 | public TableDto(String message) { 25 | this.message = message; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/BasicQueryEngine.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.dto.TableDto; 5 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.BasicPlanner; 6 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 7 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.QueryPlanner; 8 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.UpdatePlanner; 9 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.b_rule_base.BetterQueryPlanner; 10 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.b_rule_base.BetterUpdatePlanner; 11 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 12 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.FileMgr; 13 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 14 | import com.arjunsk.tiny_db.server.b_query_engine.IQueryEngine; 15 | 16 | import java.io.File; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | /** 20 | * The ANTLR parser for SQLite Dialect. 21 | * 22 | * @author Edward Sciore, Arjun Sunil Kumar 23 | */ 24 | public class BasicQueryEngine implements IQueryEngine { 25 | public static int BLOCK_SIZE = 512; 26 | 27 | private final FileMgr fm; 28 | private final BasicPlanner planner; 29 | 30 | public BasicQueryEngine(String dirname) { 31 | File dbDirectory = new File(dirname); 32 | fm = new FileMgr(dbDirectory, BLOCK_SIZE); 33 | Transaction tx = newTx(); 34 | 35 | MetadataMgr mdm = new MetadataMgr(fm.isNew(), tx); 36 | QueryPlanner qp = new BetterQueryPlanner(mdm); 37 | UpdatePlanner up = new BetterUpdatePlanner(mdm); 38 | planner = new BasicPlanner(qp, up); 39 | 40 | tx.commit(); 41 | } 42 | 43 | 44 | public TableDto doQuery(String sql) { 45 | Transaction tx = newTx(); 46 | Plan p = planner.createQueryPlan(sql, tx); 47 | RORecordScan s = p.open(); 48 | 49 | List columnNames = p.schema().fields(); 50 | 51 | List> rows = new ArrayList<>(); 52 | while (s.next()) { 53 | List row = new ArrayList<>(); 54 | for (String field : columnNames) row.add(s.getVal(field).toString()); 55 | rows.add(row); 56 | } 57 | 58 | s.close(); 59 | tx.commit(); 60 | 61 | return new TableDto(columnNames, rows); 62 | } 63 | 64 | 65 | public TableDto doUpdate(String sql) { 66 | Transaction tx = newTx(); 67 | int updatedRows = planner.executeUpdate(sql, tx); 68 | tx.commit(); 69 | 70 | String message = updatedRows + " " + (updatedRows == 1 ? "row" : "rows") + " updated."; 71 | return new TableDto(message); 72 | } 73 | 74 | public void close() { 75 | System.out.println("Shutting down"); 76 | } 77 | 78 | private Transaction newTx() { 79 | return new Transaction(fm); 80 | } 81 | 82 | public FileMgr fileMgr() { 83 | return fm; 84 | } 85 | } 86 | 87 | 88 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/BasicPlanner.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.IParser; 4 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.*; 5 | import com.arjunsk.tiny_db.server.a_frontend.impl.mysql.MySqlParser; 6 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 7 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.QueryPlanner; 8 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.UpdatePlanner; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 10 | 11 | 12 | /** 13 | * The composite planner handling Query and Update. 14 | * 15 | * @author Edward Sciore 16 | */ 17 | public class BasicPlanner { 18 | private final QueryPlanner queryPlanner; 19 | private final UpdatePlanner updatePlanner; 20 | 21 | public BasicPlanner(QueryPlanner queryPlanner, UpdatePlanner updatePlanner) { 22 | this.queryPlanner = queryPlanner; 23 | this.updatePlanner = updatePlanner; 24 | } 25 | 26 | public Plan createQueryPlan(String qry, Transaction tx) { 27 | IParser parser = new MySqlParser(qry); 28 | QueryData data = parser.queryCmd(); 29 | // System.out.println(data); 30 | return queryPlanner.createPlan(data, tx); 31 | } 32 | 33 | public int executeUpdate(String cmd, Transaction tx) { 34 | IParser parser = new MySqlParser(cmd); 35 | Object data = parser.updateCmd(); 36 | // System.out.println(data); 37 | if (data instanceof InsertData) return updatePlanner.executeInsert((InsertData) data, tx); 38 | else if (data instanceof DeleteData) return updatePlanner.executeDelete((DeleteData) data, tx); 39 | else if (data instanceof ModifyData) return updatePlanner.executeModify((ModifyData) data, tx); 40 | else if (data instanceof CreateTableData) return updatePlanner.executeCreateTable((CreateTableData) data, tx); 41 | else if (data instanceof CreateIndexData) return updatePlanner.executeCreateIndex((CreateIndexData) data, tx); 42 | else return 0; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/README.md: -------------------------------------------------------------------------------- 1 | # Query Optimizer 2 | 3 | The Optimizer (Oracle), Query Optimizer (SQL Server, MySQL) or Query Planner (PostgreSQL) translates the SQL statement 4 | to an executable program, in form of an execution plan, very much like a compiler translates source code into an 5 | executable. 6 | 7 | ## Kinds 8 | 9 | There are generally two kinds of Optimizers: 10 | 11 | 1. Rule Based Optimizer (RBO): Rule Based Optimizers follow a strict set of rules to create an execution plan — e.g., 12 | always use an index if possible. 13 | 14 | 2. Cost Based Optimizer (CBO): Cost Based Optimizers generate many different execution plans, apply a cost model to all 15 | of them and select the one with the best cost value for execution. -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/plan/Plan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 5 | 6 | /** 7 | * Plan encapsulate relational algebra and cost of operation. 8 | * This is passed on to Planner for getting an efficient physical plan. 9 | * 10 | * @author Edward Sciore, Arjun Sunil Kumar 11 | */ 12 | public interface Plan { 13 | 14 | RORecordScan open(); 15 | 16 | TableDefinition schema(); 17 | 18 | int blocksAccessed(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/plan/impl/A_TablePlan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.stats.domain.StatInfo; 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 6 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 7 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 10 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.HeapRWRecordScan; 11 | 12 | /** 13 | * The Plan class corresponding to a table. 14 | * 15 | * @author Edward Sciore 16 | */ 17 | public class A_TablePlan implements Plan { 18 | 19 | private String tblname; 20 | private Transaction tx; 21 | private TablePhysicalLayout recordValueLayout; 22 | private StatInfo si; 23 | 24 | public A_TablePlan(Transaction tx, String tblname, MetadataMgr md) { 25 | this.tblname = tblname; 26 | this.tx = tx; 27 | recordValueLayout = md.getLayout(tblname, tx); 28 | } 29 | 30 | 31 | public RORecordScan open() { 32 | 33 | // NOTE: The Place where Query Engine interacts with StorageEngine 34 | return new HeapRWRecordScan(tx, tblname, recordValueLayout); 35 | } 36 | 37 | 38 | public TableDefinition schema() { 39 | return recordValueLayout.schema(); 40 | } 41 | 42 | @Override 43 | public int blocksAccessed() { 44 | return si.blocksAccessed(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/plan/impl/B_SelectPlan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.A_Predicate; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 5 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.b_execution_engine.A_Select_RWRecordScan; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 7 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 8 | 9 | /** 10 | * The Plan class corresponding to the select 11 | * relational algebra operator. 12 | * 13 | * @author Edward Sciore 14 | */ 15 | public class B_SelectPlan implements Plan { 16 | private Plan p; 17 | private A_Predicate pred; 18 | 19 | public B_SelectPlan(Plan p, A_Predicate pred) { 20 | this.p = p; 21 | this.pred = pred; 22 | } 23 | 24 | public RORecordScan open() { 25 | RORecordScan s = p.open(); 26 | return new A_Select_RWRecordScan(s, pred); 27 | } 28 | 29 | 30 | public TableDefinition schema() { 31 | return p.schema(); 32 | } 33 | 34 | @Override 35 | public int blocksAccessed() { 36 | return p.blocksAccessed(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/plan/impl/B_SelectWithIndexPlan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.index.IndexInfo; 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 6 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 7 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.b_execution_engine.A_SelectUsingIndex_RORecordScan; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.RWIndexScan; 10 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.HeapRWRecordScan; 11 | 12 | /** 13 | * The Plan class corresponding to the indexselect 14 | * relational algebra operator. 15 | * 16 | * @author Edward Sciore, Arjun Sunil Kumar 17 | */ 18 | public class B_SelectWithIndexPlan implements Plan { 19 | private Plan p; 20 | private IndexInfo ii; 21 | private D_Constant val; 22 | 23 | 24 | public B_SelectWithIndexPlan(Plan p, IndexInfo ii, D_Constant val) { 25 | this.p = p; 26 | this.ii = ii; 27 | this.val = val; 28 | } 29 | 30 | 31 | public RORecordScan open() { 32 | // throws an exception if p is not a tableplan. 33 | HeapRWRecordScan scan = (HeapRWRecordScan) p.open(); 34 | RWIndexScan idx = ii.open(); 35 | return new A_SelectUsingIndex_RORecordScan(scan, idx, val); 36 | } 37 | 38 | public TableDefinition schema() { 39 | return p.schema(); 40 | } 41 | 42 | @Override 43 | public int blocksAccessed() { 44 | return ii.blocksAccessed() + ii.recordsOutput(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/plan/impl/C_ProjectPlan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 4 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.b_execution_engine.C_Project_RORecordScan; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 6 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Projection - Subset of columns. 12 | * 13 | * @author Edward Sciore, Arjun Sunil Kumar 14 | */ 15 | public class C_ProjectPlan implements Plan { 16 | private Plan p; 17 | private TableDefinition tableDefinition = new TableDefinition(); 18 | 19 | 20 | public C_ProjectPlan(Plan p, List fieldlist) { 21 | this.p = p; 22 | for (String fldname : fieldlist) 23 | tableDefinition.add(fldname, p.schema()); 24 | } 25 | 26 | 27 | public RORecordScan open() { 28 | RORecordScan s = p.open(); 29 | return new C_Project_RORecordScan(s, tableDefinition.fields()); 30 | } 31 | 32 | public TableDefinition schema() { 33 | return tableDefinition; 34 | } 35 | 36 | @Override 37 | public int blocksAccessed() { 38 | return p.blocksAccessed(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/QueryPlanner.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.QueryData; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 5 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 6 | 7 | 8 | /** 9 | * The Query Planner 10 | * 11 | * @author Edward Sciore 12 | */ 13 | public interface QueryPlanner { 14 | 15 | Plan createPlan(QueryData data, Transaction tx); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/UpdatePlanner.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.*; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 5 | 6 | 7 | /** 8 | * The Update Planner 9 | * 10 | * @author Edward Sciore 11 | */ 12 | public interface UpdatePlanner { 13 | 14 | int executeCreateTable(CreateTableData data, Transaction tx); 15 | 16 | int executeCreateIndex(CreateIndexData data, Transaction tx); 17 | 18 | int executeInsert(InsertData data, Transaction tx); 19 | 20 | int executeModify(ModifyData data, Transaction tx); 21 | 22 | int executeDelete(DeleteData data, Transaction tx); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/a_naive/BasicQueryPlanner.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.a_naive; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.QueryData; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 6 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 7 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.C_ProjectPlan; 8 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.B_SelectPlan; 9 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.A_TablePlan; 10 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.QueryPlanner; 11 | 12 | /** 13 | * The Query Planner without Indexes and cost considerations. 14 | * 15 | * @author Edward Sciore, Arjun Sunil Kumar 16 | */ 17 | public class BasicQueryPlanner implements QueryPlanner { 18 | private MetadataMgr mdm; 19 | 20 | public BasicQueryPlanner(MetadataMgr mdm) { 21 | this.mdm = mdm; 22 | } 23 | 24 | public Plan createPlan(QueryData data, Transaction tx) { 25 | //Step 1: Create the plan 26 | Plan p = new A_TablePlan(tx, data.table(), mdm); 27 | 28 | //Step 3: Add a selection plan for the predicate 29 | p = new B_SelectPlan(p, data.pred()); 30 | 31 | //Step 4: Project on the field names 32 | p = new C_ProjectPlan(p, data.fields()); 33 | return p; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/a_naive/BasicUpdatePlanner.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.a_naive; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.*; 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.RWRecordScan; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 8 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 9 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.B_SelectPlan; 10 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.A_TablePlan; 11 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.UpdatePlanner; 12 | 13 | import java.util.Iterator; 14 | 15 | /** 16 | * The Update Planner without Indexes and cost considerations. 17 | * 18 | * @author Edward Sciore, Arjun Sunil Kumar 19 | */ 20 | public class BasicUpdatePlanner implements UpdatePlanner { 21 | private MetadataMgr mdm; 22 | 23 | public BasicUpdatePlanner(MetadataMgr mdm) { 24 | this.mdm = mdm; 25 | } 26 | 27 | public int executeCreateTable(CreateTableData data, Transaction tx) { 28 | mdm.createTable(data.tableName(), data.newSchema(), tx); 29 | return 0; 30 | } 31 | 32 | public int executeCreateIndex(CreateIndexData data, Transaction tx) { 33 | mdm.createIndex(data.indexName(), data.tableName(), data.fieldName(), tx); 34 | return 0; 35 | } 36 | 37 | public int executeInsert(InsertData data, Transaction tx) { 38 | Plan p = new A_TablePlan(tx, data.tableName(), mdm); 39 | RWRecordScan scan = (RWRecordScan) p.open(); 40 | scan.seekToInsertStart(); 41 | Iterator iter = data.vals().iterator(); 42 | for (String fldname : data.fields()) { 43 | D_Constant val = iter.next(); 44 | scan.setVal(fldname, val); 45 | } 46 | scan.close(); 47 | return 1; 48 | } 49 | 50 | 51 | public int executeModify(ModifyData data, Transaction tx) { 52 | Plan p = new A_TablePlan(tx, data.tableName(), mdm); 53 | p = new B_SelectPlan(p, data.pred()); 54 | RWRecordScan scan = (RWRecordScan) p.open(); 55 | int count = 0; 56 | while (scan.next()) { 57 | D_Constant val = data.newValue().evaluate(scan); 58 | scan.setVal(data.targetField(), val); 59 | count++; 60 | } 61 | scan.close(); 62 | return count; 63 | } 64 | 65 | public int executeDelete(DeleteData data, Transaction tx) { 66 | Plan p = new A_TablePlan(tx, data.tableName(), mdm); 67 | p = new B_SelectPlan(p, data.pred()); 68 | RWRecordScan us = (RWRecordScan) p.open(); 69 | int count = 0; 70 | while (us.next()) { 71 | us.delete(); 72 | count++; 73 | } 74 | us.close(); 75 | return count; 76 | } 77 | 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/b_rule_base/BetterQueryPlanner.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.b_rule_base; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.QueryData; 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 6 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.index.IndexInfo; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 8 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 9 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.C_ProjectPlan; 10 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.B_SelectPlan; 11 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.B_SelectWithIndexPlan; 12 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.A_TablePlan; 13 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.QueryPlanner; 14 | 15 | import java.util.Map; 16 | 17 | /** 18 | * The Query Planner without Indexes. 19 | * 20 | * @author Edward Sciore, Arjun Sunil Kumar 21 | */ 22 | public class BetterQueryPlanner implements QueryPlanner { 23 | private MetadataMgr mdm; 24 | 25 | public BetterQueryPlanner(MetadataMgr mdm) { 26 | this.mdm = mdm; 27 | } 28 | 29 | 30 | public Plan createPlan(QueryData data, Transaction tx) { 31 | 32 | //TODO: issue with SELECT with Index and NonIndex field 33 | /* 34 | create table T2 ( A int, B varchar(9) ); 35 | create index A_IDX on T2(A); 36 | insert into T2 (A, B) values (1, 'Alice'); 37 | insert into T2 (A, B) values (2, 'Bob'); 38 | select A,B from T2; 39 | select A,B from T2 where A=2 and B='Alice'; 40 | * */ 41 | Plan p = new A_TablePlan(tx, data.table(), mdm); 42 | 43 | boolean indexFound = false; 44 | Map indexes = mdm.getIndexInfo(data.table(), tx); 45 | for (String columnName : indexes.keySet()) { 46 | D_Constant columnValue = data.pred().equatesWithConstant(columnName); 47 | if (columnValue != null) { 48 | IndexInfo columnIndexInfo = indexes.get(columnName); 49 | p = new B_SelectWithIndexPlan(p, columnIndexInfo, columnValue); 50 | 51 | indexFound = true; 52 | System.out.println("index on " + columnName + " used"); 53 | break; 54 | } 55 | } 56 | 57 | if (!indexFound) p = new B_SelectPlan(p, data.pred()); 58 | 59 | p = new C_ProjectPlan(p, data.fields()); 60 | return p; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/b_rule_base/BetterUpdatePlanner.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.b_rule_base; 2 | 3 | 4 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 5 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.*; 6 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 7 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.index.IndexInfo; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.RWIndexScan; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.RWRecordScan; 10 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 11 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 12 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.Plan; 13 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.B_SelectPlan; 14 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.plan.impl.A_TablePlan; 15 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.UpdatePlanner; 16 | 17 | import java.util.Iterator; 18 | import java.util.Map; 19 | 20 | 21 | /** 22 | * The Update Planner with Indexes. 23 | * 24 | * @author Edward Sciore, Arjun Sunil Kumar 25 | */ 26 | public class BetterUpdatePlanner implements UpdatePlanner { 27 | private MetadataMgr mdm; 28 | 29 | public BetterUpdatePlanner(MetadataMgr mdm) { 30 | this.mdm = mdm; 31 | } 32 | 33 | public int executeCreateTable(CreateTableData data, Transaction tx) { 34 | mdm.createTable(data.tableName(), data.newSchema(), tx); 35 | return 0; 36 | } 37 | 38 | 39 | public int executeCreateIndex(CreateIndexData data, Transaction tx) { 40 | mdm.createIndex(data.indexName(), data.tableName(), data.fieldName(), tx); 41 | return 0; 42 | } 43 | 44 | public int executeInsert(InsertData data, Transaction tx) { 45 | String tblname = data.tableName(); 46 | Plan p = new A_TablePlan(tx, tblname, mdm); 47 | 48 | // first, insert the record 49 | RWRecordScan scan = (RWRecordScan) p.open(); 50 | scan.seekToInsertStart(); 51 | 52 | // then modify each field, inserting an index record if appropriate 53 | RecordKey recordKey = scan.getRid(); 54 | Map indexes = mdm.getIndexInfo(tblname, tx); 55 | Iterator valIter = data.vals().iterator(); 56 | for (String fldname : data.fields()) { 57 | D_Constant val = valIter.next(); 58 | scan.setVal(fldname, val); 59 | 60 | IndexInfo ii = indexes.get(fldname); 61 | if (ii != null) { 62 | RWIndexScan idx = ii.open(); 63 | idx.insert(val, recordKey); 64 | idx.close(); 65 | } 66 | } 67 | scan.close(); 68 | return 1; 69 | } 70 | 71 | public int executeDelete(DeleteData data, Transaction tx) { 72 | String tblname = data.tableName(); 73 | Plan p = new A_TablePlan(tx, tblname, mdm); 74 | p = new B_SelectPlan(p, data.pred()); 75 | Map indexes = mdm.getIndexInfo(tblname, tx); 76 | 77 | RWRecordScan s = (RWRecordScan) p.open(); 78 | int count = 0; 79 | while (s.next()) { 80 | // first, delete the record's RID from every index 81 | RecordKey recordKey = s.getRid(); 82 | for (String fldname : indexes.keySet()) { 83 | D_Constant val = s.getVal(fldname); 84 | RWIndexScan idx = indexes.get(fldname).open(); 85 | idx.delete(val, recordKey); 86 | idx.close(); 87 | } 88 | // then delete the record 89 | s.delete(); 90 | count++; 91 | } 92 | s.close(); 93 | return count; 94 | } 95 | 96 | public int executeModify(ModifyData data, Transaction tx) { 97 | String tblname = data.tableName(); 98 | String fldname = data.targetField(); 99 | Plan p = new A_TablePlan(tx, tblname, mdm); 100 | p = new B_SelectPlan(p, data.pred()); 101 | 102 | IndexInfo ii = mdm.getIndexInfo(tblname, tx).get(fldname); 103 | RWIndexScan idx = (ii == null) ? null : ii.open(); 104 | 105 | RWRecordScan s = (RWRecordScan) p.open(); 106 | int count = 0; 107 | while (s.next()) { 108 | // first, update the record 109 | D_Constant newval = data.newValue().evaluate(s); 110 | D_Constant oldval = s.getVal(fldname); 111 | s.setVal(data.targetField(), newval); 112 | 113 | // then update the appropriate index, if it exists 114 | if (idx != null) { 115 | RecordKey recordKey = s.getRid(); 116 | idx.delete(oldval, recordKey); 117 | idx.insert(newval, recordKey); 118 | } 119 | count++; 120 | } 121 | if (idx != null) idx.close(); 122 | s.close(); 123 | return count; 124 | } 125 | 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/b_rule_base/README.md: -------------------------------------------------------------------------------- 1 | ## NOTE 2 | 3 | Here the rule is, if index is present, always use the index. -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/c_cost_based/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/a_query_optimizer/planner/c_cost_based/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/b_execution_engine/A_SelectUsingIndex_RORecordScan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.b_execution_engine; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.RWIndexScan; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.HeapRWRecordScan; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 8 | 9 | 10 | /** 11 | * The Scan using Index. (Invoked by QueryPlanner) 12 | * 13 | * @author Edward Sciore, Arjun Sunil Kumar 14 | */ 15 | public class A_SelectUsingIndex_RORecordScan implements RORecordScan { 16 | private final HeapRWRecordScan ts; 17 | private final RWIndexScan idx; 18 | 19 | // Start value of Index Field. 20 | private final D_Constant val; 21 | 22 | 23 | public A_SelectUsingIndex_RORecordScan(HeapRWRecordScan ts, RWIndexScan idx, D_Constant val) { 24 | this.ts = ts; 25 | this.idx = idx; 26 | this.val = val; 27 | seekToQueryStart(); 28 | } 29 | 30 | public void seekToQueryStart() { 31 | idx.seek(val); 32 | } 33 | 34 | public boolean next() { 35 | boolean ok = idx.hasNext(); 36 | if (ok) { 37 | RecordKey recordKey = idx.next(); 38 | ts.seekTo(recordKey); 39 | } 40 | return ok; 41 | } 42 | 43 | 44 | public int getInt(String fldname) { 45 | return ts.getInt(fldname); 46 | } 47 | 48 | public String getString(String fldname) { 49 | return ts.getString(fldname); 50 | } 51 | 52 | 53 | public D_Constant getVal(String fldname) { 54 | return ts.getVal(fldname); 55 | } 56 | 57 | 58 | public boolean hasField(String fldname) { 59 | return ts.hasField(fldname); 60 | } 61 | 62 | 63 | public void close() { 64 | idx.close(); 65 | ts.close(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/b_execution_engine/A_Select_RWRecordScan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.b_execution_engine; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.A_Predicate; 4 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 5 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.commands.ModifyData; 6 | import com.arjunsk.tiny_db.server.b_query_engine.impl.basic.a_query_optimizer.planner.b_rule_base.BetterUpdatePlanner; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.RWRecordScan; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 10 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 11 | 12 | /** 13 | * The scan class corresponding to the select relational algebra operator. All methods except 14 | * next delegate their work to the underlying scan. 15 | * 16 | * @author Edward Sciore 17 | */ 18 | public class A_Select_RWRecordScan implements RWRecordScan { 19 | 20 | private RORecordScan s; 21 | private A_Predicate pred; 22 | 23 | /** 24 | * Create a select scan having the specified underlying scan and predicate. 25 | * 26 | * @param s the scan of the underlying query 27 | * @param pred the selection predicate 28 | */ 29 | public A_Select_RWRecordScan(RORecordScan s, A_Predicate pred) { 30 | this.s = s; 31 | this.pred = pred; 32 | } 33 | 34 | // Scan methods 35 | 36 | public void seekToQueryStart() { 37 | s.seekToQueryStart(); 38 | } 39 | 40 | public boolean next() { 41 | while (s.next()) { 42 | if (pred.isSatisfied(s)) { 43 | return true; 44 | } 45 | } 46 | return false; 47 | } 48 | 49 | public int getInt(String fldname) { 50 | return s.getInt(fldname); 51 | } 52 | 53 | public String getString(String fldname) { 54 | return s.getString(fldname); 55 | } 56 | 57 | public D_Constant getVal(String fldname) { 58 | return s.getVal(fldname); 59 | } 60 | 61 | public boolean hasField(String fldname) { 62 | return s.hasField(fldname); 63 | } 64 | 65 | public void close() { 66 | s.close(); 67 | } 68 | 69 | //--------------- RWDataRecordScan Methods 70 | 71 | /** 72 | * Usage 73 | * {@link 74 | * BetterUpdatePlanner#executeModify(ModifyData, 75 | * Transaction)} 76 | */ 77 | 78 | 79 | public void setInt(String fldname, int val) { 80 | RWRecordScan us = (RWRecordScan) s; 81 | us.setInt(fldname, val); 82 | } 83 | 84 | public void setString(String fldname, String val) { 85 | RWRecordScan us = (RWRecordScan) s; 86 | us.setString(fldname, val); 87 | } 88 | 89 | public void setVal(String fldname, D_Constant val) { 90 | RWRecordScan us = (RWRecordScan) s; 91 | us.setVal(fldname, val); 92 | } 93 | 94 | public void delete() { 95 | RWRecordScan us = (RWRecordScan) s; 96 | us.delete(); 97 | } 98 | 99 | public void seekToInsertStart() { 100 | RWRecordScan us = (RWRecordScan) s; 101 | us.seekToInsertStart(); 102 | } 103 | 104 | public RecordKey getRid() { 105 | RWRecordScan us = (RWRecordScan) s; 106 | return us.getRid(); 107 | } 108 | 109 | public void seekTo(RecordKey recordKey) { 110 | RWRecordScan us = (RWRecordScan) s; 111 | us.seekTo(recordKey); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/b_execution_engine/C_Project_RORecordScan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.basic.b_execution_engine; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.RORecordScan; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * The scan class corresponding to the project relational 10 | * algebra operator. 11 | * All methods except hasField delegate their work to the 12 | * underlying scan. 13 | * 14 | * @author Edward Sciore 15 | */ 16 | public class C_Project_RORecordScan implements RORecordScan { 17 | private final RORecordScan s; 18 | private final List fieldlist; 19 | 20 | /** 21 | * Create a project scan having the specified 22 | * underlying scan and field list. 23 | * 24 | * @param s the underlying scan 25 | * @param fieldlist the list of field names 26 | */ 27 | public C_Project_RORecordScan(RORecordScan s, List fieldlist) { 28 | this.s = s; 29 | this.fieldlist = fieldlist; 30 | } 31 | 32 | public void seekToQueryStart() { 33 | s.seekToQueryStart(); 34 | } 35 | 36 | public boolean next() { 37 | return s.next(); 38 | } 39 | 40 | public int getInt(String fldname) { 41 | if (hasField(fldname)) 42 | return s.getInt(fldname); 43 | else 44 | throw new RuntimeException("field " + fldname + " not found."); 45 | } 46 | 47 | public String getString(String fldname) { 48 | if (hasField(fldname)) 49 | return s.getString(fldname); 50 | else 51 | throw new RuntimeException("field " + fldname + " not found."); 52 | } 53 | 54 | public D_Constant getVal(String fldname) { 55 | if (hasField(fldname)) 56 | return s.getVal(fldname); 57 | else 58 | throw new RuntimeException("field " + fldname + " not found."); 59 | } 60 | 61 | public boolean hasField(String fldname) { 62 | return fieldlist.contains(fldname); 63 | } 64 | 65 | public void close() { 66 | s.close(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/basic/b_execution_engine/README.md: -------------------------------------------------------------------------------- 1 | ## Notes 2 | 3 | 1. This will contain Join Scans 4 | 2. External Sorting 5 | 3. Aggregations etc. -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/calcite/CalciteQueryEngine.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.calcite; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.impl.calcite.core.B_Table; 4 | import com.arjunsk.tiny_db.server.b_query_engine.impl.calcite.core.C_Schema; 5 | import com.arjunsk.tiny_db.server.b_query_engine.impl.calcite.core.D_JavaSqlTypeToCalciteSqlTypeConversionRules; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.FileMgr; 7 | import com.arjunsk.tiny_db.server.b_query_engine.IQueryEngine; 8 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 9 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 10 | import com.arjunsk.tiny_db.server.b_query_engine.common.dto.TableDto; 11 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 12 | import java.io.File; 13 | import java.sql.Connection; 14 | import java.sql.DriverManager; 15 | import java.sql.ResultSet; 16 | import java.sql.Statement; 17 | import java.util.ArrayList; 18 | import java.util.Collections; 19 | import java.util.List; 20 | import java.util.Properties; 21 | import java.util.stream.Collectors; 22 | import lombok.SneakyThrows; 23 | import org.apache.calcite.jdbc.CalciteConnection; 24 | import org.apache.calcite.schema.SchemaPlus; 25 | import org.apache.calcite.sql.type.SqlTypeName; 26 | 27 | /** 28 | * QueryEngine Implementation with Calcite Parser and Planner. 29 | *

30 | * NOTE: There is no Optimizer implemented. Also, Since ModifiableTable is not implemented in 31 | * {@link B_Table}, we currently don't support DML operations. 32 | * 33 | * @author Arjun Sunil Kumar 34 | */ 35 | public class CalciteQueryEngine implements IQueryEngine { 36 | 37 | public static int BLOCK_SIZE = 512; 38 | FileMgr fm; 39 | MetadataMgr mdm; 40 | Connection connection; 41 | 42 | String tableName = "T1"; 43 | String schemaName = "tinydb"; 44 | 45 | @SneakyThrows 46 | public CalciteQueryEngine(String dirname) { 47 | 48 | //1. Init MetaDataManager (Catalog) 49 | File dbDirectory = new File(dirname); 50 | fm = new FileMgr(dbDirectory, BLOCK_SIZE); 51 | Transaction tx1 = newTx(); 52 | mdm = new MetadataMgr(fm.isNew(), tx1); 53 | tx1.commit(); 54 | 55 | // 4.a JDBC similar 56 | Class.forName("org.apache.calcite.jdbc.Driver"); 57 | Properties info = new Properties(); 58 | info.setProperty("lex", "JAVA"); 59 | connection = DriverManager.getConnection("jdbc:calcite:", info); 60 | } 61 | 62 | 63 | /** 64 | * Use the Syntax: 65 | * 66 | * select A,B from tinydb.T1; 67 | * 68 | */ 69 | @SneakyThrows 70 | public TableDto doQuery(String sql) { 71 | //2.a Get Table Layout 72 | Transaction tx2 = newTx(); 73 | TablePhysicalLayout tableLayout = mdm.getLayout(tableName, tx2); 74 | 75 | // 2.b Create List 76 | D_JavaSqlTypeToCalciteSqlTypeConversionRules dataTypeRules = D_JavaSqlTypeToCalciteSqlTypeConversionRules.instance(); 77 | List fieldTypes = tableLayout.schema().fields().stream() 78 | .map(e -> tableLayout.schema().type(e)).map(dataTypeRules::lookup) 79 | .collect(Collectors.toList()); 80 | 81 | // 2.c Create CalciteTable Object using fieldNames, fieldTypes etc 82 | B_Table calciteTable = new B_Table(tableName, tableLayout.schema().fields(), fieldTypes, tx2, 83 | mdm); 84 | 85 | // 3. Create Schema for the CalciteTable 86 | C_Schema schema = new C_Schema(Collections.singletonMap(tableName, calciteTable)); 87 | 88 | // 4. Add schema to the SQL root schema 89 | 90 | // 4.b Unwrap and add proxy 91 | CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class); 92 | SchemaPlus rootSchema = calciteConnection.getRootSchema(); 93 | rootSchema.add(schemaName, schema); 94 | 95 | // 5. Execute JDBC Query 96 | Statement statement = calciteConnection.createStatement(); 97 | ResultSet rs = statement.executeQuery(sql); 98 | 99 | List columnNames = tableLayout.schema().fields(); 100 | List> rows = new ArrayList<>(); 101 | while (rs.next()) { 102 | List row = new ArrayList<>(); 103 | for (String field : columnNames) { 104 | row.add(rs.getString(field)); 105 | } 106 | rows.add(row); 107 | } 108 | 109 | rs.close(); 110 | statement.close(); 111 | tx2.commit(); 112 | 113 | return new TableDto(columnNames, rows); 114 | } 115 | 116 | @Override 117 | public TableDto doUpdate(String sql) { 118 | return null; 119 | } 120 | 121 | @SneakyThrows 122 | @Override 123 | public void close() { 124 | connection.close(); 125 | } 126 | 127 | private Transaction newTx() { 128 | return new Transaction(fm); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/calcite/core/A_Enumerator.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.calcite.core; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 4 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.RWRecordScan; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.HeapRWRecordScan; 8 | import org.apache.calcite.linq4j.Enumerator; 9 | 10 | /** 11 | * Enumerator connects with Catalog and StorageEngine ReadWrite Iterator, to create a calcite 12 | * readable iterator 13 | * 14 | * @author Arjun Sunil Kumar 15 | */ 16 | class A_Enumerator implements Enumerator { 17 | 18 | private final RWRecordScan scan; 19 | private final TablePhysicalLayout layout; 20 | 21 | A_Enumerator(MetadataMgr mdm, Transaction tx, String tableName) { 22 | layout = mdm.getLayout(tableName, tx); 23 | this.scan = new HeapRWRecordScan(tx, tableName, layout); 24 | } 25 | 26 | @Override 27 | public Object[] current() { 28 | return layout.schema().fields().stream().map(this.scan::getVal) 29 | .map(e -> e.asInt() != null ? e.asInt() : e.asString()).toArray(); 30 | } 31 | 32 | @Override 33 | public boolean moveNext() { 34 | return this.scan.next(); 35 | } 36 | 37 | @Override 38 | public void reset() { 39 | this.scan.seekToQueryStart(); 40 | } 41 | 42 | @Override 43 | public void close() { 44 | this.scan.close(); 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/calcite/core/B_Table.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.calcite.core; 2 | 3 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import org.apache.calcite.DataContext; 8 | import org.apache.calcite.linq4j.AbstractEnumerable; 9 | import org.apache.calcite.linq4j.Enumerable; 10 | import org.apache.calcite.linq4j.Enumerator; 11 | import org.apache.calcite.rel.type.RelDataType; 12 | import org.apache.calcite.rel.type.RelDataTypeFactory; 13 | import org.apache.calcite.rel.type.RelDataTypeField; 14 | import org.apache.calcite.rel.type.RelDataTypeFieldImpl; 15 | import org.apache.calcite.rel.type.RelRecordType; 16 | import org.apache.calcite.rel.type.StructKind; 17 | import org.apache.calcite.schema.ScannableTable; 18 | import org.apache.calcite.schema.impl.AbstractTable; 19 | import org.apache.calcite.sql.type.SqlTypeName; 20 | 21 | /** 22 | * Calcite Table build using Enumerator 23 | * 24 | * @author Arjun Sunil Kumar 25 | */ 26 | public class B_Table extends AbstractTable implements ScannableTable {//, ModifiableTable { 27 | 28 | private final String tableName; 29 | private final List fieldNames; 30 | private final List fieldTypes; 31 | 32 | private final Transaction tx; 33 | private final MetadataMgr mdm; 34 | 35 | private RelDataType rowType; 36 | 37 | public B_Table(String tableName, List fieldNames, List fieldTypes, 38 | Transaction tx, MetadataMgr mdm 39 | ) { 40 | this.tableName = tableName; 41 | this.fieldNames = fieldNames; 42 | this.fieldTypes = fieldTypes; 43 | 44 | this.tx = tx; 45 | this.mdm = mdm; 46 | } 47 | 48 | @Override 49 | public RelDataType getRowType(RelDataTypeFactory typeFactory) { 50 | // singleton pattern 51 | if (rowType == null) { 52 | List fields = new ArrayList<>(fieldNames.size()); 53 | 54 | for (int i = 0; i < fieldNames.size(); i++) { 55 | RelDataType fieldType = typeFactory.createSqlType(fieldTypes.get(i)); 56 | RelDataTypeField field = new RelDataTypeFieldImpl(fieldNames.get(i), i, fieldType); 57 | fields.add(field); 58 | } 59 | 60 | rowType = new RelRecordType(StructKind.PEEK_FIELDS, fields, false); 61 | } 62 | 63 | return rowType; 64 | } 65 | 66 | @Override 67 | public Enumerable scan(DataContext dataContext) { 68 | return new AbstractEnumerable() { 69 | @Override 70 | public Enumerator enumerator() { 71 | return new A_Enumerator(mdm, tx, tableName); 72 | } 73 | }; 74 | } 75 | 76 | 77 | } -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/calcite/core/C_Schema.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.calcite.core; 2 | 3 | import java.util.Map; 4 | import org.apache.calcite.schema.Table; 5 | import org.apache.calcite.schema.impl.AbstractSchema; 6 | 7 | /** 8 | * Calcite Schema containing multiple table. 9 | *

10 | * This schema will be injected as Proxy to the JDBMs schema in connection. 11 | * 12 | * @author Arjun Sunil Kumar 13 | */ 14 | public class C_Schema extends AbstractSchema { 15 | 16 | private final Map tableMap; 17 | 18 | public C_Schema(Map tableMap) { 19 | this.tableMap = tableMap; 20 | } 21 | 22 | @Override 23 | public Map getTableMap() { 24 | return tableMap; 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/calcite/core/CalciteTest.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.calcite.core; 2 | 3 | import com.arjunsk.tiny_db.cli.utils.TablePrinter; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.FileMgr; 5 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.MetadataMgr; 6 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 7 | import com.arjunsk.tiny_db.server.b_query_engine.common.dto.TableDto; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 9 | import java.io.File; 10 | import java.sql.Connection; 11 | import java.sql.DriverManager; 12 | import java.sql.ResultSet; 13 | import java.sql.SQLException; 14 | import java.sql.Statement; 15 | import java.util.ArrayList; 16 | import java.util.Collections; 17 | import java.util.List; 18 | import java.util.Properties; 19 | import java.util.stream.Collectors; 20 | import org.apache.calcite.jdbc.CalciteConnection; 21 | import org.apache.calcite.schema.SchemaPlus; 22 | import org.apache.calcite.sql.type.SqlTypeName; 23 | 24 | /** 25 | * Testing Calcite Overall Flow 26 | * 27 | * @author Arjun Sunil Kumar 28 | */ 29 | public class CalciteTest { 30 | 31 | public static int BLOCK_SIZE = 512; 32 | FileMgr fm; 33 | 34 | public void run(String tableName, String schemaName) throws SQLException, ClassNotFoundException { 35 | //1. Init MetaDataManager (Catalog) 36 | File dbDirectory = new File("tinydb"); 37 | fm = new FileMgr(dbDirectory, BLOCK_SIZE); 38 | Transaction tx1 = newTx(); 39 | MetadataMgr mdm = new MetadataMgr(fm.isNew(), tx1); 40 | tx1.commit(); 41 | 42 | //2.a Get Table Layout 43 | Transaction tx2 = newTx(); 44 | TablePhysicalLayout tableLayout = mdm.getLayout(tableName, tx2); 45 | 46 | // 2.b Create List 47 | D_JavaSqlTypeToCalciteSqlTypeConversionRules dataTypeRules = D_JavaSqlTypeToCalciteSqlTypeConversionRules.instance(); 48 | List fieldTypes = tableLayout.schema().fields().stream() 49 | .map(e -> tableLayout.schema().type(e)).map(dataTypeRules::lookup) 50 | .collect(Collectors.toList()); 51 | 52 | // 2.c Create CalciteTable Object using fieldNames, fieldTypes etc 53 | B_Table calciteTable = new B_Table(tableName, tableLayout.schema().fields(), fieldTypes, tx2, 54 | mdm); 55 | 56 | // 3. Create Schema for the CalciteTable 57 | C_Schema schema = new C_Schema(Collections.singletonMap(tableName, calciteTable)); 58 | 59 | // 4. Add schema to the SQL root schema 60 | 61 | // 4.a JDBC similar 62 | Class.forName("org.apache.calcite.jdbc.Driver"); 63 | Properties info = new Properties(); 64 | info.setProperty("lex", "JAVA"); 65 | Connection connection = DriverManager.getConnection("jdbc:calcite:", info); 66 | 67 | // 4.b Unwrap and add proxy 68 | CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class); 69 | SchemaPlus rootSchema = calciteConnection.getRootSchema(); 70 | rootSchema.add(schemaName, schema); 71 | 72 | // 5. Execute JDBC Query 73 | Statement statement = calciteConnection.createStatement(); 74 | String sql = "select A,B from " + schemaName + "." + tableName + " as e"; 75 | ResultSet rs = statement.executeQuery(sql); 76 | 77 | // 6. Print 78 | List columnNames = tableLayout.schema().fields(); 79 | List> rows = new ArrayList<>(); 80 | while (rs.next()) { 81 | List row = new ArrayList<>(); 82 | for (String field : columnNames) { 83 | row.add(rs.getString(field)); 84 | } 85 | rows.add(row); 86 | } 87 | TableDto result = new TableDto(columnNames, rows); 88 | new TablePrinter().print(result); 89 | 90 | // 7. Close 91 | tx2.commit(); 92 | rs.close(); 93 | statement.close(); 94 | connection.close(); 95 | } 96 | 97 | private Transaction newTx() { 98 | return new Transaction(fm); 99 | } 100 | 101 | public static void main(String[] args) throws SQLException, ClassNotFoundException { 102 | new CalciteTest().run("T1", "tinydb"); 103 | 104 | } 105 | 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/b_query_engine/impl/calcite/core/D_JavaSqlTypeToCalciteSqlTypeConversionRules.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.b_query_engine.impl.calcite.core; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import java.sql.Types; 5 | import java.util.Map; 6 | import org.apache.calcite.sql.type.SqlTypeName; 7 | 8 | /** 9 | * Mapping for Our Storage Engine Datatype and Calcite Data Type 10 | * 11 | * @author Arjun Sunil Kumar 12 | */ 13 | public class D_JavaSqlTypeToCalciteSqlTypeConversionRules { 14 | 15 | private static final D_JavaSqlTypeToCalciteSqlTypeConversionRules INSTANCE = new D_JavaSqlTypeToCalciteSqlTypeConversionRules(); 16 | 17 | private final Map rules = ImmutableMap.builder() 18 | .put(Types.INTEGER, SqlTypeName.INTEGER).put(Types.VARCHAR, SqlTypeName.VARCHAR).build(); 19 | 20 | public static D_JavaSqlTypeToCalciteSqlTypeConversionRules instance() { 21 | return INSTANCE; 22 | } 23 | 24 | 25 | public SqlTypeName lookup(Integer name) { 26 | return rules.getOrDefault(name, SqlTypeName.ANY); 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/common/concurrency/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/common/concurrency/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/common/dist_transaction/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/common/dist_transaction/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/common/recovery/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/common/recovery/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/common/replication/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/common/replication/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/impl/keyvalue/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/impl/keyvalue/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/impl/page/buffer_pool/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/impl/page/buffer_pool/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/impl/page/buffer_pool/pinner/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/c_key_value_store/impl/page/buffer_pool/pinner/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/RORecordScan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | 5 | /** 6 | * The interface will be implemented by each query scan. 7 | * There is a Scan class for each relational 8 | * algebra operator. 9 | * 10 | * @author Edward Sciore, Arjun Sunil Kumar 11 | */ 12 | public interface RORecordScan { 13 | 14 | /** 15 | * Position the scan before its first record. A 16 | * subsequent call to next() will return the first record. 17 | */ 18 | public void seekToQueryStart(); 19 | 20 | /** 21 | * Move the scan to the next record. 22 | * 23 | * @return false if there is no next record 24 | */ 25 | public boolean next(); 26 | 27 | /** 28 | * Return the value of the specified integer field 29 | * in the current record. 30 | * 31 | * @param fldname the name of the field 32 | * @return the field's integer value in the current record 33 | */ 34 | public int getInt(String fldname); 35 | 36 | /** 37 | * Return the value of the specified string field 38 | * in the current record. 39 | * 40 | * @param fldname the name of the field 41 | * @return the field's string value in the current record 42 | */ 43 | public String getString(String fldname); 44 | 45 | /** 46 | * Return the value of the specified field in the current record. 47 | * The value is expressed as a Constant. 48 | * 49 | * @param fldname the name of the field 50 | * @return the value of that field, expressed as a Constant. 51 | */ 52 | public D_Constant getVal(String fldname); 53 | 54 | /** 55 | * Return true if the scan has the specified field. 56 | * 57 | * @param fldname the name of the field 58 | * @return true if the scan has that field 59 | */ 60 | public boolean hasField(String fldname); 61 | 62 | /** 63 | * Close the scan and its subscans, if any. 64 | */ 65 | public void close(); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/RWIndexScan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 5 | 6 | /** 7 | * This interface contains methods to traverse an index. 8 | * 9 | * @author Edward Sciore,Arjun Sunil Kumar 10 | */ 11 | public interface RWIndexScan { 12 | 13 | // CRUD 14 | public void insert(D_Constant key, RecordKey value); 15 | 16 | public void delete(D_Constant key, RecordKey value); 17 | 18 | // Iterator 19 | public void seek(D_Constant key); 20 | 21 | public boolean hasNext(); 22 | 23 | public RecordKey next(); 24 | 25 | 26 | public void close(); 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/RWRecordScan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 5 | 6 | /** 7 | * The interface implemented by all updatable scans. 8 | * 9 | * @author Edward Sciore, Arjun Sunil Kumar 10 | */ 11 | public interface RWRecordScan extends RORecordScan { 12 | /** 13 | * Modify the field value of the current record. 14 | * 15 | * @param fldname the name of the field 16 | * @param val the new value, expressed as a Constant 17 | */ 18 | public void setVal(String fldname, D_Constant val); 19 | 20 | /** 21 | * Modify the field value of the current record. 22 | * 23 | * @param fldname the name of the field 24 | * @param val the new integer value 25 | */ 26 | public void setInt(String fldname, int val); 27 | 28 | /** 29 | * Modify the field value of the current record. 30 | * 31 | * @param fldname the name of the field 32 | * @param val the new string value 33 | */ 34 | public void setString(String fldname, String val); 35 | 36 | /** 37 | * Insert a new record somewhere in the scan. 38 | */ 39 | public void seekToInsertStart(); 40 | 41 | /** 42 | * Delete the current record from the scan. 43 | */ 44 | public void delete(); 45 | 46 | /** 47 | * Return the id of the current record. 48 | * 49 | * @return the id of the current record 50 | */ 51 | public RecordKey getRid(); 52 | 53 | /** 54 | * Position the scan so that the current record has 55 | * the specified id. 56 | * 57 | * @param recordKey the id of the desired record 58 | */ 59 | public void seekTo(RecordKey recordKey); 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/file/BlockId.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.common.file; 2 | 3 | 4 | import lombok.AllArgsConstructor; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * The BlockId is used as an identifier to the Block in a db file. 11 | * 12 | * @author Edward Sciore, Arjun Sunil Kumar 13 | */ 14 | @Getter 15 | @EqualsAndHashCode 16 | @ToString 17 | @AllArgsConstructor 18 | public class BlockId { 19 | 20 | private final String fileName; 21 | private final int blockNumber; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/file/FileMgr.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.common.file; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | 8 | /** 9 | * The File Manager is mainly responsible for all file IO. 10 | *

11 | * Accepts only Blocks for R/W to the File. 12 | * 13 | * @author Edward Sciore 14 | */ 15 | public class FileMgr { 16 | 17 | private final File dbDirectory; 18 | private final int blockSize; 19 | private final boolean isNew; 20 | 21 | public FileMgr(File dbDirectory, int blockSize) { 22 | this.dbDirectory = dbDirectory; 23 | this.blockSize = blockSize; 24 | 25 | isNew = !dbDirectory.exists(); 26 | 27 | // create the directory if the database is new 28 | if (isNew) { 29 | dbDirectory.mkdirs(); 30 | } 31 | } 32 | 33 | public synchronized void read(BlockId blk, Page p) { 34 | try { 35 | RandomAccessFile f = getRandomAccessFile(blk.getFileName()); 36 | f.seek((long) blk.getBlockNumber() * blockSize); 37 | f.getChannel().read(p.contents()); 38 | f.close(); 39 | } catch (IOException e) { 40 | throw new RuntimeException("cannot read block " + blk); 41 | } 42 | } 43 | 44 | 45 | public synchronized void write(BlockId blk, Page p) { 46 | try { 47 | RandomAccessFile f = getRandomAccessFile(blk.getFileName()); 48 | f.seek((long) blk.getBlockNumber() * blockSize); 49 | f.getChannel().write(p.contents()); 50 | f.close(); 51 | } catch (IOException e) { 52 | throw new RuntimeException("cannot write block" + blk); 53 | } 54 | } 55 | 56 | 57 | public synchronized BlockId append(String filename) { 58 | int newBlockNumber = blockCount(filename); 59 | BlockId blk = new BlockId(filename, newBlockNumber); 60 | byte[] b = new byte[blockSize]; 61 | try { 62 | RandomAccessFile f = getRandomAccessFile(blk.getFileName()); 63 | f.seek((long) blk.getBlockNumber() * blockSize); 64 | f.write(b); 65 | f.close(); 66 | } catch (IOException e) { 67 | throw new RuntimeException("cannot append block" + blk); 68 | } 69 | return blk; 70 | } 71 | 72 | 73 | public int blockCount(String filename) { 74 | try { 75 | RandomAccessFile f = getRandomAccessFile(filename); 76 | int result = (int) (f.length() / blockSize); 77 | f.close(); 78 | return result; 79 | } catch (IOException e) { 80 | throw new RuntimeException("cannot access " + filename); 81 | } 82 | } 83 | 84 | 85 | public boolean isNew() { 86 | return isNew; 87 | } 88 | 89 | 90 | public int blockSize() { 91 | return blockSize; 92 | } 93 | 94 | private RandomAccessFile getRandomAccessFile(String fileName) throws FileNotFoundException { 95 | File dbTable = new File(dbDirectory, fileName); 96 | return new RandomAccessFile(dbTable, "rws"); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/file/Page.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.common.file; 2 | 3 | import java.nio.ByteBuffer; 4 | import java.nio.charset.Charset; 5 | import java.nio.charset.StandardCharsets; 6 | 7 | /** 8 | * The Page is an in-memory Block (saved in Storage) 9 | *

10 | * Page Contains ByteBuffer, ie the Data within the Block. 11 | * 12 | * @author Edward Sciore 13 | */ 14 | public class Page { 15 | 16 | public static Charset CHARSET = StandardCharsets.US_ASCII; 17 | private ByteBuffer bb; 18 | 19 | // For creating data buffers 20 | public Page(int blocksize) { 21 | bb = ByteBuffer.allocateDirect(blocksize); 22 | } 23 | 24 | public static int maxBytesRequiredForString(int strlen) { 25 | float bytesPerChar = CHARSET.newEncoder().maxBytesPerChar(); 26 | return Integer.BYTES + (strlen * (int) bytesPerChar); 27 | } 28 | 29 | public int getInt(int offset) { 30 | return bb.getInt(offset); 31 | } 32 | 33 | public void setInt(int offset, int n) { 34 | bb.putInt(offset, n); 35 | } 36 | 37 | public byte[] getBytes(int offset) { 38 | bb.position(offset); 39 | int length = bb.getInt(); 40 | byte[] b = new byte[length]; 41 | bb.get(b); 42 | return b; 43 | } 44 | 45 | public void setBytes(int offset, byte[] b) { 46 | bb.position(offset); 47 | bb.putInt(b.length); 48 | bb.put(b); 49 | } 50 | 51 | public String getString(int offset) { 52 | byte[] b = getBytes(offset); 53 | return new String(b, CHARSET); 54 | } 55 | 56 | public void setString(int offset, String s) { 57 | byte[] b = s.getBytes(CHARSET); 58 | setBytes(offset, b); 59 | } 60 | 61 | // a package private method, needed by FileMgr 62 | ByteBuffer contents() { 63 | bb.position(0); 64 | return bb; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/transaction/Transaction.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.common.transaction; 2 | 3 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.BlockId; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.FileMgr; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.Page; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.d_buffer_mgr.BufferMgr; 7 | 8 | 9 | /** 10 | * Provide transaction management for clients, 11 | * ensuring that all transactions are serializable, recoverable, 12 | * and in general satisfy the ACID properties. 13 | * 14 | * @author Edward Sciore 15 | */ 16 | public class Transaction { 17 | private static final int END_OF_FILE = -1; 18 | private static int nextTxNum = 0; 19 | private FileMgr fm; 20 | private BufferMgr bm; 21 | private int txnum; 22 | 23 | 24 | public Transaction(FileMgr fm) { 25 | this.fm = fm; 26 | txnum = nextTxNumber(); 27 | } 28 | 29 | private static synchronized int nextTxNumber() { 30 | nextTxNum++; 31 | return nextTxNum; 32 | } 33 | 34 | public void commit() { 35 | } 36 | 37 | public void rollback() { 38 | } 39 | 40 | public void recover() { 41 | 42 | } 43 | 44 | /** 45 | * Pin the specified block. 46 | * The transaction manages the buffer for the client. 47 | * 48 | * @param blk a reference to the disk block 49 | */ 50 | public void pin(BlockId blk) { 51 | } 52 | 53 | /** 54 | * Unpin the specified block. 55 | * The transaction looks up the buffer pinned to this block, 56 | * and unpins it. 57 | * 58 | * @param blk a reference to the disk block 59 | */ 60 | public void unpin(BlockId blk) { 61 | } 62 | 63 | public synchronized int getInt(BlockId blk, int offset) { 64 | Page contents = new Page(fm.blockSize()); 65 | fm.read(blk, contents); 66 | return contents.getInt(offset); 67 | } 68 | 69 | public synchronized String getString(BlockId blk, int offset) { 70 | Page contents = new Page(fm.blockSize()); 71 | fm.read(blk, contents); 72 | return contents.getString(offset); 73 | } 74 | 75 | /** 76 | * Store an integer at the specified offset 77 | * of the specified block. 78 | * The method first obtains an XLock on the block. 79 | * It then reads the current value at that offset, 80 | * puts it into an update log record, and 81 | * writes that record to the log. 82 | * Finally, it calls the buffer to store the value, 83 | * passing in the LSN of the log record and the transaction's id. 84 | * 85 | * @param blk a reference to the disk block 86 | * @param offset a byte offset within that block 87 | * @param val the value to be stored 88 | */ 89 | public synchronized void setInt(BlockId blk, int offset, int val) { 90 | Page contents = new Page(fm.blockSize()); 91 | fm.read(blk, contents); 92 | 93 | contents.setInt(offset, val); 94 | fm.write(blk, contents); 95 | } 96 | 97 | /** 98 | * Store a string at the specified offset 99 | * of the specified block. 100 | * The method first obtains an XLock on the block. 101 | * It then reads the current value at that offset, 102 | * puts it into an update log record, and 103 | * writes that record to the log. 104 | * Finally, it calls the buffer to store the value, 105 | * passing in the LSN of the log record and the transaction's id. 106 | * 107 | * @param blk a reference to the disk block 108 | * @param offset a byte offset within that block 109 | * @param val the value to be stored 110 | */ 111 | public synchronized void setString(BlockId blk, int offset, String val) { 112 | Page contents = new Page(fm.blockSize()); 113 | fm.read(blk, contents); 114 | 115 | contents.setString(offset, val); 116 | fm.write(blk, contents); 117 | } 118 | 119 | /** 120 | * Return the number of blocks in the specified file. 121 | * This method first obtains an SLock on the 122 | * "end of the file", before asking the file manager 123 | * to return the file size. 124 | * 125 | * @param filename the name of the file 126 | * @return the number of blocks in the file 127 | */ 128 | public synchronized int blockCount(String filename) { 129 | return fm.blockCount(filename); 130 | } 131 | 132 | /** 133 | * Append a new block to the end of the specified file 134 | * and returns a reference to it. 135 | * This method first obtains an XLock on the 136 | * "end of the file", before performing the append. 137 | * 138 | * @param filename the name of the file 139 | * @return a reference to the newly-created disk block 140 | */ 141 | public synchronized BlockId append(String filename) { 142 | return fm.append(filename); 143 | } 144 | 145 | public synchronized int blockSize() { 146 | return fm.blockSize(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/transaction/a_concurrency/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/transaction/a_concurrency/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/transaction/b_page_pinner/TxnPagesPinner.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.b_page_pinner; 2 | 3 | 4 | /** 5 | * Buffer List will hold the in-memory pages. 6 | * 7 | * @author Edward Sciore, Arjun Sunil Kumar 8 | */ 9 | public class TxnPagesPinner { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/transaction/c_recovery_mgr/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/transaction/c_recovery_mgr/.gitkeep -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/common/transaction/d_buffer_mgr/BufferMgr.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.d_buffer_mgr; 2 | 3 | 4 | /** 5 | * The BufferMgr will contain the Pages read from Storage. 6 | * 7 | * @author Edward Sciore, Arjun Sunil Kumar 8 | */ 9 | public class BufferMgr { 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/data/heap/HeapRWRecordScan.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap; 2 | 3 | import static java.sql.Types.INTEGER; 4 | 5 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.BlockId; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.RWRecordScan; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 10 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.HeapRecordPageImpl; 11 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 12 | 13 | /** 14 | * Provides the enumeration (iterator) for records stored in the Disk. 15 | * 16 | * @author Edward Sciore, Arjun Sunil Kumar 17 | */ 18 | 19 | public class HeapRWRecordScan implements RWRecordScan { 20 | 21 | private final Transaction tx; 22 | private final TablePhysicalLayout recordValueLayout; 23 | private HeapRecordPageImpl rp; 24 | private final String filename; 25 | private int currentSlot; 26 | 27 | public HeapRWRecordScan(Transaction tx, String tblname, TablePhysicalLayout recordValueLayout) { 28 | this.tx = tx; 29 | this.recordValueLayout = recordValueLayout; 30 | filename = tblname + ".tbl"; 31 | if (tx.blockCount(filename) == 0) { 32 | createAndMoveToNewBlock(); 33 | } else { 34 | moveToBlock(0); 35 | } 36 | } 37 | 38 | // Methods that implement Scan 39 | 40 | public void seekToQueryStart() { 41 | moveToBlock(0); 42 | } 43 | 44 | public boolean next() { 45 | currentSlot = rp.findSlotAfter(currentSlot); 46 | while (currentSlot < 0) { 47 | if (atLastBlock()) { 48 | return false; 49 | } 50 | moveToBlock(rp.getBlockId().getBlockNumber() + 1); 51 | currentSlot = rp.findSlotAfter(currentSlot); 52 | } 53 | return true; 54 | } 55 | 56 | public int getInt(String fldname) { 57 | return rp.getInt(currentSlot, fldname); 58 | } 59 | 60 | public String getString(String fldname) { 61 | return rp.getString(currentSlot, fldname); 62 | } 63 | 64 | public D_Constant getVal(String fldname) { 65 | if (recordValueLayout.schema().type(fldname) == INTEGER) { 66 | return new D_Constant(getInt(fldname)); 67 | } else { 68 | return new D_Constant(getString(fldname)); 69 | } 70 | } 71 | 72 | public boolean hasField(String fldname) { 73 | return recordValueLayout.schema().hasField(fldname); 74 | } 75 | 76 | public void close() { 77 | if (rp != null) { 78 | tx.unpin(rp.getBlockId()); 79 | } 80 | } 81 | 82 | // Methods that implement UpdateScan 83 | 84 | public void setInt(String fldname, int val) { 85 | rp.setInt(currentSlot, fldname, val); 86 | } 87 | 88 | public void setString(String fldname, String val) { 89 | rp.setString(currentSlot, fldname, val); 90 | } 91 | 92 | public void setVal(String fldname, D_Constant val) { 93 | if (recordValueLayout.schema().type(fldname) == INTEGER) { 94 | setInt(fldname, val.asInt()); 95 | } else { 96 | setString(fldname, val.asString()); 97 | } 98 | } 99 | 100 | public void seekToInsertStart() { 101 | currentSlot = rp.insertAfter(currentSlot); 102 | while (currentSlot < 0) { 103 | if (atLastBlock()) { 104 | createAndMoveToNewBlock(); 105 | } else { 106 | moveToBlock(rp.getBlockId().getBlockNumber() + 1); 107 | } 108 | currentSlot = rp.insertAfter(currentSlot); 109 | } 110 | } 111 | 112 | public void delete() { 113 | rp.delete(currentSlot); 114 | } 115 | 116 | public void seekTo(RecordKey recordKey) { 117 | close(); 118 | BlockId blk = new BlockId(filename, recordKey.getBlockNumber()); 119 | rp = new HeapRecordPageImpl(tx, blk, recordValueLayout); 120 | currentSlot = recordKey.getSlotNumber(); 121 | } 122 | 123 | public RecordKey getRid() { 124 | return new RecordKey(rp.getBlockId().getBlockNumber(), currentSlot); 125 | } 126 | 127 | // Private auxiliary methods 128 | 129 | private void moveToBlock(int blockNumber) { 130 | close(); 131 | BlockId blk = new BlockId(filename, blockNumber); 132 | rp = new HeapRecordPageImpl(tx, blk, recordValueLayout); 133 | currentSlot = -1; 134 | } 135 | 136 | private void createAndMoveToNewBlock() { 137 | close(); 138 | BlockId blk = tx.append(filename); 139 | rp = new HeapRecordPageImpl(tx, blk, recordValueLayout); 140 | rp.format(); 141 | currentSlot = -1; 142 | } 143 | 144 | private boolean atLastBlock() { 145 | return rp.getBlockId().getBlockNumber() == tx.blockCount(filename) - 1; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/data/heap/page/HeapRecordPageImpl.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page; 2 | 3 | import static java.sql.Types.INTEGER; 4 | 5 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.BlockId; 6 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 7 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 9 | 10 | /** 11 | * Page (Block) containing multiple records. 12 | *

13 | * Here we know the entries of a page as records, due to the knowledge of {@link TablePhysicalLayout} 14 | * 15 | * @author Edward Sciore, Arjun Sunil Kumar 16 | */ 17 | public class HeapRecordPageImpl { 18 | 19 | public static final int EMPTY = 0, USED = 1; 20 | private Transaction tx; 21 | private BlockId blockId; 22 | private TablePhysicalLayout recordValueLayout; 23 | 24 | public HeapRecordPageImpl(Transaction tx, BlockId blockId, TablePhysicalLayout recordValueLayout) { 25 | this.tx = tx; 26 | this.blockId = blockId; 27 | this.recordValueLayout = recordValueLayout; 28 | } 29 | 30 | /** 31 | * Return the integer value stored for the specified field of a specified slot. 32 | * 33 | * @param fldname the name of the field. 34 | * @return the integer stored in that field 35 | */ 36 | public int getInt(int slot, String fldname) { 37 | int fldpos = offset(slot) + recordValueLayout.offset(fldname); 38 | return tx.getInt(blockId, fldpos); 39 | } 40 | 41 | /** 42 | * Return the string value stored for the specified field of the specified slot. 43 | * 44 | * @param fldname the name of the field. 45 | * @return the string stored in that field 46 | */ 47 | public String getString(int slot, String fldname) { 48 | int fldpos = offset(slot) + recordValueLayout.offset(fldname); 49 | return tx.getString(blockId, fldpos); 50 | } 51 | 52 | /** 53 | * Store an integer at the specified field of the specified slot. 54 | * 55 | * @param fldname the name of the field 56 | * @param val the integer value stored in that field 57 | */ 58 | public void setInt(int slot, String fldname, int val) { 59 | int fldpos = offset(slot) + recordValueLayout.offset(fldname); 60 | tx.setInt(blockId, fldpos, val); 61 | } 62 | 63 | /** 64 | * Store a string at the specified field of the specified slot. 65 | * 66 | * @param fldname the name of the field 67 | * @param val the string value stored in that field 68 | */ 69 | public void setString(int slot, String fldname, String val) { 70 | int fldpos = offset(slot) + recordValueLayout.offset(fldname); 71 | tx.setString(blockId, fldpos, val); 72 | } 73 | 74 | public void delete(int slot) { 75 | setFlag(slot, EMPTY); 76 | } 77 | 78 | /** 79 | * Use the layout to format a new block of records. These values should not be logged (because the 80 | * old values are meaningless). 81 | */ 82 | public void format() { 83 | int slot = 0; 84 | while (isValidSlot(slot)) { 85 | tx.setInt(blockId, offset(slot), EMPTY); 86 | TableDefinition sch = recordValueLayout.schema(); 87 | for (String fldname : sch.fields()) { 88 | int fldpos = offset(slot) + recordValueLayout.offset(fldname); 89 | if (sch.type(fldname) == INTEGER) { 90 | tx.setInt(blockId, fldpos, 0); 91 | } else { 92 | tx.setString(blockId, fldpos, ""); 93 | } 94 | } 95 | slot++; 96 | } 97 | } 98 | 99 | public int findSlotAfter(int slot) { 100 | return searchAfter(slot, USED); 101 | } 102 | 103 | public int insertAfter(int slot) { 104 | int newslot = searchAfter(slot, EMPTY); 105 | if (newslot >= 0) { 106 | setFlag(newslot, USED); 107 | } 108 | return newslot; 109 | } 110 | 111 | public BlockId getBlockId() { 112 | return blockId; 113 | } 114 | 115 | // Private auxiliary methods 116 | 117 | /** 118 | * Set the record's empty/inuse flag. 119 | */ 120 | private void setFlag(int slot, int flag) { 121 | tx.setInt(blockId, offset(slot), flag); 122 | } 123 | 124 | private int searchAfter(int slot, int flag) { 125 | slot++; 126 | while (isValidSlot(slot)) { 127 | if (tx.getInt(blockId, offset(slot)) == flag) { 128 | return slot; 129 | } 130 | slot++; 131 | } 132 | return -1; 133 | } 134 | 135 | private boolean isValidSlot(int slot) { 136 | return offset(slot + 1) <= tx.blockSize(); 137 | } 138 | 139 | private int offset(int slot) { 140 | return slot * recordValueLayout.slotSize(); 141 | } 142 | } 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/data/heap/page/RecordKey.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page; 2 | 3 | import java.io.Serializable; 4 | import lombok.AllArgsConstructor; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * An identifier for a record within a file. A RID consists of the block number in the file, and the 11 | * location of the record in that block. 12 | * 13 | * @author Edward Sciore, Arjun Sunil Kumar 14 | */ 15 | @Getter 16 | @EqualsAndHashCode 17 | @ToString 18 | @AllArgsConstructor 19 | public class RecordKey implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | 24 | private int blockNumber; 25 | private int slotNumber; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/advanced/AdvancedBPlusTreeIndex.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.advanced; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.advanced.serde.ConstantSerializer; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.advanced.serde.RecordKeySerializer; 6 | import com.github.davidmoten.bplustree.BPlusTree; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.RWIndexScan; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 10 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 11 | 12 | import java.util.Iterator; 13 | import lombok.SneakyThrows; 14 | 15 | 16 | /** 17 | * B+ tree index using https://github.com/davidmoten/bplustree 18 | * 19 | * @author Arjun Sunil Kumar 20 | */ 21 | public class AdvancedBPlusTreeIndex implements RWIndexScan { 22 | 23 | BPlusTree tree; 24 | Iterator iterator; 25 | 26 | public AdvancedBPlusTreeIndex(Transaction tx, String idxname, TablePhysicalLayout leafRecordValueLayout) { 27 | tree = BPlusTree.file().directory("tinydb") 28 | .maxLeafKeys(32) 29 | .maxNonLeafKeys(8) 30 | .segmentSizeMB(1) 31 | .keySerializer(new ConstantSerializer()) 32 | .valueSerializer(new RecordKeySerializer()) 33 | .naturalOrder(); 34 | } 35 | 36 | @Override 37 | public void insert(D_Constant key, RecordKey value) { 38 | tree.insert(key, value); 39 | } 40 | 41 | @Override 42 | public void delete(D_Constant key, RecordKey value) { 43 | throw new RuntimeException("Not implemented by library. To support later"); 44 | } 45 | 46 | @Override 47 | public void seek(D_Constant key) { 48 | iterator = tree.find(key).iterator(); 49 | } 50 | 51 | @Override 52 | public boolean hasNext() { 53 | return iterator.hasNext(); 54 | } 55 | 56 | @Override 57 | public RecordKey next() { 58 | return iterator.next(); 59 | } 60 | 61 | @SneakyThrows 62 | @Override 63 | public void close() { 64 | tree.close(); 65 | } 66 | 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/advanced/BPlusTreeTest.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.advanced; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.advanced.serde.ConstantSerializer; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.advanced.serde.RecordKeySerializer; 6 | import com.github.davidmoten.bplustree.BPlusTree; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 8 | 9 | public class BPlusTreeTest { 10 | 11 | public static void main(String[] args) throws Exception { 12 | BPlusTree tree = BPlusTree.file().directory("tinydb") 13 | .maxLeafKeys(32) 14 | .maxNonLeafKeys(8) 15 | .segmentSizeMB(1) 16 | .keySerializer(new ConstantSerializer()) 17 | .valueSerializer(new RecordKeySerializer()) 18 | .naturalOrder(); 19 | 20 | tree.insert(new D_Constant(1), new RecordKey(1, 2)); 21 | tree.insert(new D_Constant(2), new RecordKey(3, 4)); 22 | 23 | tree.find(new D_Constant(1)).forEach(e -> System.out.println(e)); 24 | 25 | tree.close(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/advanced/serde/ConstantSerializer.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.advanced.serde; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.github.davidmoten.bplustree.LargeByteBuffer; 5 | import com.github.davidmoten.bplustree.Serializer; 6 | 7 | public class ConstantSerializer implements Serializer { 8 | 9 | @Override 10 | public D_Constant read(LargeByteBuffer bb) { 11 | int ival = bb.getInt(); 12 | return new D_Constant(ival); 13 | } 14 | 15 | @Override 16 | public void write(LargeByteBuffer bb, D_Constant c) { 17 | bb.putInt(c.asInt()); 18 | } 19 | 20 | @Override 21 | public int maxSize() { 22 | return Integer.BYTES; 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/advanced/serde/RecordKeySerializer.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.advanced.serde; 2 | 3 | import com.github.davidmoten.bplustree.LargeByteBuffer; 4 | import com.github.davidmoten.bplustree.Serializer; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 6 | 7 | public class RecordKeySerializer implements Serializer { 8 | 9 | @Override 10 | public RecordKey read(LargeByteBuffer bb) { 11 | int blknum = bb.getInt(); 12 | int slot = bb.getInt(); 13 | return new RecordKey(blknum, slot); 14 | } 15 | 16 | @Override 17 | public void write(LargeByteBuffer bb, RecordKey recordKey) { 18 | bb.putInt(recordKey.getBlockNumber()); 19 | bb.putInt(recordKey.getSlotNumber()); 20 | } 21 | 22 | @Override 23 | public int maxSize() { 24 | return Integer.BYTES + Integer.BYTES; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/basic/BTreeDir.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.BlockId; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.common.BTPage; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.common.DirEntry; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 8 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 9 | 10 | /** 11 | * A B-tree directory block. 12 | * 13 | * @author Edward Sciore 14 | */ 15 | public class BTreeDir { 16 | private Transaction tx; 17 | private TablePhysicalLayout recordValueLayout; 18 | private BTPage contents; 19 | private String filename; 20 | 21 | /** 22 | * Creates an object to hold the contents of the specified 23 | * B-tree block. 24 | * 25 | * @param blk a reference to the specified B-tree block 26 | * @param recordValueLayout the metadata of the B-tree directory file 27 | * @param tx the calling transaction 28 | */ 29 | public BTreeDir(Transaction tx, BlockId blk, TablePhysicalLayout recordValueLayout) { 30 | this.tx = tx; 31 | this.recordValueLayout = recordValueLayout; 32 | contents = new BTPage(tx, blk, recordValueLayout); 33 | filename = blk.getFileName(); 34 | } 35 | 36 | /** 37 | * Closes the directory page. 38 | */ 39 | public void close() { 40 | contents.close(); 41 | } 42 | 43 | /** 44 | * Returns the block number of the B-tree leaf block 45 | * that contains the specified search key. 46 | * 47 | * @param searchkey the search key value 48 | * @return the block number of the leaf block containing that search key 49 | */ 50 | public int search(D_Constant searchkey) { 51 | BlockId childblk = findChildBlock(searchkey); 52 | while (contents.getFlag() > 0) { 53 | contents.close(); 54 | contents = new BTPage(tx, childblk, recordValueLayout); 55 | childblk = findChildBlock(searchkey); 56 | } 57 | return childblk.getBlockNumber(); 58 | } 59 | 60 | /** 61 | * Creates a new root block for the B-tree. 62 | * The new root will have two children: 63 | * the old root, and the specified block. 64 | * Since the root must always be in block 0 of the file, 65 | * the contents of the old root will get transferred to a new block. 66 | * 67 | * @param e the directory entry to be added as a child of the new root 68 | */ 69 | public void makeNewRoot(DirEntry e) { 70 | D_Constant firstval = contents.getDataVal(0); 71 | int level = contents.getFlag(); 72 | BlockId newblk = contents.split(0, level); //ie, transfer all the records 73 | DirEntry oldroot = new DirEntry(firstval, newblk.getBlockNumber()); 74 | insertEntry(oldroot); 75 | insertEntry(e); 76 | contents.setFlag(level + 1); 77 | } 78 | 79 | /** 80 | * Inserts a new directory entry into the B-tree block. 81 | * If the block is at level 0, then the entry is inserted there. 82 | * Otherwise, the entry is inserted into the appropriate 83 | * child node, and the return value is examined. 84 | * A non-null return value indicates that the child node 85 | * split, and so the returned entry is inserted into 86 | * this block. 87 | * If this block splits, then the method similarly returns 88 | * the entry information of the new block to its caller; 89 | * otherwise, the method returns null. 90 | * 91 | * @param e the directory entry to be inserted 92 | * @return the directory entry of the newly-split block, if one exists; otherwise, null 93 | */ 94 | public DirEntry insert(DirEntry e) { 95 | if (contents.getFlag() == 0) return insertEntry(e); 96 | BlockId childblk = findChildBlock(e.dataVal()); 97 | BTreeDir child = new BTreeDir(tx, childblk, recordValueLayout); 98 | DirEntry myentry = child.insert(e); 99 | child.close(); 100 | return (myentry != null) ? insertEntry(myentry) : null; 101 | } 102 | 103 | private DirEntry insertEntry(DirEntry e) { 104 | int newslot = 1 + contents.findSlotBefore(e.dataVal()); 105 | contents.insertDir(newslot, e.dataVal(), e.blockNumber()); 106 | if (!contents.isFull()) return null; 107 | // else page is full, so split it 108 | int level = contents.getFlag(); 109 | int splitpos = contents.getNumRecs() / 2; 110 | D_Constant splitval = contents.getDataVal(splitpos); 111 | BlockId newblk = contents.split(splitpos, level); 112 | return new DirEntry(splitval, newblk.getBlockNumber()); 113 | } 114 | 115 | private BlockId findChildBlock(D_Constant searchkey) { 116 | int slot = contents.findSlotBefore(searchkey); 117 | if (contents.getDataVal(slot + 1).equals(searchkey)) slot++; 118 | int blknum = contents.getChildNum(slot); 119 | return new BlockId(filename, blknum); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/basic/BTreeLeaf.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.BlockId; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.common.BTPage; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.common.DirEntry; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 9 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 10 | 11 | /** 12 | * An object that holds the contents of a B-tree leaf block. 13 | * 14 | * @author Edward Sciore 15 | */ 16 | public class BTreeLeaf { 17 | private Transaction tx; 18 | private TablePhysicalLayout recordValueLayout; 19 | private D_Constant searchkey; 20 | private BTPage contents; 21 | private int currentslot; 22 | private String filename; 23 | 24 | /** 25 | * Opens a buffer to hold the specified leaf block. 26 | * The buffer is positioned immediately before the first record 27 | * having the specified search key (if any). 28 | * 29 | * @param blk a reference to the disk block 30 | * @param recordValueLayout the metadata of the B-tree leaf file 31 | * @param searchkey the search key value 32 | * @param tx the calling transaction 33 | */ 34 | public BTreeLeaf(Transaction tx, BlockId blk, TablePhysicalLayout recordValueLayout, D_Constant searchkey) { 35 | this.tx = tx; 36 | this.recordValueLayout = recordValueLayout; 37 | this.searchkey = searchkey; 38 | contents = new BTPage(tx, blk, recordValueLayout); 39 | currentslot = contents.findSlotBefore(searchkey); 40 | filename = blk.getFileName(); 41 | } 42 | 43 | /** 44 | * Closes the leaf page. 45 | */ 46 | public void close() { 47 | contents.close(); 48 | } 49 | 50 | /** 51 | * Moves to the next leaf record having the 52 | * previously-specified search key. 53 | * Returns false if there is no more such records. 54 | * 55 | * @return false if there are no more leaf records for the search key 56 | */ 57 | public boolean next() { 58 | currentslot++; 59 | if (currentslot >= contents.getNumRecs()) 60 | return tryOverflow(); 61 | else if (contents.getDataVal(currentslot).equals(searchkey)) 62 | return true; 63 | else 64 | return tryOverflow(); 65 | } 66 | 67 | /** 68 | * Returns the dataRID value of the current leaf record. 69 | * 70 | * @return the dataRID of the current record 71 | */ 72 | public RecordKey getDataRid() { 73 | return contents.getDataRid(currentslot); 74 | } 75 | 76 | /** 77 | * Deletes the leaf record having the specified dataRID 78 | * 79 | * @param value the dataRId whose record is to be deleted 80 | */ 81 | public void delete(RecordKey value) { 82 | while (next()) 83 | if (getDataRid().equals(value)) { 84 | contents.delete(currentslot); 85 | return; 86 | } 87 | } 88 | 89 | /** 90 | * Inserts a new leaf record having the specified dataRID 91 | * and the previously-specified search key. 92 | * 93 | * DONE: If the record does not fit in the page, then 94 | * the page splits and the method returns the 95 | * directory entry for the new page; 96 | * otherwise, the method returns null. 97 | * 98 | * DONE: If all of the records in the page have the same dataval, 99 | * then the block does not split; instead, all but one of the 100 | * records are placed into an overflow block. 101 | * 102 | * @param value the dataRID value of the new record 103 | * @return the directory entry of the newly-split page, if one exists. 104 | */ 105 | public DirEntry insert(RecordKey value) { 106 | if (contents.getFlag() >= 0 && contents.getDataVal(0).compareTo(searchkey) > 0) { 107 | D_Constant firstval = contents.getDataVal(0); 108 | BlockId newblk = contents.split(0, contents.getFlag()); 109 | currentslot = 0; 110 | contents.setFlag(-1); 111 | contents.insertLeaf(currentslot, searchkey, value); 112 | return new DirEntry(firstval, newblk.getBlockNumber()); 113 | } 114 | 115 | currentslot++; 116 | contents.insertLeaf(currentslot, searchkey, value); 117 | if (!contents.isFull()) 118 | return null; 119 | // else page is full, so split it 120 | D_Constant firstkey = contents.getDataVal(0); 121 | D_Constant lastkey = contents.getDataVal(contents.getNumRecs() - 1); 122 | if (lastkey.equals(firstkey)) { 123 | // create an overflow block to hold all but the first record 124 | BlockId newblk = contents.split(1, contents.getFlag()); 125 | contents.setFlag(newblk.getBlockNumber()); 126 | return null; 127 | } else { 128 | int splitpos = contents.getNumRecs() / 2; 129 | D_Constant splitkey = contents.getDataVal(splitpos); 130 | if (splitkey.equals(firstkey)) { 131 | // move right, looking for the next key 132 | while (contents.getDataVal(splitpos).equals(splitkey)) 133 | splitpos++; 134 | splitkey = contents.getDataVal(splitpos); 135 | } else { 136 | // move left, looking for first entry having that key 137 | while (contents.getDataVal(splitpos - 1).equals(splitkey)) 138 | splitpos--; 139 | } 140 | BlockId newblk = contents.split(splitpos, -1); 141 | return new DirEntry(splitkey, newblk.getBlockNumber()); 142 | } 143 | } 144 | 145 | private boolean tryOverflow() { 146 | D_Constant firstkey = contents.getDataVal(0); 147 | int flag = contents.getFlag(); 148 | if (!searchkey.equals(firstkey) || flag < 0) 149 | return false; 150 | contents.close(); 151 | BlockId nextblk = new BlockId(filename, flag); 152 | contents = new BTPage(tx, nextblk, recordValueLayout); 153 | currentslot = 0; 154 | return true; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/basic/BasicBPlusTreeIndex.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.BlockId; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.common.BTPage; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.common.DirEntry; 7 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 8 | import com.arjunsk.tiny_db.server.d_storage_engine.RWIndexScan; 9 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 10 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 11 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 12 | 13 | import static java.sql.Types.INTEGER; 14 | 15 | /** 16 | * A B-tree implementation of the Index interface. 17 | * 18 | * @author Edward Sciore 19 | */ 20 | public class BasicBPlusTreeIndex implements RWIndexScan { 21 | private Transaction tx; 22 | private TablePhysicalLayout dirRecordValueLayout, leafRecordValueLayout; 23 | private String leaftbl; 24 | private BTreeLeaf leaf = null; 25 | private BlockId rootblk; 26 | 27 | 28 | public BasicBPlusTreeIndex(Transaction tx, String idxname, TablePhysicalLayout leafRecordValueLayout) { 29 | this.tx = tx; 30 | // deal with the leaves 31 | leaftbl = idxname + "leaf"; 32 | this.leafRecordValueLayout = leafRecordValueLayout; 33 | if (tx.blockCount(leaftbl) == 0) { 34 | BlockId blk = tx.append(leaftbl); 35 | BTPage node = new BTPage(tx, blk, leafRecordValueLayout); 36 | node.format(blk, -1); 37 | } 38 | 39 | // deal with the directory 40 | TableDefinition dirsch = new TableDefinition(); 41 | dirsch.add("block", leafRecordValueLayout.schema()); 42 | dirsch.add("dataval", leafRecordValueLayout.schema()); 43 | String dirtbl = idxname + "dir"; 44 | dirRecordValueLayout = new TablePhysicalLayout(dirsch); 45 | rootblk = new BlockId(dirtbl, 0); 46 | if (tx.blockCount(dirtbl) == 0) { 47 | // create new root block 48 | tx.append(dirtbl); 49 | BTPage node = new BTPage(tx, rootblk, dirRecordValueLayout); 50 | node.format(rootblk, 0); 51 | // insert initial directory entry 52 | int fldtype = dirsch.type("dataval"); 53 | D_Constant minval = (fldtype == INTEGER) ? new D_Constant(Integer.MIN_VALUE) : new D_Constant(""); 54 | node.insertDir(0, minval, 0); 55 | node.close(); 56 | } 57 | } 58 | 59 | /** 60 | * Estimate the number of block accesses 61 | * required to find all index records having 62 | * a particular search key. 63 | * 64 | * @param numblocks the number of blocks in the B-tree directory 65 | * @param rpb the number of index entries per block 66 | * @return the estimated traversal cost 67 | */ 68 | public static int searchCost(int numblocks, int rpb) { 69 | return 1 + (int) (Math.log(numblocks) / Math.log(rpb)); 70 | } 71 | 72 | public void seek(D_Constant key) { 73 | close(); 74 | BTreeDir root = new BTreeDir(tx, rootblk, dirRecordValueLayout); 75 | int blknum = root.search(key); 76 | root.close(); 77 | BlockId leafblk = new BlockId(leaftbl, blknum); 78 | leaf = new BTreeLeaf(tx, leafblk, leafRecordValueLayout, key); 79 | } 80 | 81 | /** 82 | * Move to the next leaf record having the 83 | * previously-specified search key. 84 | * Returns false if there are no more such leaf records. 85 | * 86 | * @see RWIndexScan#hasNext() 87 | */ 88 | public boolean hasNext() { 89 | return leaf.next(); 90 | } 91 | 92 | /** 93 | * Return the dataRID value from the current leaf record. 94 | * 95 | * @see RWIndexScan#next() 96 | */ 97 | public RecordKey next() { 98 | return leaf.getDataRid(); 99 | } 100 | 101 | public void insert(D_Constant key, RecordKey value) { 102 | seek(key); 103 | DirEntry e = leaf.insert(value); 104 | leaf.close(); 105 | if (e == null) return; 106 | 107 | BTreeDir root = new BTreeDir(tx, rootblk, dirRecordValueLayout); 108 | DirEntry e2 = root.insert(e); 109 | if (e2 != null) root.makeNewRoot(e2); 110 | root.close(); 111 | } 112 | 113 | /** 114 | * Delete the specified index record. 115 | * The method first traverses the directory to find 116 | * the leaf page containing that record; then it 117 | * deletes the record from the page. 118 | */ 119 | public void delete(D_Constant key, RecordKey value) { 120 | seek(key); 121 | leaf.delete(value); 122 | leaf.close(); 123 | } 124 | 125 | /** 126 | * Close the index by closing its open leaf page, 127 | * if necessary. 128 | * 129 | * @see RWIndexScan#close() 130 | */ 131 | public void close() { 132 | if (leaf != null) leaf.close(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/basic/common/BTPage.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.common; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | import com.arjunsk.tiny_db.server.d_storage_engine.common.file.BlockId; 5 | import com.arjunsk.tiny_db.server.d_storage_engine.common.transaction.Transaction; 6 | import com.arjunsk.tiny_db.server.d_storage_engine.impl.data.heap.page.RecordKey; 7 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TablePhysicalLayout; 8 | import com.arjunsk.tiny_db.server.b_query_engine.common.catalog.table.TableDefinition; 9 | 10 | import static java.sql.Types.INTEGER; 11 | 12 | /** 13 | * B-tree directory and leaf pages have many commonalities: 14 | * in particular, their records are stored in sorted order, 15 | * and pages split when full. 16 | * A BTNode object contains this common functionality. 17 | * 18 | * @author Edward Sciore 19 | */ 20 | public class BTPage { 21 | private Transaction tx; 22 | private BlockId currentblk; 23 | private TablePhysicalLayout recordValueLayout; 24 | 25 | /** 26 | * Open a node for the specified B-tree block. 27 | * 28 | * @param currentblk a reference to the B-tree block 29 | * @param recordValueLayout the metadata for the particular B-tree file 30 | * @param tx the calling transaction 31 | */ 32 | public BTPage(Transaction tx, BlockId currentblk, TablePhysicalLayout recordValueLayout) { 33 | this.tx = tx; 34 | this.currentblk = currentblk; 35 | this.recordValueLayout = recordValueLayout; 36 | tx.pin(currentblk); 37 | } 38 | 39 | /** 40 | * Calculate the position where the first record having 41 | * the specified search key should be, then returns 42 | * the position before it. 43 | * 44 | * @param searchkey the search key 45 | * @return the position before where the search key goes 46 | */ 47 | public int findSlotBefore(D_Constant searchkey) { 48 | int slot = 0; 49 | while (slot < getNumRecs() && getDataVal(slot).compareTo(searchkey) < 0) 50 | slot++; 51 | return slot - 1; 52 | } 53 | 54 | /** 55 | * Close the page by unpinning its buffer. 56 | */ 57 | public void close() { 58 | if (currentblk != null) 59 | tx.unpin(currentblk); 60 | currentblk = null; 61 | } 62 | 63 | /** 64 | * Return true if the block is full. 65 | * 66 | * @return true if the block is full 67 | */ 68 | public boolean isFull() { 69 | return slotpos(getNumRecs() + 1) >= tx.blockSize(); 70 | } 71 | 72 | /** 73 | * Split the page at the specified position. 74 | * A new page is created, and the records of the page 75 | * starting at the split position are transferred to the new page. 76 | * 77 | * @param splitpos the split position 78 | * @param flag the initial value of the flag field 79 | * @return the reference to the new block 80 | */ 81 | public BlockId split(int splitpos, int flag) { 82 | BlockId newblk = appendNew(flag); 83 | BTPage newpage = new BTPage(tx, newblk, recordValueLayout); 84 | transferRecs(splitpos, newpage); 85 | newpage.setFlag(flag); 86 | newpage.close(); 87 | return newblk; 88 | } 89 | 90 | /** 91 | * Return the dataval of the record at the specified slot. 92 | * 93 | * @param slot the integer slot of an index record 94 | * @return the dataval of the record at that slot 95 | */ 96 | public D_Constant getDataVal(int slot) { 97 | return getVal(slot, "dataval"); 98 | } 99 | 100 | /** 101 | * Return the value of the page's flag field 102 | * 103 | * @return the value of the page's flag field 104 | */ 105 | public int getFlag() { 106 | return tx.getInt(currentblk, 0); 107 | } 108 | 109 | /** 110 | * Set the page's flag field to the specified value 111 | * 112 | * @param val the new value of the page flag 113 | */ 114 | public void setFlag(int val) { 115 | tx.setInt(currentblk, 0, val); 116 | } 117 | 118 | /** 119 | * Append a new block to the end of the specified B-tree file, 120 | * having the specified flag value. 121 | * 122 | * @param flag the initial value of the flag 123 | * @return a reference to the newly-created block 124 | */ 125 | public BlockId appendNew(int flag) { 126 | BlockId blk = tx.append(currentblk.getFileName()); 127 | tx.pin(blk); 128 | format(blk, flag); 129 | return blk; 130 | } 131 | 132 | public void format(BlockId blk, int flag) { 133 | tx.setInt(blk, 0, flag); 134 | tx.setInt(blk, Integer.BYTES, 0); // #records = 0 135 | int recsize = recordValueLayout.slotSize(); 136 | for (int pos = 2 * Integer.BYTES; pos + recsize <= tx.blockSize(); pos += recsize) 137 | makeDefaultRecord(blk, pos); 138 | } 139 | 140 | private void makeDefaultRecord(BlockId blk, int pos) { 141 | for (String fldname : recordValueLayout.schema().fields()) { 142 | int offset = recordValueLayout.offset(fldname); 143 | if (recordValueLayout.schema().type(fldname) == INTEGER) 144 | tx.setInt(blk, pos + offset, 0); 145 | else 146 | tx.setString(blk, pos + offset, ""); 147 | } 148 | } 149 | // Methods called only by BTreeDir 150 | 151 | /** 152 | * Return the block number stored in the index record 153 | * at the specified slot. 154 | * 155 | * @param slot the slot of an index record 156 | * @return the block number stored in that record 157 | */ 158 | public int getChildNum(int slot) { 159 | return getInt(slot, "block"); 160 | } 161 | 162 | /** 163 | * Insert a directory entry at the specified slot. 164 | * 165 | * @param slot the slot of an index record 166 | * @param val the dataval to be stored 167 | * @param blknum the block number to be stored 168 | */ 169 | public void insertDir(int slot, D_Constant val, int blknum) { 170 | insert(slot); 171 | setVal(slot, "dataval", val); 172 | setInt(slot, "block", blknum); 173 | } 174 | 175 | // Methods called only by BTreeLeaf 176 | 177 | /** 178 | * Return the dataRID value stored in the specified leaf index record. 179 | * 180 | * @param slot the slot of the desired index record 181 | * @return the dataRID value store at that slot 182 | */ 183 | public RecordKey getDataRid(int slot) { 184 | return new RecordKey(getInt(slot, "block"), getInt(slot, "id")); 185 | } 186 | 187 | /** 188 | * Insert a leaf index record at the specified slot. 189 | * 190 | * @param slot the slot of the desired index record 191 | * @param val the new dataval 192 | * @param recordKey the new dataRID 193 | */ 194 | public void insertLeaf(int slot, D_Constant val, RecordKey recordKey) { 195 | insert(slot); 196 | setVal(slot, "dataval", val); 197 | setInt(slot, "block", recordKey.getBlockNumber()); 198 | setInt(slot, "id", recordKey.getSlotNumber()); 199 | } 200 | 201 | /** 202 | * Delete the index record at the specified slot. 203 | * 204 | * @param slot the slot of the deleted index record 205 | */ 206 | public void delete(int slot) { 207 | for (int i = slot + 1; i < getNumRecs(); i++) 208 | copyRecord(i, i - 1); 209 | setNumRecs(getNumRecs() - 1); 210 | return; 211 | } 212 | 213 | /** 214 | * Return the number of index records in this page. 215 | * 216 | * @return the number of index records in this page 217 | */ 218 | public int getNumRecs() { 219 | return tx.getInt(currentblk, Integer.BYTES); 220 | } 221 | 222 | // Private methods 223 | 224 | private void setNumRecs(int n) { 225 | tx.setInt(currentblk, Integer.BYTES, n); 226 | } 227 | 228 | private int getInt(int slot, String fldname) { 229 | int pos = fldpos(slot, fldname); 230 | return tx.getInt(currentblk, pos); 231 | } 232 | 233 | private String getString(int slot, String fldname) { 234 | int pos = fldpos(slot, fldname); 235 | return tx.getString(currentblk, pos); 236 | } 237 | 238 | private D_Constant getVal(int slot, String fldname) { 239 | int type = recordValueLayout.schema().type(fldname); 240 | if (type == INTEGER) 241 | return new D_Constant(getInt(slot, fldname)); 242 | else 243 | return new D_Constant(getString(slot, fldname)); 244 | } 245 | 246 | private void setInt(int slot, String fldname, int val) { 247 | int pos = fldpos(slot, fldname); 248 | tx.setInt(currentblk, pos, val); 249 | } 250 | 251 | private void setString(int slot, String fldname, String val) { 252 | int pos = fldpos(slot, fldname); 253 | tx.setString(currentblk, pos, val); 254 | } 255 | 256 | private void setVal(int slot, String fldname, D_Constant val) { 257 | int type = recordValueLayout.schema().type(fldname); 258 | if (type == INTEGER) 259 | setInt(slot, fldname, val.asInt()); 260 | else 261 | setString(slot, fldname, val.asString()); 262 | } 263 | 264 | private void insert(int slot) { 265 | for (int i = getNumRecs(); i > slot; i--) 266 | copyRecord(i - 1, i); 267 | setNumRecs(getNumRecs() + 1); 268 | } 269 | 270 | private void copyRecord(int from, int to) { 271 | TableDefinition sch = recordValueLayout.schema(); 272 | for (String fldname : sch.fields()) 273 | setVal(to, fldname, getVal(from, fldname)); 274 | } 275 | 276 | private void transferRecs(int slot, BTPage dest) { 277 | int destslot = 0; 278 | while (slot < getNumRecs()) { 279 | dest.insert(destslot); 280 | TableDefinition sch = recordValueLayout.schema(); 281 | for (String fldname : sch.fields()) 282 | dest.setVal(destslot, fldname, getVal(slot, fldname)); 283 | delete(slot); 284 | destslot++; 285 | } 286 | } 287 | 288 | private int fldpos(int slot, String fldname) { 289 | int offset = recordValueLayout.offset(fldname); 290 | return slotpos(slot) + offset; 291 | } 292 | 293 | private int slotpos(int slot) { 294 | int slotsize = recordValueLayout.slotSize(); 295 | return Integer.BYTES + Integer.BYTES + (slot * slotsize); 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/bplustree/basic/common/DirEntry.java: -------------------------------------------------------------------------------- 1 | package com.arjunsk.tiny_db.server.d_storage_engine.impl.index.bplustree.basic.common; 2 | 3 | import com.arjunsk.tiny_db.server.a_frontend.common.domain.clause.D_Constant; 4 | 5 | /** 6 | * A directory entry has two components: the number of the child block, 7 | * and the dataval of the first record in that block. 8 | * 9 | * @author Edward Sciore 10 | */ 11 | public class DirEntry { 12 | private D_Constant dataval; 13 | private int blocknum; 14 | 15 | /** 16 | * Creates a new entry for the specified dataval and block number. 17 | * 18 | * @param dataval the dataval 19 | * @param blocknum the block number 20 | */ 21 | public DirEntry(D_Constant dataval, int blocknum) { 22 | this.dataval = dataval; 23 | this.blocknum = blocknum; 24 | } 25 | 26 | /** 27 | * Returns the dataval component of the entry 28 | * 29 | * @return the dataval component of the entry 30 | */ 31 | public D_Constant dataVal() { 32 | return dataval; 33 | } 34 | 35 | /** 36 | * Returns the block number component of the entry 37 | * 38 | * @return the block number component of the entry 39 | */ 40 | public int blockNumber() { 41 | return blocknum; 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/hash/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dborchard/tiny-db/816330ef0d7e3a1f56a079de1a9950ea082b76a6/src/main/java/com/arjunsk/tiny_db/server/d_storage_engine/impl/index/hash/.gitkeep --------------------------------------------------------------------------------