diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..cd7f0fd Binary files /dev/null and b/.DS_Store differ diff --git a/.history/README_20221223212951.md b/.history/README_20221223212951.md new file mode 100644 index 0000000..7d62d22 --- /dev/null +++ b/.history/README_20221223212951.md @@ -0,0 +1,81 @@ +[![Open in Visual Studio Code](https://classroom.github.com/assets/open-in-vscode-c66648af7eb3fe8bc4f294546bfd86ef473780cde1dea487d3c4ff354943c9ae.svg)](https://classroom.github.com/online_ide?assignment_repo_id=9434865&assignment_repo_type=AssignmentRepo) + +# CSC3170 Course Project + +## Project Overall Description + +This is our implementation for the course project of CSC3170, 2022 Fall, CUHK(SZ). For details of the project, you can refer to [project-description.md](project-description.md). In this project, we will utilize what we learned in the lectures and tutorials in the course, and implement either one of the following major jobs: + + + +- [ ] **Application with Database System(s)** +- [X] **Implementation of a Database System** + +## Team Members + +Our team consists of the following members, listed in the table below (the team leader is shown in the first row, and is marked with 🚩 behind his/her name): + + + +| Student ID | Student Name | GitHub Account (in Email) | GitHub Username | Actual Contribution | +| ---------- | ------------ | -------------------------- | ---------- | ------------ | +| 120090609 | 刘恒睿 🚩 | 120090609@link.cuhk.edu.cn | [RichardRui9](https://github.com/RichardRui9) |*fill in the blank* | +| 120090860 | 李楠轩 | 120090860@link.cuhk.edu.cn | [George-Mac](https://github.com/George-Mac) |*fill in the blank* | +| 120090495 | 张家荣 | 120090495@link.cuhk.edu.cn | [Yae-mikooo](https://github.com/Yae-mikooo) | *fill in the blank*| +| 119010269 | 宋泽方 | 119010269@link.cuhk.edu.cn | [Song199010](https://github.com/Song199010) | *fill in the blank*| +| 120090565 | 胡文涵 | 120090565@link.cuhk.edu.cn | [Clarice927](https://github.com/Clarice927) | *fill in the blank*| +| 119010216 | 路东竹渊 | 119010216@link.cuhk.edu.cn | [Tim-Lu-cuhksz](https://github.com/Tim-Lu-cuhksz) |*fill in the blank*| +| 120090771 | 邱纬纶 | 120090771@link.cuhk.edu.cn | [alanqwl](https://github.com/alanqwl) | *fill in the blank*| +| 120090224 | 杨尚霖 | 120090224@link.cuhk.edu.cn | [UnitedSnakes](https://github.com/CSC3170-2022Fall/project-microhard/commits?author=UnitedSnakes) | *fill in the blank*| +| 120090470 | 李鹏 | 120090470@link.cuhk.edu.cn | [pengleee](https://github.com/pengleee) | *fill in the blank*| + +## Project Specification + + + +After thorough discussion, our team made the choice and the specification information is listed below: + +- Our option choice is: **Option 3** + +## Project Abstract + + + +Abstract – Microhard + +This CSC3170 project, based on a design from UCB CS61B, Fall 2014, requires our famous group Microhard to include a very simple query language to write a miniature relational database management system (DBMS). + +The prototype Java codes provided by UCB have initiated a skeleton for DBMS and the first step for Microhard is to fill in the missing Java codes in five prototype Java files. As of December 05, Microhard has finished this job and successfully implemented the miniature DBMS. This DBMS reads query language tokens from terminal input and then invokes appropriate Java functions to perform specific operations. + +Microhard then decides to add to the query language more syntaxes like "group by" (for more details please refer to Todo.md) and optimize the speed and efficiency of the DBMS. These updates will be released soon. + +Please do not hesitate to contact us if we can be of assistance. + +Microhard +December 05, 2022 + +## Program Design +The project mainly consists of 7 Java files: `CommandInterpreter.java`, `Tokenizer.java`, `Database.java`, `Table.java`, `Row.java`, `Condition.java` and `Column.java`. + +`CommandInterpreter.java` uses `Tokenizer.java` to parse user commands, and passes appropriate tests. `Tokenizer.java` interacts with database or creates conditions. `Database.java` is the container for tables. `Table.java` is the brains of the project. It writes and reads from files, and also performs db operations when given commands by `CommandIntepreter.java`. `Condition.java` represents a where condition command in a select clause. `Row.java` represents a single row of data. `Column.java` acts as an index of a column in a list of rows. Besides, there are `Utils.java`, `DBException.java`, `Main.java` in the project. + +In *testing* folder, there are database files `blank.db`, `enrolled.db`, `schedule.db` and `students.db`. Tester Python files and sample input/output are also included. + +## Functionality Implementation +### Basic Functions +1. Load table from .db: It uses the `readTable()` method in `Table.java` to read the contents of db file and return as a table. +2. Omit comments in expressions +3. Basic *select from ... where* operation +4. Create table from select clause +5. Join two tables +### Enhanced Functions +1. Case-Insensitive to User’s Input +2. Multiple-Relations Joint +3. Select All: Users can use `select *` to see all the data from the table. A `for` loop is used to get all the column names of the table. Then, normal `select` by the total names is executed. +4. Condition “OR” +5. Results “Order by” +6. Multi-Load + +Please visit documentation [here](TestCases.md) for more test details. +## Difficulty Encountered & Solutions +*fill in the blank fill in the blank fill in the blank fill in the blank* diff --git a/.history/README_20221223214319.md b/.history/README_20221223214319.md new file mode 100644 index 0000000..2161730 --- /dev/null +++ b/.history/README_20221223214319.md @@ -0,0 +1,81 @@ +[![Open in Visual Studio Code](https://classroom.github.com/assets/open-in-vscode-c66648af7eb3fe8bc4f294546bfd86ef473780cde1dea487d3c4ff354943c9ae.svg)](https://classroom.github.com/online_ide?assignment_repo_id=9434865&assignment_repo_type=AssignmentRepo) + +# CSC3170 Course Project + +## Project Overall Description + +This is our implementation for the course project of CSC3170, 2022 Fall, CUHK(SZ). For details of the project, you can refer to [project-description.md](project-description.md). In this project, we will utilize what we learned in the lectures and tutorials in the course, and implement either one of the following major jobs: + + + +- [ ] **Application with Database System(s)** +- [X] **Implementation of a Database System** + +## Team Members + +Our team consists of the following members, listed in the table below (the team leader is shown in the first row, and is marked with 🚩 behind his/her name): + + + +| Student ID | Student Name | GitHub Account (in Email) | GitHub Username | Actual Contribution | +| ---------- | ------------ | -------------------------- | ---------- | ------------ | +| 120090609 | 刘恒睿 🚩 | 120090609@link.cuhk.edu.cn | [RichardRui9](https://github.com/RichardRui9) |*fill in the blank* | +| 120090860 | 李楠轩 | 120090860@link.cuhk.edu.cn | [George-Mac](https://github.com/George-Mac) |*fill in the blank* | +| 120090495 | 张家荣 | 120090495@link.cuhk.edu.cn | [Yae-mikooo](https://github.com/Yae-mikooo) | *fill in the blank*| +| 119010269 | 宋泽方 | 119010269@link.cuhk.edu.cn | [Song199010](https://github.com/Song199010) | *fill in the blank*| +| 120090565 | 胡文涵 | 120090565@link.cuhk.edu.cn | [Clarice927](https://github.com/Clarice927) | *fill in the blank*| +| 119010216 | 路东竹渊 | 119010216@link.cuhk.edu.cn | [Tim-Lu-cuhksz](https://github.com/Tim-Lu-cuhksz) |*fill in the blank*| +| 120090771 | 邱纬纶 | 120090771@link.cuhk.edu.cn | [alanqwl](https://github.com/alanqwl) | *fill in the blank*| +| 120090224 | 杨尚霖 | 120090224@link.cuhk.edu.cn | [UnitedSnakes](https://github.com/CSC3170-2022Fall/project-microhard/commits?author=UnitedSnakes) | *fill in the blank*| +| 120090470 | 李鹏 | 120090470@link.cuhk.edu.cn | [pengleee](https://github.com/pengleee) | *fill in the blank*| + +## Project Specification + + + +After thorough discussion, our team made the choice and the specification information is listed below: + +- Our option choice is: **Option 3** + +## Project Abstract + + + +Abstract – Microhard + +This CSC3170 project, based on a design from UCB CS61B, Fall 2014, requires our famous group Microhard to include a very simple query language to write a miniature relational database management system (DBMS). + +The prototype Java codes provided by UCB have initiated a skeleton for DBMS and the first step for Microhard is to fill in the missing Java codes in five prototype Java files. As of December 05, Microhard has finished this job and successfully implemented the miniature DBMS. This DBMS reads query language tokens from terminal input and then invokes appropriate Java functions to perform specific operations. + +Microhard then decides to add to the query language more syntaxes like "group by" (for more details please refer to Todo.md) and optimize the speed and efficiency of the DBMS. These updates will be released soon. + +Please do not hesitate to contact us if we can be of assistance. + +Microhard +December 05, 2022 + +## Program Design +The project mainly consists of 7 Java files: `CommandInterpreter.java`, `Tokenizer.java`, `Database.java`, `Table.java`, `Row.java`, `Condition.java` and `Column.java`. + +`CommandInterpreter.java` uses `Tokenizer.java` to parse user commands, and passes appropriate tests. `Tokenizer.java` interacts with database or creates conditions. `Database.java` is the container for tables. `Table.java` is the brains of the project. It writes and reads from files, and also performs db operations when given commands by `CommandIntepreter.java`. `Condition.java` represents a where condition command in a select clause. `Row.java` represents a single row of data. `Column.java` acts as an index of a column in a list of rows. Besides, there are `Utils.java`, `DBException.java`, `Main.java` in the project. + +In *testing* folder, there are database files `blank.db`, `enrolled.db`, `schedule.db` and `students.db`. Tester Python files and sample input/output are also included. + +## Functionality Implementation +### Basic Functions +1. Load table from .db: It uses the `readTable()` method in `Table.java` to read the contents of db file and return as a table. +2. Omit comments in expressions +3. Basic *select from ... where* operation +4. Create table from select clause +5. Join two tables +### Enhanced Functions +1. Case-Insensitive to User’s Input +2. Multiple-Relations Joint +3. Select All: Users can use `select *` to see all the data from the table. A `for` loop is used to get all the column names of the table. Then, normal `select` by the total names is executed. +4. Condition “OR” +5. Results “Order by”: Users can reorder the result table in ascending/descending order based on the value(s) of one or more columns, in which `asc` or none for 'ascending' and `desc` for 'descending'. More details can be found in `selectStatement` file, `CommandInterpreter.java` file. +6. Multi-Load + +Please visit documentation [here](TestCases.md) for more test details. +## Difficulty Encountered & Solutions +*fill in the blank fill in the blank fill in the blank fill in the blank* diff --git a/.history/README_20221223214436.md b/.history/README_20221223214436.md new file mode 100644 index 0000000..59e3a80 --- /dev/null +++ b/.history/README_20221223214436.md @@ -0,0 +1,81 @@ +[![Open in Visual Studio Code](https://classroom.github.com/assets/open-in-vscode-c66648af7eb3fe8bc4f294546bfd86ef473780cde1dea487d3c4ff354943c9ae.svg)](https://classroom.github.com/online_ide?assignment_repo_id=9434865&assignment_repo_type=AssignmentRepo) + +# CSC3170 Course Project + +## Project Overall Description + +This is our implementation for the course project of CSC3170, 2022 Fall, CUHK(SZ). For details of the project, you can refer to [project-description.md](project-description.md). In this project, we will utilize what we learned in the lectures and tutorials in the course, and implement either one of the following major jobs: + + + +- [ ] **Application with Database System(s)** +- [X] **Implementation of a Database System** + +## Team Members + +Our team consists of the following members, listed in the table below (the team leader is shown in the first row, and is marked with 🚩 behind his/her name): + + + +| Student ID | Student Name | GitHub Account (in Email) | GitHub Username | Actual Contribution | +| ---------- | ------------ | -------------------------- | ---------- | ------------ | +| 120090609 | 刘恒睿 🚩 | 120090609@link.cuhk.edu.cn | [RichardRui9](https://github.com/RichardRui9) |*fill in the blank* | +| 120090860 | 李楠轩 | 120090860@link.cuhk.edu.cn | [George-Mac](https://github.com/George-Mac) |*fill in the blank* | +| 120090495 | 张家荣 | 120090495@link.cuhk.edu.cn | [Yae-mikooo](https://github.com/Yae-mikooo) | *fill in the blank*| +| 119010269 | 宋泽方 | 119010269@link.cuhk.edu.cn | [Song199010](https://github.com/Song199010) | *fill in the blank*| +| 120090565 | 胡文涵 | 120090565@link.cuhk.edu.cn | [Clarice927](https://github.com/Clarice927) | *fill in the blank*| +| 119010216 | 路东竹渊 | 119010216@link.cuhk.edu.cn | [Tim-Lu-cuhksz](https://github.com/Tim-Lu-cuhksz) |*fill in the blank*| +| 120090771 | 邱纬纶 | 120090771@link.cuhk.edu.cn | [alanqwl](https://github.com/alanqwl) | *fill in the blank*| +| 120090224 | 杨尚霖 | 120090224@link.cuhk.edu.cn | [UnitedSnakes](https://github.com/CSC3170-2022Fall/project-microhard/commits?author=UnitedSnakes) | *fill in the blank*| +| 120090470 | 李鹏 | 120090470@link.cuhk.edu.cn | [pengleee](https://github.com/pengleee) | *fill in the blank*| + +## Project Specification + + + +After thorough discussion, our team made the choice and the specification information is listed below: + +- Our option choice is: **Option 3** + +## Project Abstract + + + +Abstract – Microhard + +This CSC3170 project, based on a design from UCB CS61B, Fall 2014, requires our famous group Microhard to include a very simple query language to write a miniature relational database management system (DBMS). + +The prototype Java codes provided by UCB have initiated a skeleton for DBMS and the first step for Microhard is to fill in the missing Java codes in five prototype Java files. As of December 05, Microhard has finished this job and successfully implemented the miniature DBMS. This DBMS reads query language tokens from terminal input and then invokes appropriate Java functions to perform specific operations. + +Microhard then decides to add to the query language more syntaxes like "group by" (for more details please refer to Todo.md) and optimize the speed and efficiency of the DBMS. These updates will be released soon. + +Please do not hesitate to contact us if we can be of assistance. + +Microhard +December 05, 2022 + +## Program Design +The project mainly consists of 7 Java files: `CommandInterpreter.java`, `Tokenizer.java`, `Database.java`, `Table.java`, `Row.java`, `Condition.java` and `Column.java`. + +`CommandInterpreter.java` uses `Tokenizer.java` to parse user commands, and passes appropriate tests. `Tokenizer.java` interacts with database or creates conditions. `Database.java` is the container for tables. `Table.java` is the brains of the project. It writes and reads from files, and also performs db operations when given commands by `CommandIntepreter.java`. `Condition.java` represents a where condition command in a select clause. `Row.java` represents a single row of data. `Column.java` acts as an index of a column in a list of rows. Besides, there are `Utils.java`, `DBException.java`, `Main.java` in the project. + +In *testing* folder, there are database files `blank.db`, `enrolled.db`, `schedule.db` and `students.db`. Tester Python files and sample input/output are also included. + +## Functionality Implementation +### Basic Functions +1. Load table from .db: It uses the `readTable()` method in `Table.java` to read the contents of db file and return as a table. +2. Omit comments in expressions +3. Basic *select from ... where* operation +4. Create table from select clause +5. Join two tables +### Enhanced Functions +1. Case-Insensitive to User’s Input +2. Multiple-Relations Joint +3. Select All: Users can use `select *` to see all the data from the table. A `for` loop is used to get all the column names of the table. Then, normal `select` by the total names is executed. +4. Condition “OR” +5. Results “Order by”: Users can reorder the result table in ascending/descending order based on the value(s) of one or more columns, in which `asc` or none for 'ascending' and `desc` for 'descending'. More details can be found in `selectStatement` method, `CommandInterpreter.java` file. +6. Multi-Load + +Please visit documentation [here](TestCases.md) for more test details. +## Difficulty Encountered & Solutions +*fill in the blank fill in the blank fill in the blank fill in the blank* diff --git a/.history/Todo_20221205231106.md b/.history/Todo_20221205231106.md new file mode 100644 index 0000000..23a7a71 --- /dev/null +++ b/.history/Todo_20221205231106.md @@ -0,0 +1,15 @@ +# Extra functions need to do + +## Realized Functions + +- [x] **Ignored case sensitivity of commands** +- [x] **Realized *SELECT* Clause** +- [x] **Realized *FROM* Clause** +- [x] **Realized *WHERE* Clause** + +## Functions in Process + +- [ ] **Realizing *SELECT \** (select all) command** +- [ ] **Realizing *GROUP BY* Clause** +- [ ] **Realizing *ORDER BY* Clause** +- [ ] **Optimizing the error processing** \ No newline at end of file diff --git a/.history/Todo_20221205231142.md b/.history/Todo_20221205231142.md new file mode 100644 index 0000000..f0f3560 --- /dev/null +++ b/.history/Todo_20221205231142.md @@ -0,0 +1,15 @@ +# Functions need to do + +## Realized Functions + +- [x] **Ignored case sensitivity of commands** +- [x] **Realized *SELECT* Clause** +- [x] **Realized *FROM* Clause** +- [x] **Realized *WHERE* Clause** + +## Functions in Process + +- [ ] **Realizing *SELECT \** (select all) command** +- [ ] **Realizing *GROUP BY* Clause** +- [ ] **Realizing *ORDER BY* Clause** +- [ ] **Optimizing the error processing** \ No newline at end of file diff --git a/.history/Todo_20221205232650.md b/.history/Todo_20221205232650.md new file mode 100644 index 0000000..ab35f8e --- /dev/null +++ b/.history/Todo_20221205232650.md @@ -0,0 +1,16 @@ +# Functions need to do + +## Realized Functions + +- [x] **Ignored case sensitivity of commands** +- [x] **Realized *SELECT* Clause** +- [x] **Realized *FROM* Clause** +- [x] **Realized *WHERE* Clause** + +## Functions in Process + +- [ ] **Realizing *SELECT \** (select all) command** +- [ ] **Realizing *GROUP BY* Clause** +- [ ] **Realizing *ORDER BY* Clause** +- [ ] **Optimizing the error processing** +- [ ] **Optimizing the speed/efficiency of data handling** \ No newline at end of file diff --git a/.history/Todo_20221208153028.md b/.history/Todo_20221208153028.md new file mode 100644 index 0000000..98925e7 --- /dev/null +++ b/.history/Todo_20221208153028.md @@ -0,0 +1,18 @@ +# Planned Functions + +## Realized Functions + +- [x] **Ignored case sensitivity of commands** +- [x] **Realized *SELECT* Clause** +- [x] **Realized *FROM* Clause** +- [x] **Realized *WHERE* Clause** +- [x] **Realized *FROM* Clause for more than two tables** +- [X] **Realizing *SELECT \** (select all) command** + +## Functions in Process + +- [ ] **Realizing *GROUP BY* Clause** +- [ ] **Realizing *ORDER BY* Clause** +- [ ] **Optimizing the error processing** +- [ ] **Optimizing the speed/efficiency of data handling** +- [ ] **Realizing *Aggregate Function* including *avg*, *min*, *max*, *count*, etc** \ No newline at end of file diff --git a/.history/proj1/db61b/CommandInterpreter_20221210102529.java b/.history/proj1/db61b/CommandInterpreter_20221210102529.java new file mode 100644 index 0000000..bfb3a2e --- /dev/null +++ b/.history/proj1/db61b/CommandInterpreter_20221210102529.java @@ -0,0 +1,449 @@ +// This is a SUGGESTED skeleton for a class that parses and executes database +// statements. Be sure to read the STRATEGY section, and ask us if you have any +// questions about it. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution adds or changes about 50 +// lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Scanner; + +import static db61b.Utils.*; +import static db61b.Tokenizer.*; + +/** An object that reads and interprets a sequence of commands from an + * input source. + * @author */ +class CommandInterpreter { + + /* STRATEGY. + * + * This interpreter parses commands using a technique called + * "recursive descent." The idea is simple: we convert the BNF grammar, + * as given in the specification document, into a program. + * + * First, we break up the input into "tokens": strings that correspond + * to the "base case" symbols used in the BNF grammar. These are + * keywords, such as "select" or "create"; punctuation and relation + * symbols such as ";", ",", ">="; and other names (of columns or tables). + * All whitespace and comments get discarded in this process, so that the + * rest of the program can deal just with things mentioned in the BNF. + * The class Tokenizer performs this breaking-up task, known as + * "tokenizing" or "lexical analysis." + * + * The rest of the parser consists of a set of functions that call each + * other (possibly recursively, although that isn't needed for this + * particular grammar) to operate on the sequence of tokens, one function + * for each BNF rule. Consider a rule such as + * + * ::= create table
; + * + * We can treat this as a definition for a function named (say) + * createStatement. The purpose of this function is to consume the + * tokens for one create statement from the remaining token sequence, + * to perform the required actions, and to return the resulting value, + * if any (a create statement has no value, just side-effects, but a + * select clause is supposed to produce a table, according to the spec.) + * + * The body of createStatement is dictated by the right-hand side of the + * rule. For each token (like create), we check that the next item in + * the token stream is "create" (and report an error otherwise), and then + * advance to the next token. For a metavariable, like
, + * we consume the tokens for
, and do whatever is + * appropriate with the resulting value. We do so by calling the + * tableDefinition function, which is constructed (as is createStatement) + * to do exactly this. + * + * Thus, the body of createStatement would look like this (_input is + * the sequence of tokens): + * + * _input.next("create"); + * _input.next("table"); + * String name = name(); + * Table table = tableDefinition(); + * _input.next(";"); + * + * plus other code that operates on name and table to perform the function + * of the create statement. The .next method of Tokenizer is set up to + * throw an exception (DBException) if the next token does not match its + * argument. Thus, any syntax error will cause an exception, which your + * program can catch to do error reporting. + * + * This leaves the issue of what to do with rules that have alternatives + * (the "|" symbol in the BNF grammar). Fortunately, our grammar has + * been written with this problem in mind. When there are multiple + * alternatives, you can always tell which to pick based on the next + * unconsumed token. For example,
has two alternative + * right-hand sides, one of which starts with "(", and one with "as". + * So all you have to do is test: + * + * if (_input.nextIs("(")) { + * _input.next("("); + * // code to process ", )" + * } else { + * // code to process "as " + * } + * + * combining the calls to .nextIs and .next. + * + * You can handle the list of s in the preceding in a number + * of ways, but personally, I suggest a simple loop: + * + * ... = columnName(); + * while (_input.nextIs(",")) { + * _input.next(","); + * ... = columnName(); + * } + * + * or if you prefer even greater concision: + * + * ... = columnName(); + * while (_input.nextIf(",")) { + * ... = columnName(); + * } + * + * (You'll have to figure out what do with the names you accumulate, of + * course). + */ + + + /** A new CommandInterpreter executing commands read from INP, writing + * prompts on PROMPTER, if it is non-null. */ + + CommandInterpreter(Scanner inp, PrintStream prompter) { + _input = new Tokenizer(inp, prompter); + _database = new Database(); + } + + /** Parse and execute one statement from the token stream. Return true + * iff the command is something other than quit or exit. */ + boolean statement() { + switch (_input.peek().toLowerCase()) { + case "create": + createStatement(); + break; + case "load": + loadStatement(); + break; + case "exit": case "quit": + exitStatement(); + return false; + case "*EOF*": + return false; + case "insert": + insertStatement(); + break; + case "print": + printStatement(); + break; + case "select": + selectStatement(); + break; + case "store": + storeStatement(); + break; + default: + throw error("unrecognizable command"); + } + return true; + } + + /** Parse and execute a create statement from the token stream. */ + void createStatement() { + _input.next("create"); + _input.next("table"); + String name = name(); + Table table = tableDefinition(); + _input.next(";"); + _database.put(name, table); + + } + + /** Parse and execute an exit or quit statement. Actually does nothing + * except check syntax, since statement() handles the actual exiting. */ + void exitStatement() { + if (!_input.nextIf("quit")) { + _input.next("exit"); + } + _input.next(";"); + } + + /** Parse and execute an insert statement from the token stream. */ + void insertStatement() { + _input.next("insert"); + _input.next("into"); + Table table = tableName(); + _input.next("values"); + + ArrayList values = new ArrayList<>(); + values.add(literal()); + while (_input.nextIf(",")) { + values.add(literal()); + } + + table.add(new Row(values.toArray(new String[values.size()]))); + _input.next(";"); + } + + /** Parse and execute a load statement from the token stream. */ + void loadStatement() { + _input.next("load"); + String name = name(); + Table content = Table.readTable(name); + _database.put(name, content); + _input.next(";"); + System.out.printf("Loaded %s.db%n", name); + } + + /** Parse and execute a store statement from the token stream. */ + void storeStatement() { + _input.next("store"); + String name = _input.peek(); + Table table = tableName(); + table.writeTable(name); + System.out.printf("Stored %s.db%n", name); + _input.next(";"); + } + + /** Parse and execute a print statement from the token stream. */ + void printStatement() { + _input.next("print"); + String name = _input.peek(); + Table table = tableName(); + _input.next(";"); + System.out.printf("Contents of %s:%n", name); + table.print(); + } + + /** Parse and execute a select statement from the token stream. */ + void selectStatement() { + Table table = selectClause(); + System.out.println("Search results:"); + table.print(); + _input.next(";"); + } + + /** Parse and execute a table definition, returning the specified + * table. */ + Table tableDefinition() { + Table table; + if (_input.nextIf("(")) { + ArrayList lst = new ArrayList(); + lst.add(columnName()); + while (_input.nextIf(",")) { + lst.add(columnName()); + } + _input.next(")"); + table = new Table(lst); + } else { + _input.next("as"); + table = selectClause(); + } + return table; + } + + /** Parse and execute a select clause from the token stream, returning the + * resulting table. */ + Table selectClause() { + _input.next("select"); + ArrayList col = new ArrayList(); + ArrayList
tab = new ArrayList
(); + /* Select all */ + if (_input.nextIf("*")) { + _input.next("from"); + /* We need to get all the columns from the required tables.*/ + Table tab1 = tableName(); + tab.add(tab1); + while (_input.nextIf(",")) { + Table tab2 = tableName(); + tab.add(tab2); + } + for (int i = 0; i < tab.size(); i++) { + Table tab_item = tab.get(i); + ArrayList col_list = getAllCol(tab_item); + col.addAll(col_list); + } + /* Eliminate the duplicate column names */ + ArrayList listTemp = new ArrayList(); + for(int j=0; j con_aux = new ArrayList(); + Table t1 = tab.get(0); Table t2 = tab.get(1); + joined_table = t1.select(t2, rm_dup(getAllCol(t1), getAllCol(t2)), con_aux); + for (int i = 2; i < tab.size(); i++) { + joined_table = joined_table.select(tab.get(i), + rm_dup(getAllCol(joined_table), getAllCol(tab.get(i))), con_aux); + } + } + + ConditionClause con = new ConditionClause(); + if (_input.nextIf("where")) { + // if (table2 == null) { /* Only one table involved */ + // con = conditionClause(tab.get(0)); + // } else { + // con = conditionClause(tab.toArray(new Table[0])); + // } + con = conditionClause(joined_table); + } + + return joined_table.select(col, con.conList, con.operations); + // return tab.get(0); + // if (table2 == null) { + // return table1.select(col, con); + // } else { + // return table1.select(table2, col, con); + // } + + } + + ArrayList rm_dup(ArrayList l1, ArrayList l2) { + ArrayList result = new ArrayList(); + result.addAll(l1); + for (int i = 0; i < l2.size(); i++) { + if (!l1.contains(l2.get(i))) { + result.add(l2.get(i)); + } + } + return result; + } + + ArrayList getAllCol(Table t) { + ArrayList result = new ArrayList(); + for (int i = 0; i < t.columns(); i++) { + result.add(t.getTitle(i)); + } + return result; + } + + /** Parse and return a valid name (identifier) from the token stream. */ + String name() { + return _input.next(Tokenizer.IDENTIFIER); + } + + /** Parse and return a valid column name from the token stream. Column + * names are simply names; we use a different method name to clarify + * the intent of the code. */ + String columnName() { + return name(); + } + + /** Parse a valid table name from the token stream, and return the Table + * that it designates, which must be loaded. */ + Table tableName() { + String name = name(); + Table table = _database.get(name); + if (table == null) { + throw error("unknown table: %s", name); + } + return table; + } + + /** Parse a literal and return the string it represents (i.e., without + * single quotes). */ + String literal() { + String lit = _input.next(Tokenizer.LITERAL); + return lit.substring(1, lit.length() - 1).trim(); + } + + /** Parse and return a list of Conditions that apply to TABLES from the + * token stream. This denotes the conjunction (`and') zero + * or more Conditions. */ + public class ConditionClause{ + ArrayList conList; + String operations; + public ConditionClause() { + this.conList = null; + this.operations = ""; + } + public ConditionClause(ArrayList conList, String operations) { + this.conList = conList; + this.operations = operations; + } + } + + ConditionClause conditionClause(Table... tables) { + ArrayList conList = new ArrayList(); + conList.add(condition(tables)); + StringBuffer operation = new StringBuffer(""); + while (_input.nextIs("and") || _input.nextIs("or")){ + if (_input.nextIf("and")) + operation.append("0"); + if (_input.nextIf("or")) + operation.append("1"); + conList.add(condition(tables)); + } + return new ConditionClause(conList, operation.toString()); + } + + /** Parse and return a Condition that applies to TABLES from the + * token stream. */ + Condition condition(Table... tables) { + Column leftCol = new Column(columnName(), tables); + String relation = _input.next(RELATION); + if (_input.nextIs(LITERAL)){ + return new Condition(leftCol, relation, literal()); + } + else{ + Column rightCol = new Column(columnName(), tables); + return new Condition(leftCol, relation, rightCol); + } + + } + + /** Advance the input past the next semicolon. */ + void skipCommand() { + while (true) { + try { + while (!_input.nextIf(";") && !_input.nextIf("*EOF*")) { + _input.next(); + } + return; + } catch (DBException excp) { + /* No action */ + } + } + } + + /** The command input source. */ + private Tokenizer _input; + /** Database containing all tables. */ + private Database _database; +} diff --git a/.history/proj1/db61b/CommandInterpreter_20221210102940.java b/.history/proj1/db61b/CommandInterpreter_20221210102940.java new file mode 100644 index 0000000..6a3e6b9 --- /dev/null +++ b/.history/proj1/db61b/CommandInterpreter_20221210102940.java @@ -0,0 +1,488 @@ +// This is a SUGGESTED skeleton for a class that parses and executes database +// statements. Be sure to read the STRATEGY section, and ask us if you have any +// questions about it. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution adds or changes about 50 +// lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Scanner; +import java.util.Collections; + +import static db61b.Utils.*; +import static db61b.Tokenizer.*; + +/** An object that reads and interprets a sequence of commands from an + * input source. + * @author */ +class CommandInterpreter { + + /* STRATEGY. + * + * This interpreter parses commands using a technique called + * "recursive descent." The idea is simple: we convert the BNF grammar, + * as given in the specification document, into a program. + * + * First, we break up the input into "tokens": strings that correspond + * to the "base case" symbols used in the BNF grammar. These are + * keywords, such as "select" or "create"; punctuation and relation + * symbols such as ";", ",", ">="; and other names (of columns or tables). + * All whitespace and comments get discarded in this process, so that the + * rest of the program can deal just with things mentioned in the BNF. + * The class Tokenizer performs this breaking-up task, known as + * "tokenizing" or "lexical analysis." + * + * The rest of the parser consists of a set of functions that call each + * other (possibly recursively, although that isn't needed for this + * particular grammar) to operate on the sequence of tokens, one function + * for each BNF rule. Consider a rule such as + * + * ::= create table
; + * + * We can treat this as a definition for a function named (say) + * createStatement. The purpose of this function is to consume the + * tokens for one create statement from the remaining token sequence, + * to perform the required actions, and to return the resulting value, + * if any (a create statement has no value, just side-effects, but a + * select clause is supposed to produce a table, according to the spec.) + * + * The body of createStatement is dictated by the right-hand side of the + * rule. For each token (like create), we check that the next item in + * the token stream is "create" (and report an error otherwise), and then + * advance to the next token. For a metavariable, like
, + * we consume the tokens for
, and do whatever is + * appropriate with the resulting value. We do so by calling the + * tableDefinition function, which is constructed (as is createStatement) + * to do exactly this. + * + * Thus, the body of createStatement would look like this (_input is + * the sequence of tokens): + * + * _input.next("create"); + * _input.next("table"); + * String name = name(); + * Table table = tableDefinition(); + * _input.next(";"); + * + * plus other code that operates on name and table to perform the function + * of the create statement. The .next method of Tokenizer is set up to + * throw an exception (DBException) if the next token does not match its + * argument. Thus, any syntax error will cause an exception, which your + * program can catch to do error reporting. + * + * This leaves the issue of what to do with rules that have alternatives + * (the "|" symbol in the BNF grammar). Fortunately, our grammar has + * been written with this problem in mind. When there are multiple + * alternatives, you can always tell which to pick based on the next + * unconsumed token. For example,
has two alternative + * right-hand sides, one of which starts with "(", and one with "as". + * So all you have to do is test: + * + * if (_input.nextIs("(")) { + * _input.next("("); + * // code to process ", )" + * } else { + * // code to process "as " + * } + * + * combining the calls to .nextIs and .next. + * + * You can handle the list of s in the preceding in a number + * of ways, but personally, I suggest a simple loop: + * + * ... = columnName(); + * while (_input.nextIs(",")) { + * _input.next(","); + * ... = columnName(); + * } + * + * or if you prefer even greater concision: + * + * ... = columnName(); + * while (_input.nextIf(",")) { + * ... = columnName(); + * } + * + * (You'll have to figure out what do with the names you accumulate, of + * course). + */ + + + /** A new CommandInterpreter executing commands read from INP, writing + * prompts on PROMPTER, if it is non-null. */ + + CommandInterpreter(Scanner inp, PrintStream prompter) { + _input = new Tokenizer(inp, prompter); + _database = new Database(); + } + + /** Parse and execute one statement from the token stream. Return true + * iff the command is something other than quit or exit. */ + boolean statement() { + switch (_input.peek().toLowerCase()) { + case "create": + createStatement(); + break; + case "load": + loadStatement(); + break; + case "exit": case "quit": + exitStatement(); + return false; + case "*EOF*": + return false; + case "insert": + insertStatement(); + break; + case "print": + printStatement(); + break; + case "select": + selectStatement(); + break; + case "store": + storeStatement(); + break; + default: + throw error("unrecognizable command"); + } + return true; + } + + /** Parse and execute a create statement from the token stream. */ + void createStatement() { + _input.next("create"); + _input.next("table"); + String name = name(); + Table table = tableDefinition(); + _input.next(";"); + _database.put(name, table); + + } + + /** Parse and execute an exit or quit statement. Actually does nothing + * except check syntax, since statement() handles the actual exiting. */ + void exitStatement() { + if (!_input.nextIf("quit")) { + _input.next("exit"); + } + _input.next(";"); + } + + /** Parse and execute an insert statement from the token stream. */ + void insertStatement() { + _input.next("insert"); + _input.next("into"); + Table table = tableName(); + _input.next("values"); + + ArrayList values = new ArrayList<>(); + values.add(literal()); + while (_input.nextIf(",")) { + values.add(literal()); + } + + table.add(new Row(values.toArray(new String[values.size()]))); + _input.next(";"); + } + + /** Parse and execute a load statement from the token stream. */ + void loadStatement() { + _input.next("load"); + String name = name(); + Table content = Table.readTable(name); + _database.put(name, content); + _input.next(";"); + System.out.printf("Loaded %s.db%n", name); + } + + /** Parse and execute a store statement from the token stream. */ + void storeStatement() { + _input.next("store"); + String name = _input.peek(); + Table table = tableName(); + table.writeTable(name); + System.out.printf("Stored %s.db%n", name); + _input.next(";"); + } + + /** Parse and execute a print statement from the token stream. */ + void printStatement() { + _input.next("print"); + String name = _input.peek(); + Table table = tableName(); + _input.next(";"); + System.out.printf("Contents of %s:%n", name); + table.print(); + } + + /** Parse and execute a select statement from the token stream. */ + void selectStatement() { + Table table = selectClause(); + ArrayList columnNames = new ArrayList(); + if (_input.nextIf("order")) { + _input.next("by"); + columnNames.add(columnName()); + while (_input.nextIf(",")) { + columnNames.add(columnName()); + } + if (_input.nextIf("desc")) { + table = orderTable_desc(table, columnNames); + } + else if (_input.nextIf("asc")) { + table = orderTable_asc(table, columnNames); + } + else { + table = orderTable_asc(table, columnNames); + } + } + System.out.println("Search results:"); + table.print(); + _input.next(";"); + } + + /* Reorder the table in ascending order. If two rows have the same value, + it will keep the original sequence. */ + Table orderTable_asc(Table table, ArrayList columnNames) { + ArrayList orderedlist = new ArrayList(); + orderedlist = table.selectcolumn(columnNames.get(0)); + Collections.sort(orderedlist); + table = table.sort(orderedlist, columnNames.get(0)); + return table; + } + + /* Reorder the table in descending order. If two rows have the same value, + it will keep the original sequence. */ + Table orderTable_desc(Table table, ArrayList columnNames) { + ArrayList orderedlist = new ArrayList(); + orderedlist = table.selectcolumn(columnNames.get(0)); + Collections.sort(orderedlist); + Collections.reverse(orderedlist); + table = table.sort(orderedlist, columnNames.get(0)); + return table; + } + + /** Parse and execute a table definition, returning the specified + * table. */ + Table tableDefinition() { + Table table; + if (_input.nextIf("(")) { + ArrayList lst = new ArrayList(); + lst.add(columnName()); + while (_input.nextIf(",")) { + lst.add(columnName()); + } + _input.next(")"); + table = new Table(lst); + } else { + _input.next("as"); + table = selectClause(); + } + return table; + } + + /** Parse and execute a select clause from the token stream, returning the + * resulting table. */ + Table selectClause() { + _input.next("select"); + ArrayList col = new ArrayList(); + ArrayList
tab = new ArrayList
(); + /* Select all */ + if (_input.nextIf("*")) { + _input.next("from"); + /* We need to get all the columns from the required tables.*/ + Table tab1 = tableName(); + tab.add(tab1); + while (_input.nextIf(",")) { + Table tab2 = tableName(); + tab.add(tab2); + } + for (int i = 0; i < tab.size(); i++) { + Table tab_item = tab.get(i); + ArrayList col_list = getAllCol(tab_item); + col.addAll(col_list); + } + /* Eliminate the duplicate column names */ + ArrayList listTemp = new ArrayList(); + for(int j=0; j con_aux = new ArrayList(); + Table t1 = tab.get(0); Table t2 = tab.get(1); + joined_table = t1.select(t2, rm_dup(getAllCol(t1), getAllCol(t2)), con_aux); + for (int i = 2; i < tab.size(); i++) { + joined_table = joined_table.select(tab.get(i), + rm_dup(getAllCol(joined_table), getAllCol(tab.get(i))), con_aux); + } + } + + ConditionClause con = new ConditionClause(); + if (_input.nextIf("where")) { + // if (table2 == null) { /* Only one table involved */ + // con = conditionClause(tab.get(0)); + // } else { + // con = conditionClause(tab.toArray(new Table[0])); + // } + con = conditionClause(joined_table); + } + + return joined_table.select(col, con.conList, con.operations); + // return tab.get(0); + // if (table2 == null) { + // return table1.select(col, con); + // } else { + // return table1.select(table2, col, con); + // } + + } + + ArrayList rm_dup(ArrayList l1, ArrayList l2) { + ArrayList result = new ArrayList(); + result.addAll(l1); + for (int i = 0; i < l2.size(); i++) { + if (!l1.contains(l2.get(i))) { + result.add(l2.get(i)); + } + } + return result; + } + + ArrayList getAllCol(Table t) { + ArrayList result = new ArrayList(); + for (int i = 0; i < t.columns(); i++) { + result.add(t.getTitle(i)); + } + return result; + } + + /** Parse and return a valid name (identifier) from the token stream. */ + String name() { + return _input.next(Tokenizer.IDENTIFIER); + } + + /** Parse and return a valid column name from the token stream. Column + * names are simply names; we use a different method name to clarify + * the intent of the code. */ + String columnName() { + return name(); + } + + /** Parse a valid table name from the token stream, and return the Table + * that it designates, which must be loaded. */ + Table tableName() { + String name = name(); + Table table = _database.get(name); + if (table == null) { + throw error("unknown table: %s", name); + } + return table; + } + + /** Parse a literal and return the string it represents (i.e., without + * single quotes). */ + String literal() { + String lit = _input.next(Tokenizer.LITERAL); + return lit.substring(1, lit.length() - 1).trim(); + } + + /** Parse and return a list of Conditions that apply to TABLES from the + * token stream. This denotes the conjunction (`and') zero + * or more Conditions. */ + public class ConditionClause{ + ArrayList conList; + String operations; + public ConditionClause() { + this.conList = null; + this.operations = ""; + } + public ConditionClause(ArrayList conList, String operations) { + this.conList = conList; + this.operations = operations; + } + } + + ConditionClause conditionClause(Table... tables) { + ArrayList conList = new ArrayList(); + conList.add(condition(tables)); + StringBuffer operation = new StringBuffer(""); + while (_input.nextIs("and") || _input.nextIs("or")){ + if (_input.nextIf("and")) + operation.append("0"); + if (_input.nextIf("or")) + operation.append("1"); + conList.add(condition(tables)); + } + return new ConditionClause(conList, operation.toString()); + } + + /** Parse and return a Condition that applies to TABLES from the + * token stream. */ + Condition condition(Table... tables) { + Column leftCol = new Column(columnName(), tables); + String relation = _input.next(RELATION); + if (_input.nextIs(LITERAL)){ + return new Condition(leftCol, relation, literal()); + } + else{ + Column rightCol = new Column(columnName(), tables); + return new Condition(leftCol, relation, rightCol); + } + + } + + /** Advance the input past the next semicolon. */ + void skipCommand() { + while (true) { + try { + while (!_input.nextIf(";") && !_input.nextIf("*EOF*")) { + _input.next(); + } + return; + } catch (DBException excp) { + /* No action */ + } + } + } + + /** The command input source. */ + private Tokenizer _input; + /** Database containing all tables. */ + private Database _database; +} diff --git a/.history/proj1/db61b/CommandInterpreter_20221210162445.java b/.history/proj1/db61b/CommandInterpreter_20221210162445.java new file mode 100644 index 0000000..a41ad22 --- /dev/null +++ b/.history/proj1/db61b/CommandInterpreter_20221210162445.java @@ -0,0 +1,503 @@ +// This is a SUGGESTED skeleton for a class that parses and executes database +// statements. Be sure to read the STRATEGY section, and ask us if you have any +// questions about it. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution adds or changes about 50 +// lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Scanner; +import java.util.Collections; + +import static db61b.Utils.*; +import static db61b.Tokenizer.*; + +/** An object that reads and interprets a sequence of commands from an + * input source. + * @author */ +class CommandInterpreter { + + /* STRATEGY. + * + * This interpreter parses commands using a technique called + * "recursive descent." The idea is simple: we convert the BNF grammar, + * as given in the specification document, into a program. + * + * First, we break up the input into "tokens": strings that correspond + * to the "base case" symbols used in the BNF grammar. These are + * keywords, such as "select" or "create"; punctuation and relation + * symbols such as ";", ",", ">="; and other names (of columns or tables). + * All whitespace and comments get discarded in this process, so that the + * rest of the program can deal just with things mentioned in the BNF. + * The class Tokenizer performs this breaking-up task, known as + * "tokenizing" or "lexical analysis." + * + * The rest of the parser consists of a set of functions that call each + * other (possibly recursively, although that isn't needed for this + * particular grammar) to operate on the sequence of tokens, one function + * for each BNF rule. Consider a rule such as + * + * ::= create table
; + * + * We can treat this as a definition for a function named (say) + * createStatement. The purpose of this function is to consume the + * tokens for one create statement from the remaining token sequence, + * to perform the required actions, and to return the resulting value, + * if any (a create statement has no value, just side-effects, but a + * select clause is supposed to produce a table, according to the spec.) + * + * The body of createStatement is dictated by the right-hand side of the + * rule. For each token (like create), we check that the next item in + * the token stream is "create" (and report an error otherwise), and then + * advance to the next token. For a metavariable, like
, + * we consume the tokens for
, and do whatever is + * appropriate with the resulting value. We do so by calling the + * tableDefinition function, which is constructed (as is createStatement) + * to do exactly this. + * + * Thus, the body of createStatement would look like this (_input is + * the sequence of tokens): + * + * _input.next("create"); + * _input.next("table"); + * String name = name(); + * Table table = tableDefinition(); + * _input.next(";"); + * + * plus other code that operates on name and table to perform the function + * of the create statement. The .next method of Tokenizer is set up to + * throw an exception (DBException) if the next token does not match its + * argument. Thus, any syntax error will cause an exception, which your + * program can catch to do error reporting. + * + * This leaves the issue of what to do with rules that have alternatives + * (the "|" symbol in the BNF grammar). Fortunately, our grammar has + * been written with this problem in mind. When there are multiple + * alternatives, you can always tell which to pick based on the next + * unconsumed token. For example,
has two alternative + * right-hand sides, one of which starts with "(", and one with "as". + * So all you have to do is test: + * + * if (_input.nextIs("(")) { + * _input.next("("); + * // code to process ", )" + * } else { + * // code to process "as " + * } + * + * combining the calls to .nextIs and .next. + * + * You can handle the list of s in the preceding in a number + * of ways, but personally, I suggest a simple loop: + * + * ... = columnName(); + * while (_input.nextIs(",")) { + * _input.next(","); + * ... = columnName(); + * } + * + * or if you prefer even greater concision: + * + * ... = columnName(); + * while (_input.nextIf(",")) { + * ... = columnName(); + * } + * + * (You'll have to figure out what do with the names you accumulate, of + * course). + */ + + + /** A new CommandInterpreter executing commands read from INP, writing + * prompts on PROMPTER, if it is non-null. */ + + CommandInterpreter(Scanner inp, PrintStream prompter) { + _input = new Tokenizer(inp, prompter); + _database = new Database(); + } + + /** Parse and execute one statement from the token stream. Return true + * iff the command is something other than quit or exit. */ + boolean statement() { + switch (_input.peek().toLowerCase()) { + case "create": + createStatement(); + break; + case "load": + loadStatement(); + break; + case "exit": case "quit": + exitStatement(); + return false; + case "*EOF*": + return false; + case "insert": + insertStatement(); + break; + case "print": + printStatement(); + break; + case "select": + selectStatement(); + break; + case "store": + storeStatement(); + break; + default: + throw error("unrecognizable command"); + } + return true; + } + + /** Parse and execute a create statement from the token stream. */ + void createStatement() { + _input.next("create"); + _input.next("table"); + String name = name(); + Table table = tableDefinition(); + _input.next(";"); + _database.put(name, table); + + } + + /** Parse and execute an exit or quit statement. Actually does nothing + * except check syntax, since statement() handles the actual exiting. */ + void exitStatement() { + if (!_input.nextIf("quit")) { + _input.next("exit"); + } + _input.next(";"); + } + + /** Parse and execute an insert statement from the token stream. */ + void insertStatement() { + _input.next("insert"); + _input.next("into"); + Table table = tableName(); + _input.next("values"); + + ArrayList values = new ArrayList<>(); + values.add(literal()); + while (_input.nextIf(",")) { + values.add(literal()); + } + + table.add(new Row(values.toArray(new String[values.size()]))); + _input.next(";"); + } + + /** Parse and execute a load statement from the token stream. */ + void loadStatement() { + _input.next("load"); + String name = name(); + Table content = Table.readTable(name); + _database.put(name, content); + _input.next(";"); + System.out.printf("Loaded %s.db%n", name); + } + + /** Parse and execute a store statement from the token stream. */ + void storeStatement() { + _input.next("store"); + String name = _input.peek(); + Table table = tableName(); + table.writeTable(name); + System.out.printf("Stored %s.db%n", name); + _input.next(";"); + } + + /** Parse and execute a print statement from the token stream. */ + void printStatement() { + _input.next("print"); + String name = _input.peek(); + Table table = tableName(); + _input.next(";"); + System.out.printf("Contents of %s:%n", name); + table.print(); + } + + /** Parse and execute a select statement from the token stream. */ + void selectStatement() { + Table table = selectClause(); + ArrayList columnNames = new ArrayList(); + if (_input.nextIf("order")) { + _input.next("by"); + columnNames.add(columnName()); + while (_input.nextIf(",")) { + columnNames.add(columnName()); + } + if (_input.nextIf("desc")) { + table = orderTable_desc(table, columnNames); + } + else if (_input.nextIf("asc")) { + table = orderTable_asc(table, columnNames); + } + else { + table = orderTable_asc(table, columnNames); + } + } + System.out.println("Search results:"); + table.print(); + _input.next(";"); + } + + /* Reorder the table in ascending order. If two rows have the same value, + it will keep the original sequence. */ + Table orderTable_asc(Table table, ArrayList columnNames) { + ArrayList orderedlist = new ArrayList(); + if (columnNames.size() == 1) { + orderedlist = table.selectcolumn(columnNames.get(0)); + Collections.sort(orderedlist); + table = table.sort(orderedlist, columnNames.get(0)); + } + else { + orderedlist = table.selectcolumns(columnNames); + Collections.sort(orderedlist); + table = table.sort(orderedlist, columnNames); + } + return table; + } + + /* Reorder the table in descending order. If two rows have the same value, + it will keep the original sequence. */ + Table orderTable_desc(Table table, ArrayList columnNames) { + ArrayList orderedlist = new ArrayList(); + if (columnNames.size() == 1) { + orderedlist = table.selectcolumn(columnNames.get(0)); + Collections.sort(orderedlist); + Collections.reverse(orderedlist); + table = table.sort(orderedlist, columnNames.get(0)); + } + else { + orderedlist = table.selectcolumns(columnNames); + Collections.sort(orderedlist); + Collections.reverse(orderedlist); + table = table.sort(orderedlist, columnNames); + } + return table; + } + + /** Parse and execute a table definition, returning the specified + * table. */ + Table tableDefinition() { + Table table; + if (_input.nextIf("(")) { + ArrayList lst = new ArrayList(); + lst.add(columnName()); + while (_input.nextIf(",")) { + lst.add(columnName()); + } + _input.next(")"); + table = new Table(lst); + } else { + _input.next("as"); + table = selectClause(); + } + return table; + } + + /** Parse and execute a select clause from the token stream, returning the + * resulting table. */ + Table selectClause() { + _input.next("select"); + ArrayList col = new ArrayList(); + ArrayList
tab = new ArrayList
(); + /* Select all */ + if (_input.nextIf("*")) { + _input.next("from"); + /* We need to get all the columns from the required tables.*/ + Table tab1 = tableName(); + tab.add(tab1); + while (_input.nextIf(",")) { + Table tab2 = tableName(); + tab.add(tab2); + } + for (int i = 0; i < tab.size(); i++) { + Table tab_item = tab.get(i); + ArrayList col_list = getAllCol(tab_item); + col.addAll(col_list); + } + /* Eliminate the duplicate column names */ + ArrayList listTemp = new ArrayList(); + for(int j=0; j con_aux = new ArrayList(); + Table t1 = tab.get(0); Table t2 = tab.get(1); + joined_table = t1.select(t2, rm_dup(getAllCol(t1), getAllCol(t2)), con_aux); + for (int i = 2; i < tab.size(); i++) { + joined_table = joined_table.select(tab.get(i), + rm_dup(getAllCol(joined_table), getAllCol(tab.get(i))), con_aux); + } + } + + ConditionClause con = new ConditionClause(); + if (_input.nextIf("where")) { + // if (table2 == null) { /* Only one table involved */ + // con = conditionClause(tab.get(0)); + // } else { + // con = conditionClause(tab.toArray(new Table[0])); + // } + con = conditionClause(joined_table); + } + + return joined_table.select(col, con.conList, con.operations); + // return tab.get(0); + // if (table2 == null) { + // return table1.select(col, con); + // } else { + // return table1.select(table2, col, con); + // } + + } + + ArrayList rm_dup(ArrayList l1, ArrayList l2) { + ArrayList result = new ArrayList(); + result.addAll(l1); + for (int i = 0; i < l2.size(); i++) { + if (!l1.contains(l2.get(i))) { + result.add(l2.get(i)); + } + } + return result; + } + + ArrayList getAllCol(Table t) { + ArrayList result = new ArrayList(); + for (int i = 0; i < t.columns(); i++) { + result.add(t.getTitle(i)); + } + return result; + } + + /** Parse and return a valid name (identifier) from the token stream. */ + String name() { + return _input.next(Tokenizer.IDENTIFIER); + } + + /** Parse and return a valid column name from the token stream. Column + * names are simply names; we use a different method name to clarify + * the intent of the code. */ + String columnName() { + return name(); + } + + /** Parse a valid table name from the token stream, and return the Table + * that it designates, which must be loaded. */ + Table tableName() { + String name = name(); + Table table = _database.get(name); + if (table == null) { + throw error("unknown table: %s", name); + } + return table; + } + + /** Parse a literal and return the string it represents (i.e., without + * single quotes). */ + String literal() { + String lit = _input.next(Tokenizer.LITERAL); + return lit.substring(1, lit.length() - 1).trim(); + } + + /** Parse and return a list of Conditions that apply to TABLES from the + * token stream. This denotes the conjunction (`and') zero + * or more Conditions. */ + public class ConditionClause{ + ArrayList conList; + String operations; + public ConditionClause() { + this.conList = null; + this.operations = ""; + } + public ConditionClause(ArrayList conList, String operations) { + this.conList = conList; + this.operations = operations; + } + } + + ConditionClause conditionClause(Table... tables) { + ArrayList conList = new ArrayList(); + conList.add(condition(tables)); + StringBuffer operation = new StringBuffer(""); + while (_input.nextIs("and") || _input.nextIs("or")){ + if (_input.nextIf("and")) + operation.append("0"); + if (_input.nextIf("or")) + operation.append("1"); + conList.add(condition(tables)); + } + return new ConditionClause(conList, operation.toString()); + } + + /** Parse and return a Condition that applies to TABLES from the + * token stream. */ + Condition condition(Table... tables) { + Column leftCol = new Column(columnName(), tables); + String relation = _input.next(RELATION); + if (_input.nextIs(LITERAL)){ + return new Condition(leftCol, relation, literal()); + } + else{ + Column rightCol = new Column(columnName(), tables); + return new Condition(leftCol, relation, rightCol); + } + + } + + /** Advance the input past the next semicolon. */ + void skipCommand() { + while (true) { + try { + while (!_input.nextIf(";") && !_input.nextIf("*EOF*")) { + _input.next(); + } + return; + } catch (DBException excp) { + /* No action */ + } + } + } + + /** The command input source. */ + private Tokenizer _input; + /** Database containing all tables. */ + private Database _database; +} diff --git a/.history/proj1/db61b/Row_20221202114255.java b/.history/proj1/db61b/Row_20221202114255.java new file mode 100644 index 0000000..fc8ee35 --- /dev/null +++ b/.history/proj1/db61b/Row_20221202114255.java @@ -0,0 +1,76 @@ +// This is a SUGGESTED skeleton for a class that describes a single Row of a +// Table. You can throw this away if you want, but it is a good idea to try to +// understand it first. Our solution changes or adds about 10 lines in this +// skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.util.Arrays; +import java.util.List; + +/** A single row of a database. + * @author + */ +class Row { + /** A Row whose column values are DATA. The array DATA must not be altered + * subsequently. */ + Row(String[] data) { + _data = data; + } + + /** Given M COLUMNS that were created from a sequence of Tables + * [t0,...,tn] as well as ROWS [r0,...,rn] that were drawn from those + * same tables [t0,...,tn], constructs a new Row containing M values, + * where the ith value of this new Row is taken from the location given + * by the ith COLUMN (for each i from 0 to M-1). + * + * More specifically, if _table is the Table number corresponding to + * COLUMN i, then the ith value of the newly created Row should come from + * ROWS[_table]. + * + * Even more specifically, the ith value of the newly created Row should + * be equal to ROWS[_table].get(_column), where _column is the column + * number in ROWS[_table] corresponding to COLUMN i. + * + * There is a method in the Column class that you'll need to use, see + * {@link db61b.Column#getFrom}). you're wondering why this looks like a + * potentially clickable link it is! Just not in source. You might + * consider converting this spec to HTML using the Javadoc command. + */ + Row(List columns, Row... rows) { + // FILL IN + } + + /** Return my number of columns. */ + int size() { + return 0; // REPLACE WITH SOLUTION + } + + /** Return the value of my Kth column. Requires that 0 <= K < size(). */ + String get(int k) { + return null; // REPLACE WITH SOLUTION + } + + @Override + public boolean equals(Object obj) { + return false; // REPLACE WITH SOLUTION + } + + /* NOTE: Whenever you override the .equals() method for a class, you + * should also override hashCode so as to insure that if + * two objects are supposed to be equal, they also return the same + * .hashCode() value (the converse need not be true: unequal objects MAY + * also return the same .hashCode()). The hash code is used by certain + * Java library classes to expedite searches (see Chapter 7 of Data + * Structures (Into Java)). */ + + @Override + public int hashCode() { + return Arrays.hashCode(_data); + } + + /** Contents of this row. */ + private String[] _data; +} diff --git a/.history/proj1/db61b/Row_20221202215354.java b/.history/proj1/db61b/Row_20221202215354.java new file mode 100644 index 0000000..6d01ce1 --- /dev/null +++ b/.history/proj1/db61b/Row_20221202215354.java @@ -0,0 +1,89 @@ +package db61b; + +import java.util.Arrays; +import java.util.List; +import static db61b.Utils.*; + +/** A single row of a database. + * @author + */ +class Row { + /** A Row whose column values are DATA. The array DATA must not be altered + * subsequently. */ + Row(String[] data) { + _data = data; + } + + /** Given M COLUMNS that were created from a sequence of Tables + * [t0,...,tn] as well as ROWS [r0,...,rn] that were drawn from those + * same tables [t0,...,tn], constructs a new Row containing M values, + * where the ith value of this new Row is taken from the location given + * by the ith COLUMN (for each i from 0 to M-1). + * + * More specifically, if _table is the Table number corresponding to + * COLUMN i, then the ith value of the newly created Row should come from + * ROWS[_table]. + * + * Even more specifically, the ith value of the newly created Row should + * be equal to ROWS[_table].get(_column), where _column is the column + * number in ROWS[_table] corresponding to COLUMN i. + * + * There is a method in the Column class that you'll need to use, see + * {@link db61b.Column#getFrom}). you're wondering why this looks like a + * potentially clickable link it is! Just not in source. You might + * consider converting this spec to HTML using the Javadoc command. + */ + Row(List columns, Row... rows) { + int M = columns.size(); + String[] new_row = new String[M]; + for (int i = 0; i < M; i++) { + new_row[i] = columns.get(i).getFrom(rows); + } + _data = new_row; + } + + /** Return my number of columns. */ + int size() { + return _data.length; + } + + /** Return the value of my Kth column. Requires that 0 <= K < size(). */ + String get(int k) { + if (k >= 0 && k < size()) { + return _data[k]; + } + else { + throw error("IndexOutOfBounds"); + } + } + + @Override + public boolean equals(Object obj) { + if (this.size() != ((Row) obj).size()) { + return false; + } + for (int i = 0; i < this.size(); i++) { + if (!(this.get(i).equals(((Row) obj).get(i)))) { + return false; + } + } + return true; + } + + /* NOTE: Whenever you override the .equals() method for a class, you + * should also override hashCode so as to insure that if + * two objects are supposed to be equal, they also return the same + * .hashCode() value (the converse need not be true: unequal objects MAY + * also return the same .hashCode()). The hash code is used by certain + * Java library classes to expedite searches (see Chapter 7 of Data + * Structures (Into Java)). */ + + @Override + public int hashCode() { + return Arrays.hashCode(_data); + } + + /** Contents of this row. */ + private String[] _data; +} + diff --git a/.history/proj1/db61b/Row_20221202215510.java b/.history/proj1/db61b/Row_20221202215510.java new file mode 100644 index 0000000..6d01ce1 --- /dev/null +++ b/.history/proj1/db61b/Row_20221202215510.java @@ -0,0 +1,89 @@ +package db61b; + +import java.util.Arrays; +import java.util.List; +import static db61b.Utils.*; + +/** A single row of a database. + * @author + */ +class Row { + /** A Row whose column values are DATA. The array DATA must not be altered + * subsequently. */ + Row(String[] data) { + _data = data; + } + + /** Given M COLUMNS that were created from a sequence of Tables + * [t0,...,tn] as well as ROWS [r0,...,rn] that were drawn from those + * same tables [t0,...,tn], constructs a new Row containing M values, + * where the ith value of this new Row is taken from the location given + * by the ith COLUMN (for each i from 0 to M-1). + * + * More specifically, if _table is the Table number corresponding to + * COLUMN i, then the ith value of the newly created Row should come from + * ROWS[_table]. + * + * Even more specifically, the ith value of the newly created Row should + * be equal to ROWS[_table].get(_column), where _column is the column + * number in ROWS[_table] corresponding to COLUMN i. + * + * There is a method in the Column class that you'll need to use, see + * {@link db61b.Column#getFrom}). you're wondering why this looks like a + * potentially clickable link it is! Just not in source. You might + * consider converting this spec to HTML using the Javadoc command. + */ + Row(List columns, Row... rows) { + int M = columns.size(); + String[] new_row = new String[M]; + for (int i = 0; i < M; i++) { + new_row[i] = columns.get(i).getFrom(rows); + } + _data = new_row; + } + + /** Return my number of columns. */ + int size() { + return _data.length; + } + + /** Return the value of my Kth column. Requires that 0 <= K < size(). */ + String get(int k) { + if (k >= 0 && k < size()) { + return _data[k]; + } + else { + throw error("IndexOutOfBounds"); + } + } + + @Override + public boolean equals(Object obj) { + if (this.size() != ((Row) obj).size()) { + return false; + } + for (int i = 0; i < this.size(); i++) { + if (!(this.get(i).equals(((Row) obj).get(i)))) { + return false; + } + } + return true; + } + + /* NOTE: Whenever you override the .equals() method for a class, you + * should also override hashCode so as to insure that if + * two objects are supposed to be equal, they also return the same + * .hashCode() value (the converse need not be true: unequal objects MAY + * also return the same .hashCode()). The hash code is used by certain + * Java library classes to expedite searches (see Chapter 7 of Data + * Structures (Into Java)). */ + + @Override + public int hashCode() { + return Arrays.hashCode(_data); + } + + /** Contents of this row. */ + private String[] _data; +} + diff --git a/.history/proj1/db61b/Table_20221210102529.java b/.history/proj1/db61b/Table_20221210102529.java new file mode 100644 index 0000000..b916970 --- /dev/null +++ b/.history/proj1/db61b/Table_20221210102529.java @@ -0,0 +1,292 @@ +// This is a SUGGESTED skeleton for a class that represents a single +// Table. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution changes or adds +// about 100 lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import static db61b.Utils.*; + +/** A single table in a database. + * @author P. N. Hilfinger + */ +class Table implements Iterable { + /** A new Table whose columns are given by COLUMNTITLES, which may + * not contain dupliace names. */ + Table(String[] columnTitles) { + for (int i = columnTitles.length - 1; i >= 1; i -= 1) { + for (int j = i - 1; j >= 0; j -= 1) { + if (columnTitles[i].equals(columnTitles[j])) { + throw error("duplicate column name: %s", + columnTitles[i]); + } + } + } + // FILL IN + table_titles = columnTitles; + // System.out.printf("title length: %d%n", table_titles.length); + } + + /** A new Table whose columns are give by COLUMNTITLES. */ + Table(List columnTitles) { + this(columnTitles.toArray(new String[columnTitles.size()])); + } + + /** Return the number of columns in this table. */ + public int columns() { + int numOfColumns = table_titles.length; + return numOfColumns; // REPLACE WITH SOLUTION + } + + /** Return the title of the Kth column. Requires 0 <= K < columns(). */ + public String getTitle(int k) { + if (k < 0 || k >= this.columns()) { + throw error("index out of range: %d", k); + } + String titleName = table_titles[k]; + return titleName; // REPLACE WITH SOLUTION + } + + /** Return the number of the column whose title is TITLE, or -1 if + * there isn't one. */ + public int findColumn(String title) { + int columnIndex = -1; + for (int i = 0; i < table_titles.length; i++) { + if (title.toLowerCase().equals(table_titles[i].toLowerCase())) { + columnIndex = i; + break; + } + } + return columnIndex; // REPLACE WITH SOLUTION + } + + /** Return the number of Rows in this table. */ + public int size() { + int size; + size = _rows.size(); + return size; // REPLACE WITH SOLUTION + } + + /** Returns an iterator that returns my rows in an unspecfied order. */ + @Override + public Iterator iterator() { + return _rows.iterator(); + } + + /** Add ROW to THIS if no equal row already exists. Return true if anything + * was added, false otherwise. */ + public boolean add(Row row) { + if (row.size() != table_titles.length) { + throw error("The size of row is inconsistant with the number of the columns: %d", row.size()); + } else { + // apply add method in HashSet class + return _rows.add(row); + } + // REPLACE WITH SOLUTION + } + + /** Read the contents of the file NAME.db, and return as a Table. + * Format errors in the .db file cause a DBException. */ + static Table readTable(String name) { + BufferedReader input; + Table table; + input = null; + table = null; + try { + /*input = new BufferedReader(new FileReader(name + ".db"));*/ + input = new BufferedReader(new FileReader("proj1/testing/" + name + ".db")); + String header = input.readLine(); + if (header == null) { + throw error("missing header in DB file"); + } + String[] columnNames = header.split(","); + table = new Table(columnNames); + String data = input.readLine(); + while (data != null) { + table.add(new Row(data.split(","))); + data = input.readLine(); + } + } catch (FileNotFoundException e) { + throw error("could not find %s.db", name); + } catch (IOException e) { + throw error("problem reading from %s.db", name); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + /* Ignore IOException */ + } + } + } + return table; + } + + /** Write the contents of TABLE into the file NAME.db. Any I/O errors + * cause a DBException. */ + void writeTable(String name) { + PrintStream output; + output = null; + try { + String sep; + sep = ""; + output = new PrintStream(name + ".db"); + // FILL THIS IN + // print the title of the table + for (int i = 0; i < table_titles.length - 1; i++) { + output.print(table_titles[i]); + output.print(","); + } + output.println(table_titles[table_titles.length - 1]); + // print the data of the table + for (Row r : _rows) { + for (int j = 0; j < this.columns() - 1; j++) { + output.print(r.get(j)); + output.print(","); + } + output.println(r.get(this.columns() - 1)); + } + } catch (IOException e) { + throw error("trouble writing to %s.db", name); + } finally { + if (output != null) { + output.close(); + } + } + } + + /** Print my contents on the standard output. */ + void print() { + // FILL IN + int i = 0; + for (Row r : _rows) { + System.out.print(" "); + for (i = 0; i < table_titles.length; i++) { + System.out.print(" "); + System.out.print(r.get(i)); + } + System.out.print("\n"); + } + } + + /** Return a new Table whose columns are COLUMNNAMES, selected from + * rows of this table that satisfy CONDITIONS. */ + Table select(List columnNames, List conditions, String operations) { + Table result = new Table(columnNames); + // FILL IN + ArrayList newColumns = new ArrayList(); + for (String c : columnNames) { + newColumns.add(new Column(c , this)); + } + if (conditions == null) { + for (Row r : _rows) { + result.add(new Row(newColumns, r)); + } + } else { + for (Row r : _rows) { + if (Condition.test(conditions, operations, r)) { + result.add(new Row(newColumns, r)); + } + } + } + return result; + } + + /** Return a new Table whose columns are COLUMNNAMES, selected + * from pairs of rows from this table and from TABLE2 that match + * on all columns with identical names and satisfy CONDITIONS. */ + Table select(Table table2, List columnNames, + List conditions) { + Table result = new Table(columnNames); + + StringBuffer operations = new StringBuffer(conditions.size()); + for (int i = 1; i < conditions.size(); i++) operations.append("0"); + + if (table2 == null) { + result = this.select(columnNames, conditions, operations.toString()); + return result; + } + List common_column_name; + // FILL IN + // comparing the name of each title in this table and table2, find the common column name + common_column_name = new ArrayList(); + for (int i = 0; i < table_titles.length; i++) { + for (int j = 0; j < table2.columns(); j++) { + if (this.getTitle(i).equals(table2.getTitle(j))) { + common_column_name.add(this.getTitle(i)); + break; + } + } + } + List common1, common2; + common1 = new ArrayList(); + common2 = new ArrayList(); + + for (int k = 0; k < common_column_name.size(); k++) { + common1.add(new Column(common_column_name.get(k), this)); + common2.add(new Column(common_column_name.get(k), table2)); + } + + List common = new ArrayList(); + for (String n : columnNames) { + common.add(new Column(n, this, table2)); + } + + if (conditions == null) { + for (Row thisRow : this) { + for (Row table2Row : table2) { + if (equijoin(common1, common2, thisRow, table2Row)) { + result.add(new Row(common, thisRow, table2Row)); + } + } + } + } else { + for (Row thisRow : this) { + for (Row table2Row : table2) { + if (equijoin(common1, common2, thisRow, table2Row) && Condition.test(conditions, operations.toString(), thisRow, table2Row)) { + result.add(new Row(common, thisRow, table2Row)); + } + } + } + } + return result; + } + + /** Return true if the columns COMMON1 from ROW1 and COMMON2 from + * ROW2 all have identical values. Assumes that COMMON1 and + * COMMON2 have the same number of elements and the same names, + * that the columns in COMMON1 apply to this table, those in + * COMMON2 to another, and that ROW1 and ROW2 come, respectively, + * from those tables. */ + private static boolean equijoin(List common1, List common2, + Row row1, Row row2) { + String str1, str2; + for (int i = 0; i < common1.size(); i++) { + str1 = common1.get(i).getFrom(row1); + str2 = common2.get(i).getFrom(row2); + if (!str1.equals(str2)) { + return false; + } + } + return true; // REPLACE WITH SOLUTION + } + + /** My rows. */ + private HashSet _rows = new HashSet<>(); + // FILL IN + /** My titles */ + private String table_titles[]; +} diff --git a/.history/proj1/db61b/Table_20221210103217.java b/.history/proj1/db61b/Table_20221210103217.java new file mode 100644 index 0000000..148eea2 --- /dev/null +++ b/.history/proj1/db61b/Table_20221210103217.java @@ -0,0 +1,332 @@ +// This is a SUGGESTED skeleton for a class that represents a single +// Table. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution changes or adds +// about 100 lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import static db61b.Utils.*; + +/** A single table in a database. + * @author P. N. Hilfinger + */ +class Table implements Iterable { + /** A new Table whose columns are given by COLUMNTITLES, which may + * not contain dupliace names. */ + Table(String[] columnTitles) { + for (int i = columnTitles.length - 1; i >= 1; i -= 1) { + for (int j = i - 1; j >= 0; j -= 1) { + if (columnTitles[i].equals(columnTitles[j])) { + throw error("duplicate column name: %s", + columnTitles[i]); + } + } + } + // FILL IN + table_titles = columnTitles; + // System.out.printf("title length: %d%n", table_titles.length); + } + + /** A new Table whose columns are give by COLUMNTITLES. */ + Table(List columnTitles) { + this(columnTitles.toArray(new String[columnTitles.size()])); + } + + /** Return the number of columns in this table. */ + public int columns() { + int numOfColumns = table_titles.length; + return numOfColumns; // REPLACE WITH SOLUTION + } + + /** Return the title of the Kth column. Requires 0 <= K < columns(). */ + public String getTitle(int k) { + if (k < 0 || k >= this.columns()) { + throw error("index out of range: %d", k); + } + String titleName = table_titles[k]; + return titleName; // REPLACE WITH SOLUTION + } + + /** Return the number of the column whose title is TITLE, or -1 if + * there isn't one. */ + public int findColumn(String title) { + int columnIndex = -1; + for (int i = 0; i < table_titles.length; i++) { + if (title.toLowerCase().equals(table_titles[i].toLowerCase())) { + columnIndex = i; + break; + } + } + return columnIndex; // REPLACE WITH SOLUTION + } + + /** Return the number of Rows in this table. */ + public int size() { + int size; + size = _rows.size(); + return size; // REPLACE WITH SOLUTION + } + + /** Returns an iterator that returns my rows in an unspecfied order. */ + @Override + public Iterator iterator() { + return _rows.iterator(); + } + + /** Add ROW to THIS if no equal row already exists. Return true if anything + * was added, false otherwise. */ + public boolean add(Row row) { + if (row.size() != table_titles.length) { + throw error("The size of row is inconsistant with the number of the columns: %d", row.size()); + } else { + // apply add method in HashSet class + return _rows.add(row); + } + // REPLACE WITH SOLUTION + } + + /** Read the contents of the file NAME.db, and return as a Table. + * Format errors in the .db file cause a DBException. */ + static Table readTable(String name) { + BufferedReader input; + Table table; + input = null; + table = null; + try { + /*input = new BufferedReader(new FileReader(name + ".db"));*/ + input = new BufferedReader(new FileReader("proj1/testing/" + name + ".db")); + String header = input.readLine(); + if (header == null) { + throw error("missing header in DB file"); + } + String[] columnNames = header.split(","); + table = new Table(columnNames); + String data = input.readLine(); + while (data != null) { + table.add(new Row(data.split(","))); + data = input.readLine(); + } + } catch (FileNotFoundException e) { + throw error("could not find %s.db", name); + } catch (IOException e) { + throw error("problem reading from %s.db", name); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + /* Ignore IOException */ + } + } + } + return table; + } + + /** Write the contents of TABLE into the file NAME.db. Any I/O errors + * cause a DBException. */ + void writeTable(String name) { + PrintStream output; + output = null; + try { + String sep; + sep = ""; + output = new PrintStream(name + ".db"); + // FILL THIS IN + // print the title of the table + for (int i = 0; i < table_titles.length - 1; i++) { + output.print(table_titles[i]); + output.print(","); + } + output.println(table_titles[table_titles.length - 1]); + // print the data of the table + for (Row r : _rows) { + for (int j = 0; j < this.columns() - 1; j++) { + output.print(r.get(j)); + output.print(","); + } + output.println(r.get(this.columns() - 1)); + } + } catch (IOException e) { + throw error("trouble writing to %s.db", name); + } finally { + if (output != null) { + output.close(); + } + } + } + + /** Print my contents on the standard output. */ + void print() { + // FILL IN + int i = 0; + for (Row r : _rows) { + System.out.print(" "); + for (i = 0; i < table_titles.length; i++) { + System.out.print(" "); + System.out.print(r.get(i)); + } + System.out.print("\n"); + } + } + + /** Return a list which contains a column of the table. */ + ArrayList selectcolumn(String columnName) { + ArrayList result = new ArrayList(); + ArrayList newColumn = new ArrayList(); + int columnnumber = this.findColumn(columnName); + newColumn.add(new Column(columnName , this)); + for (Row r : _rows) { + result.add(r.get(columnnumber)); + } + return result; + } + + /** Reorder the sequence of rows in a table. */ + Table sort(ArrayList order, String columnName) { + ArrayList columnNames = new ArrayList(); + for (int i = 0; i < this.columns(); i++) { + columnNames.add(this.getTitle(i)); + } + Table result = new Table(columnNames); + ArrayList memory = new ArrayList(); + ArrayList newColumns = new ArrayList(); + int columnnumber = this.findColumn(columnName); + for (String c : columnNames) { + newColumns.add(new Column(c, this)); + } + for (int i = 0; i < order.size(); i++) { + int count = 0; + for (Row r : _rows) { + if ((order.get(i) == r.get(columnnumber) && (!memory.contains(count)))) { + result.add(new Row(newColumns, r)); + memory.add(count); + break; + } + count++; + } + } + return result; + } + + /** Return a new Table whose columns are COLUMNNAMES, selected from + * rows of this table that satisfy CONDITIONS. */ + Table select(List columnNames, List conditions, String operations) { + Table result = new Table(columnNames); + // FILL IN + ArrayList newColumns = new ArrayList(); + for (String c : columnNames) { + newColumns.add(new Column(c , this)); + } + if (conditions == null) { + for (Row r : _rows) { + result.add(new Row(newColumns, r)); + } + } else { + for (Row r : _rows) { + if (Condition.test(conditions, operations, r)) { + result.add(new Row(newColumns, r)); + } + } + } + return result; + } + + /** Return a new Table whose columns are COLUMNNAMES, selected + * from pairs of rows from this table and from TABLE2 that match + * on all columns with identical names and satisfy CONDITIONS. */ + Table select(Table table2, List columnNames, + List conditions) { + Table result = new Table(columnNames); + + StringBuffer operations = new StringBuffer(conditions.size()); + for (int i = 1; i < conditions.size(); i++) operations.append("0"); + + if (table2 == null) { + result = this.select(columnNames, conditions, operations.toString()); + return result; + } + List common_column_name; + // FILL IN + // comparing the name of each title in this table and table2, find the common column name + common_column_name = new ArrayList(); + for (int i = 0; i < table_titles.length; i++) { + for (int j = 0; j < table2.columns(); j++) { + if (this.getTitle(i).equals(table2.getTitle(j))) { + common_column_name.add(this.getTitle(i)); + break; + } + } + } + List common1, common2; + common1 = new ArrayList(); + common2 = new ArrayList(); + + for (int k = 0; k < common_column_name.size(); k++) { + common1.add(new Column(common_column_name.get(k), this)); + common2.add(new Column(common_column_name.get(k), table2)); + } + + List common = new ArrayList(); + for (String n : columnNames) { + common.add(new Column(n, this, table2)); + } + + if (conditions == null) { + for (Row thisRow : this) { + for (Row table2Row : table2) { + if (equijoin(common1, common2, thisRow, table2Row)) { + result.add(new Row(common, thisRow, table2Row)); + } + } + } + } else { + for (Row thisRow : this) { + for (Row table2Row : table2) { + if (equijoin(common1, common2, thisRow, table2Row) && Condition.test(conditions, operations.toString(), thisRow, table2Row)) { + result.add(new Row(common, thisRow, table2Row)); + } + } + } + } + return result; + } + + /** Return true if the columns COMMON1 from ROW1 and COMMON2 from + * ROW2 all have identical values. Assumes that COMMON1 and + * COMMON2 have the same number of elements and the same names, + * that the columns in COMMON1 apply to this table, those in + * COMMON2 to another, and that ROW1 and ROW2 come, respectively, + * from those tables. */ + private static boolean equijoin(List common1, List common2, + Row row1, Row row2) { + String str1, str2; + for (int i = 0; i < common1.size(); i++) { + str1 = common1.get(i).getFrom(row1); + str2 = common2.get(i).getFrom(row2); + if (!str1.equals(str2)) { + return false; + } + } + return true; // REPLACE WITH SOLUTION + } + + /** My rows. */ + // private HashSet _rows = new HashSet<>(); + private ArrayList _rows = new ArrayList<>(); + // FILL IN + /** My titles */ + private String table_titles[]; +} diff --git a/.history/proj1/db61b/Table_20221210162722.java b/.history/proj1/db61b/Table_20221210162722.java new file mode 100644 index 0000000..f11a1da --- /dev/null +++ b/.history/proj1/db61b/Table_20221210162722.java @@ -0,0 +1,397 @@ +// This is a SUGGESTED skeleton for a class that represents a single +// Table. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution changes or adds +// about 100 lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import static db61b.Utils.*; + +/** A single table in a database. + * @author P. N. Hilfinger + */ +class Table implements Iterable { + /** A new Table whose columns are given by COLUMNTITLES, which may + * not contain dupliace names. */ + Table(String[] columnTitles) { + for (int i = columnTitles.length - 1; i >= 1; i -= 1) { + for (int j = i - 1; j >= 0; j -= 1) { + if (columnTitles[i].equals(columnTitles[j])) { + throw error("duplicate column name: %s", + columnTitles[i]); + } + } + } + // FILL IN + table_titles = columnTitles; + // System.out.printf("title length: %d%n", table_titles.length); + } + + /** A new Table whose columns are give by COLUMNTITLES. */ + Table(List columnTitles) { + this(columnTitles.toArray(new String[columnTitles.size()])); + } + + /** Return the number of columns in this table. */ + public int columns() { + int numOfColumns = table_titles.length; + return numOfColumns; // REPLACE WITH SOLUTION + } + + /** Return the title of the Kth column. Requires 0 <= K < columns(). */ + public String getTitle(int k) { + if (k < 0 || k >= this.columns()) { + throw error("index out of range: %d", k); + } + String titleName = table_titles[k]; + return titleName; // REPLACE WITH SOLUTION + } + + /** Return the number of the column whose title is TITLE, or -1 if + * there isn't one. */ + public int findColumn(String title) { + int columnIndex = -1; + for (int i = 0; i < table_titles.length; i++) { + if (title.toLowerCase().equals(table_titles[i].toLowerCase())) { + columnIndex = i; + break; + } + } + return columnIndex; // REPLACE WITH SOLUTION + } + + /** Return the number of Rows in this table. */ + public int size() { + int size; + size = _rows.size(); + return size; // REPLACE WITH SOLUTION + } + + /** Returns an iterator that returns my rows in an unspecfied order. */ + @Override + public Iterator iterator() { + return _rows.iterator(); + } + + /** Add ROW to THIS if no equal row already exists. Return true if anything + * was added, false otherwise. */ + public boolean add(Row row) { + if (row.size() != table_titles.length) { + throw error("The size of row is inconsistant with the number of the columns: %d", row.size()); + } else { + // apply add method in HashSet class + return _rows.add(row); + } + // REPLACE WITH SOLUTION + } + + /** Read the contents of the file NAME.db, and return as a Table. + * Format errors in the .db file cause a DBException. */ + static Table readTable(String name) { + BufferedReader input; + Table table; + input = null; + table = null; + try { + /*input = new BufferedReader(new FileReader(name + ".db"));*/ + input = new BufferedReader(new FileReader("proj1/testing/" + name + ".db")); + String header = input.readLine(); + if (header == null) { + throw error("missing header in DB file"); + } + String[] columnNames = header.split(","); + table = new Table(columnNames); + String data = input.readLine(); + while (data != null) { + table.add(new Row(data.split(","))); + data = input.readLine(); + } + } catch (FileNotFoundException e) { + throw error("could not find %s.db", name); + } catch (IOException e) { + throw error("problem reading from %s.db", name); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + /* Ignore IOException */ + } + } + } + return table; + } + + /** Write the contents of TABLE into the file NAME.db. Any I/O errors + * cause a DBException. */ + void writeTable(String name) { + PrintStream output; + output = null; + try { + String sep; + sep = ""; + output = new PrintStream(name + ".db"); + // FILL THIS IN + // print the title of the table + for (int i = 0; i < table_titles.length - 1; i++) { + output.print(table_titles[i]); + output.print(","); + } + output.println(table_titles[table_titles.length - 1]); + // print the data of the table + for (Row r : _rows) { + for (int j = 0; j < this.columns() - 1; j++) { + output.print(r.get(j)); + output.print(","); + } + output.println(r.get(this.columns() - 1)); + } + } catch (IOException e) { + throw error("trouble writing to %s.db", name); + } finally { + if (output != null) { + output.close(); + } + } + } + + /** Print my contents on the standard output. */ + void print() { + // FILL IN + int i = 0; + for (Row r : _rows) { + System.out.print(" "); + for (i = 0; i < table_titles.length; i++) { + System.out.print(" "); + System.out.print(r.get(i)); + } + System.out.print("\n"); + } + } + + /** Return a list which contains a column of the table. */ + ArrayList selectcolumn(String columnName) { + ArrayList result = new ArrayList(); + int columnnumber = this.findColumn(columnName); + for (Row r : _rows) { + result.add(r.get(columnnumber)); + } + return result; + } + + /** Return a list which contains columns of the table. */ + ArrayList selectcolumns(ArrayList columnNames) { + ArrayList result = new ArrayList(); + ArrayList columnnumber = new ArrayList(); + for (String c : columnNames) { + columnnumber.add(this.findColumn(c)); + } + for (Row r : _rows) { + String tem = ""; + for (int i = 0; i < columnnumber.size(); i++) { + tem += r.get(columnnumber.get(i)); + } + result.add(tem); + } + return result; + } + + /** Reorder the sequence of rows in a table. */ + Table sort(ArrayList order, String columnName) { + ArrayList columnNames = new ArrayList(); + for (int i = 0; i < this.columns(); i++) { + columnNames.add(this.getTitle(i)); + } + Table result = new Table(columnNames); + ArrayList memory = new ArrayList(); + ArrayList newColumns = new ArrayList(); + int columnnumber = this.findColumn(columnName); + for (String c : columnNames) { + newColumns.add(new Column(c, this)); + } + for (int i = 0; i < order.size(); i++) { + int count = 0; + for (Row r : _rows) { + if ((order.get(i) == r.get(columnnumber)) && (!memory.contains(count))) { + result.add(new Row(newColumns, r)); + memory.add(count); + break; + } + count++; + } + } + return result; + } + + /** Reorder the sequence of rows in a table. */ + Table sort(ArrayList order, ArrayList columnName) { + ArrayList columnNames = new ArrayList(); + for (int i = 0; i < this.columns(); i++) { + columnNames.add(this.getTitle(i)); + } + Table result = new Table(columnNames); + ArrayList memory = new ArrayList(); + ArrayList newColumns = new ArrayList(); + ArrayList columnnumber = new ArrayList(); + for (String c : columnName) { + columnnumber.add(this.findColumn(c)); + } + for (String c : columnNames) { + newColumns.add(new Column(c, this)); + } + for (int i = 0; i < order.size(); i++) { + int count = 0; + for (Row r : _rows) { + int start = 0; + int end = 0; + int flag = 1; + for (int j = 0; j < columnnumber.size(); j++) { + start += end; + end += r.get(columnnumber.get(j)).length(); + if (memory.contains(count)) { + flag = 0; + // System.out.println("countbreak"); + break; + } + String left = order.get(i).substring(start, end); + String right = r.get(columnnumber.get(j)); + if (!left.equals(right)) { + // System.out.printf("start is %d, end is %d, left is %s, right is %s\n", start, end, left, right); + flag = 0; + // System.out.println("stringbreak"); + break; + } + } + if ((flag == 1) && (!memory.contains(count))) { + result.add(new Row(newColumns, r)); + memory.add(count); + break; + } + count++; + } + } + return result; + } + + /** Return a new Table whose columns are COLUMNNAMES, selected from + * rows of this table that satisfy CONDITIONS. */ + Table select(List columnNames, List conditions, String operations) { + Table result = new Table(columnNames); + // FILL IN + ArrayList newColumns = new ArrayList(); + for (String c : columnNames) { + newColumns.add(new Column(c , this)); + } + if (conditions == null) { + for (Row r : _rows) { + result.add(new Row(newColumns, r)); + } + } else { + for (Row r : _rows) { + if (Condition.test(conditions, operations, r)) { + result.add(new Row(newColumns, r)); + } + } + } + return result; + } + + /** Return a new Table whose columns are COLUMNNAMES, selected + * from pairs of rows from this table and from TABLE2 that match + * on all columns with identical names and satisfy CONDITIONS. */ + Table select(Table table2, List columnNames, + List conditions) { + Table result = new Table(columnNames); + + StringBuffer operations = new StringBuffer(conditions.size()); + for (int i = 1; i < conditions.size(); i++) operations.append("0"); + + if (table2 == null) { + result = this.select(columnNames, conditions, operations.toString()); + return result; + } + List common_column_name; + // FILL IN + // comparing the name of each title in this table and table2, find the common column name + common_column_name = new ArrayList(); + for (int i = 0; i < table_titles.length; i++) { + for (int j = 0; j < table2.columns(); j++) { + if (this.getTitle(i).equals(table2.getTitle(j))) { + common_column_name.add(this.getTitle(i)); + break; + } + } + } + List common1, common2; + common1 = new ArrayList(); + common2 = new ArrayList(); + + for (int k = 0; k < common_column_name.size(); k++) { + common1.add(new Column(common_column_name.get(k), this)); + common2.add(new Column(common_column_name.get(k), table2)); + } + + List common = new ArrayList(); + for (String n : columnNames) { + common.add(new Column(n, this, table2)); + } + + if (conditions == null) { + for (Row thisRow : this) { + for (Row table2Row : table2) { + if (equijoin(common1, common2, thisRow, table2Row)) { + result.add(new Row(common, thisRow, table2Row)); + } + } + } + } else { + for (Row thisRow : this) { + for (Row table2Row : table2) { + if (equijoin(common1, common2, thisRow, table2Row) && Condition.test(conditions, operations.toString(), thisRow, table2Row)) { + result.add(new Row(common, thisRow, table2Row)); + } + } + } + } + return result; + } + + /** Return true if the columns COMMON1 from ROW1 and COMMON2 from + * ROW2 all have identical values. Assumes that COMMON1 and + * COMMON2 have the same number of elements and the same names, + * that the columns in COMMON1 apply to this table, those in + * COMMON2 to another, and that ROW1 and ROW2 come, respectively, + * from those tables. */ + private static boolean equijoin(List common1, List common2, + Row row1, Row row2) { + String str1, str2; + for (int i = 0; i < common1.size(); i++) { + str1 = common1.get(i).getFrom(row1); + str2 = common2.get(i).getFrom(row2); + if (!str1.equals(str2)) { + return false; + } + } + return true; // REPLACE WITH SOLUTION + } + + /** My rows. */ + // private HashSet _rows = new HashSet<>(); + private ArrayList _rows = new ArrayList<>(); + // FILL IN + /** My titles */ + private String table_titles[]; +} diff --git a/.vscode/settings.json b/.vscode/settings.json index 282f119..ad5cb37 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,8 @@ "SPTF", "Stackoverflow", "unitization" + ], + "githubPullRequests.ignoredPullRequestBranches": [ + "main" ] } \ No newline at end of file diff --git a/Pre_PPT.pdf b/Pre_PPT.pdf new file mode 100644 index 0000000..ba35e22 Binary files /dev/null and b/Pre_PPT.pdf differ diff --git a/Pre_PPT.pptx b/Pre_PPT.pptx new file mode 100644 index 0000000..4540efd Binary files /dev/null and b/Pre_PPT.pptx differ diff --git a/README.md b/README.md index 0b25279..fa05320 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,3 @@ -[![Open in Visual Studio Code](https://classroom.github.com/assets/open-in-vscode-c66648af7eb3fe8bc4f294546bfd86ef473780cde1dea487d3c4ff354943c9ae.svg)](https://classroom.github.com/online_ide?assignment_repo_id=9434865&assignment_repo_type=AssignmentRepo) # CSC3170 Course Project ## Project Overall Description @@ -7,8 +6,8 @@ This is our implementation for the course project of CSC3170, 2022 Fall, CUHK(SZ -- [x] **Application with Database System(s)** -- [ ] **Implementation of a Database System** +- [ ] **Application with Database System(s)** +- [X] **Implementation of a Database System** ## Team Members @@ -16,13 +15,17 @@ Our team consists of the following members, listed in the table below (the team -| Student ID | Student Name | GitHub Account (in Email) | -| ---------- | ------------ | ------------------------- | -| 123010245 | 张三 🚩 | sanzhang@gmail.com | -| 123010345 | 李四 | sili@outlook.com | -| 123030245 | 王五 | wuwang@foxmail.com | -| 123020245 | 陈六 | liuchen@qq.com | -| 123010249 | 朱九 | jiuzhu@163.com | +| Student ID | Student Name | GitHub Account (in Email) | GitHub Username | Actual Contribution | +| ---------- | ------------ | -------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| 120090609 | 刘恒睿 🚩 | 120090609@link.cuhk.edu.cn | [RichardRui9](https://github.com/RichardRui9) | Participate in coding `CommandInterpreter.java`; Multi-load expression; Present the third part| +| 120090860 | 李楠轩 | 120090860@link.cuhk.edu.cn | [George-Mac](https://github.com/George-Mac) | Basic: Help completing Table.
Enhanced: Case-Insensitive.
Presentation: Make
first part of PPT
and give a presentation of that part. | +| 120090495 | 张家荣 | 120090495@link.cuhk.edu.cn | [Yae-mikooo](https://github.com/Yae-mikooo) | Complete `ConditionClause`; Support `or` and `and` condition | +| 119010269 | 宋泽方 | 119010269@link.cuhk.edu.cn | [Song199010](https://github.com/Song199010) | Complete `Row.java`; Implement `order by` with single and multiple columns| +| 120090565 | 胡文涵 | 120090565@link.cuhk.edu.cn | [Clarice927](https://github.com/Clarice927) | Complete `Condition.java`| +| 119010216 | 路东竹渊 | 119010216@link.cuhk.edu.cn | [Tim-Lu-cuhksz](https://github.com/Tim-Lu-cuhksz) | Basic: Implement `selectStatement`, `selectClause` in `CommandInterpreter.java`
Enhanced: Multi-relation Join & Aggregate Functions
Presentation: Present the seond part | +| 120090771 | 邱纬纶 | 120090771@link.cuhk.edu.cn | [alanqwl](https://github.com/alanqwl) | Complete `Table.java`| +| 120090224 | 杨尚霖 | 120090224@link.cuhk.edu.cn | [UnitedSnakes](https://github.com/CSC3170-2022Fall/project-microhard/commits?author=UnitedSnakes) | Help complete `Table.java` and fix bugs related to duplicate rows| +| 120090470 | 李鹏 | 120090470@link.cuhk.edu.cn | [pengleee](https://github.com/pengleee) | Help complete `Table.java` and `select *` | ## Project Specification @@ -30,12 +33,89 @@ Our team consists of the following members, listed in the table below (the team After thorough discussion, our team made the choice and the specification information is listed below: -- Our option choice is: **Option 1** -- Our branch choice is: **Branch 1** -- The difficulty level is: **Normal** +- Our option choice is: **Option 3** -As for Option 2, our topic background specification can be found in [background-specification.md](background-specification.md). +## How to run our program + +### Use Makefile + +1. Go to the file Table.java and change the read path![1](https://github.com/CSC3170-2022Fall/project-microhard/blob/main/image/README/1.png) +2. Enter "cd proj1" in the terminal +3. Enter "make check" in the terminal![2](https://github.com/CSC3170-2022Fall/project-microhard/blob/main/image/README/2.png) + +### Run Main.java + +1. Go to the file Table.java and change the read path![3](https://github.com/CSC3170-2022Fall/project-microhard/blob/main/image/README/3.png) +2. Run the main.java file ## Project Abstract - \ No newline at end of file + + +Abstract – Microhard + +This CSC3170 project, based on a design from UCB CS61B, Fall 2014, requires our famous group Microhard to include a very simple query language to write a miniature relational database management system (DBMS). + +The prototype Java codes provided by UCB have initiated a skeleton for DBMS and the first step for Microhard is to fill in the missing Java codes in five prototype Java files. As of December 05, Microhard has finished this job and successfully implemented the miniature DBMS. This DBMS reads query language tokens from terminal input and then invokes appropriate Java functions to perform specific operations. + +Microhard then decides to add to the query language more syntaxes like aggregate functions (for more details please refer to Todo.md) and optimize the speed and efficiency of the DBMS. These updates will be released soon. + +Please do not hesitate to contact us if we can be of assistance. + +Microhard +December 05, 2022 + +## Program Design + +The project mainly consists of 7 Java files: `CommandInterpreter.java`, `Tokenizer.java`, `Database.java`, `Table.java`, `Row.java`, `Condition.java` and `Column.java`. + +`CommandInterpreter.java` uses `Tokenizer.java` to parse user commands, and passes appropriate tests. `Tokenizer.java` interacts with database or creates conditions. `Database.java` is the container for tables. `Table.java` is the brains of the project. It writes and reads from files, and also performs db operations when given commands by `CommandIntepreter.java`. `Condition.java` represents a where condition command in a select clause. `Row.java` represents a single row of data. `Column.java` acts as an index of a column in a list of rows. Besides, there are `Utils.java`, `DBException.java`, `Main.java` in the project. + +In *testing* folder, there are database files `blank.db`, `enrolled.db`, `schedule.db` and `students.db`. Tester Python files and sample input/output are also included. + +## Functionality Implementation + +### Basic Functions + +1. Load table from .db: It uses the `readTable()` method in `Table.java` to read the contents of db file and return as a table. +2. Omit comments in expressions +3. Basic *select from ... where* operation +4. Create table from select clause +5. Join two tables + +### Enhanced Functions + +1. Case-Insensitive to User's Input +2. Multiple-Relations Joint: Users can specify more than two tables after `from` statement in the query expressions. After multi-relation join operation, one can specify certain conditions to select rows from the joined table. The basic idea is that we iteratively invoke `table.select` from `Table.java` to join the first two relations, and join the obtained relation with the third relation. Finally, we will obtain a joined table with non-repeated columns from each separate relation, after which `ConditionClause` is applied. More details can be found in `CommandInterpreter.java`. +3. Select All: Users can use `select *` to see all the data from the table. A `for` loop is used to get all the column names of the table. Then, normal `select` by the total names is executed. +4. Condition “OR”: We support conditions with a mixture of `and` operation and `or` operation. +5. Results “Order by”: Users can reorder the result table in ascending/descending order based on the value(s) of one or more columns, in which `asc` or none for 'ascending' and `desc` for 'descending'. More details can be found in `selectStatement` method, `CommandInterpreter.java` file. +6. Multi-Load: We suppoer loading tables in one line, e.g.`load students, enrolled, schedule;` instead of `load students;\nload enrolled;\nload schedule;`. When facing errors, it will throw the first error that encountered. +7. Aggregate Function: Users can use `avg`, `count`, `min`, `max` to calculate the corresponding values. Note that only `count` supports non-numerical attribute. If a non-numerical attribute appear in the argument of `avg`, `min`, `max`, an error will occur. To support this functionality, `select_aux` is written to detect the presence of any aggregate function name. If an aggregate function exists, the pertinent value is computed and returned as a single-value table. + +Please visit documentation [here](TestCases.md) for more test details. + +## Difficulty Encountered & Solutions + +"order by" + +When implementing the enhanced function "order by", we changed HashMap into ArrayList. This update led to the bug of printing duplicated rows. The solution was to use a Hashmap to record the printed rows so that the program prints each row exactly once. + +"_input.next()" + +We suggest trying to use "_input.nextIf()" rather than "_input.next()". Our experience is that the latter one usually leads to more bugs. + +"aggregate functions and group by" + +The combination of aggregate functions and "group by" syntax is powerful. We implemented aggregate functions "avg", "count", "min" and "max". We also tried to implement "group by" but failed. Implementing any of them can be easy, but combining them together can be quite hard. There are many reasons for this. The following content might provide some sense. + +* Assign _input_dup as an deep copy of _input. (This is implemented using Cloneable.) +* Use a while loop with _input_dup.nextIf() to record if there exists "group by" syntax. Use String gb_col to record the columns following "group by". If there is no "group by", gb_col = "". +* If gb_col = "", run the naive select functions. +* Else, iterate through the result table obtained after "select", "from" and "where" operations and divide it into ... + +## Presentation Video Link + +Please refer to [video link](https://cuhko365-my.sharepoint.com/:v:/g/personal/120090609_link_cuhk_edu_cn/Ed3DS1e8eoVPqPzizAdmyNQBEqQWCmFjDLqoRN7TOhw2nw?e=cVpTYw). + +Our presentation slides can be seen [here](Pre_PPT.pdf). diff --git a/TestCases.md b/TestCases.md new file mode 100644 index 0000000..c43476f --- /dev/null +++ b/TestCases.md @@ -0,0 +1,227 @@ +# Test Cases for Presentation + +This file includes all test cases that we would like to present in front of TA and Professor Clement. The test cases are divided into two parts: basic tests and enhanced tests. Basic tests are the one provided by UCB template, while enhanced tests comprise functionalities beyond the basic part. + +For those who have made an effort to develop the enhanced part, please fill in some *extreme cases* in the following section that you think it is worth presenting and can make our work more competitive. ***Please also indicate if any of the test cases are redundant and can be removed or merged. We just find that there are two many test cases and the time is limited!!!*** + +*** +## UCB Basic Tests + +### Test 1: *load* and *print* +``` +load students; +load enrolled; +load schedule; + +print schedule; + +==========result=========== +Contents of schedule: + 21228 61A EECS 2-3MWF 1 Pimentel F 2003 + 21231 61A EECS 1-2MWF 1 Pimentel S 2004 + 21229 61B EECS 11-12MWF 155 Dwinelle F 2003 + 21232 61B EECS 1-2MWF 2050 VLSB S 2004 + 21103 54 Math 1-2MWF 2050 VLSB F 2003 + 21105 54 Math 1-2MWF 1 Pimentel S 2004 + 21001 1A English 9-10MWF 2301 Tolman F 2003 + 21005 1A English 230-5TuTh 130 Wheeler S 2004 +``` +### Test 2: *basic select from where* +``` +/* Comments */ +select SID, Firstname +from students +where Lastname = ’Chan’; + +==========result=========== +Search results: + 102 Valerie + 106 Yangfan +``` +### Test 3: *select from two tables* +``` +select Firstname, Lastname, Grade +from students, enrolled +where CCN = ’21001’; + +==========result=========== +Search results: + Jason Knowles B + Valerie Chan B+ + Shana Brown B+ + Yangfan Chan B +``` +### Test 4: *create table* +``` +/*Create table*/ +create table enrolled2 as select SID +from enrolled, schedule +where Dept = ’EECS’ and Num = ’61A’; +/*Print created table*/ +print enrolled2; +/*Select from created table*/ +select Firstname, Lastname +from students, enrolled2; + +==========result=========== +Contents of enrolled2: + 101 + 102 + 104 + 105 + 106 + +Search results: + Jason Knowles + Valerie Chan + Thomas Armstrong + Shana Brown + Yangfan Chan +``` + +*** +## Enhanced Tests + +### Test 5: *Case Insensitivity* +``` +SELecT SID, Firstname +FROm students +whERE SID > '103'; + +==========result=========== +Search results: + 104 Thomas + 105 Shana + 106 Yangfan +``` + +### Test 6: *select from multiple tables* +``` +select SID, Grade, Year +from students, enrolled, schedule +where Year = '2003'; + +==========result=========== +Search results: + 101 B 2003 + 101 B 2003 + 102 A 2003 + 102 B+ 2003 + 104 A- 2003 + 104 B+ 2003 + 105 A 2003 + 105 B+ 2003 + 106 A 2003 + 106 B 2003 +``` + +### Test 7: *select all* +``` +select * +from students, enrolled, schedule +where Year = '2003' and Grade = 'B'; + +==========result=========== +Search results: + 101 Knowles Jason F 2003 EECS 7000 21228 B 61A EECS 2-3MWF 1 Pimentel F 2003 + 101 Knowles Jason F 2003 EECS 7000 21001 B 1A English 9-10MWF 2301 Tolman F 2003 + 106 Chan Yangfan F 2003 LSUnd 9999 21001 B 1A English 9-10MWF 2301 Tolman F 2003 +``` + +### Test 8: *order by one column (ascending order)* +``` +select SID, Firstname, Lastname, Grade +from students, enrolled +order by Lastname; + +==========result=========== +Search results: + 104 Thomas Armstrong A- + 104 Thomas Armstrong B+ + 104 Thomas Armstrong A- + 104 Thomas Armstrong A- + 105 Shana Brown A + 105 Shana Brown B+ + 102 Valerie Chan A + 102 Valerie Chan A- + 102 Valerie Chan A + 102 Valerie Chan B+ + 106 Yangfan Chan A + 106 Yangfan Chan B + 106 Yangfan Chan A + 101 Jason Knowles B + 101 Jason Knowles B+ + 101 Jason Knowles A- + 101 Jason Knowles B + 103 Jonathan Xavier B+ + 103 Jonathan Xavier B+ +``` + +### Test 9: *order by multiple columns (descending order)* +``` +select SID, Fistname, Grade +from students, enrolled +where SID > '103' and Grade = 'A' +order by SID, Firstname desc; + +==========result=========== +Search results: + 106 Yangfan A + 106 Yangfan A + 105 Shana A +``` + +### Test 10: *condition or & and* +``` +select SID, Fistname, Grade +from students, enrolled, schedule +where Dept = 'EECS' and Year = '2003' or SID = '103'; + +==========result=========== +Search results: + 101 Jason B + 102 Valerie A + 103 Jonathan B+ + 103 Jonathan B+ + 104 Thomas A- + 104 Thomas B+ + 105 Shana A +``` + +### Test 11: *aggregate function* +``` +/*avg*/ +select avg(Scholarship) +from students +where YearEnter = '2003'; + +select SID, avg(Scholarship) from students; + +/*count*/ +select count(*) +from students; + +/*min*/ +select min(SID) from students; +select min(Lastname) from students; + +/*max*/ +select max(Scholarship) from students; + +==========result=========== +Search results: + 8416 + +Error: Aggregate function without Group By does not allow other columns to exist! + +Search results: + 6 + +Search results: + 101 + +Error: There seem to be problems with the column. Try select another one! + +Search results: + 11250 +``` diff --git a/Todo.md b/Todo.md new file mode 100644 index 0000000..9c76853 --- /dev/null +++ b/Todo.md @@ -0,0 +1,18 @@ +# Planned Functions + +## Realized Functions + +- [x] **Ignored case sensitivity of commands** +- [x] **Realized *SELECT* Clause** +- [x] **Realized *FROM* Clause** +- [x] **Realized *WHERE* Clause** +- [x] **Realized *FROM* Clause for more than two tables** +- [X] **Realizing *SELECT \** (select all) command** +- [X] **Realizing *ORDER BY* Clause** +- [X] **Realizing *Aggregate Function* including *avg*, *min*, *max*, *count*** + +## Functions in Process + +- [ ] **Realizing *GROUP BY* Clause** +- [ ] **Optimizing the error processing** +- [ ] **Optimizing the speed/efficiency of data handling** \ No newline at end of file diff --git a/image/.DS_Store b/image/.DS_Store new file mode 100644 index 0000000..14d3b50 Binary files /dev/null and b/image/.DS_Store differ diff --git a/image/README/1.png b/image/README/1.png new file mode 100644 index 0000000..da7d788 Binary files /dev/null and b/image/README/1.png differ diff --git a/image/README/1672236517154.png b/image/README/1672236517154.png new file mode 100644 index 0000000..e9b43d9 Binary files /dev/null and b/image/README/1672236517154.png differ diff --git a/image/README/2.png b/image/README/2.png new file mode 100644 index 0000000..d70e3ef Binary files /dev/null and b/image/README/2.png differ diff --git a/image/README/3.png b/image/README/3.png new file mode 100644 index 0000000..95b51c1 Binary files /dev/null and b/image/README/3.png differ diff --git a/proj1/.DS_Store b/proj1/.DS_Store new file mode 100644 index 0000000..7a28afc Binary files /dev/null and b/proj1/.DS_Store differ diff --git a/proj1/Makefile b/proj1/Makefile new file mode 100644 index 0000000..01704e9 --- /dev/null +++ b/proj1/Makefile @@ -0,0 +1,41 @@ +# This makefile is defined to give you the following targets: +# +# default: The default target: Compiles the program in package db61b. +# style: Run our style checker on the project source files. Requires that +# the source files compile. +# check: Compiles the db61b package, if needed, and then performs the +# tests described in testing/Makefile. +# clean: Remove regeneratable files (such as .class files) produced by +# other targets and Emacs backup files. +# +# In other words, type 'make' to compile everything; 'make check' to +# compile and test everything, and 'make clean' to clean things up. +# +# You can use this file without understanding most of it, of course, but +# I strongly recommend that you try to figure it out, and where you cannot, +# that you ask questions. The Lab Reader contains documentation. + +# Name of package containing main procedure +PACKAGE = db61b + +STYLEPROG = style61b + +# Targets that don't correspond to files, but are to be treated as commands. +.PHONY: default check clean style + +default: + $(MAKE) -C $(PACKAGE) default + +check: default + $(MAKE) -C testing check + +style: + $(MAKE) -C $(PACKAGE) STYLEPROG=$(STYLEPROG) style + +# 'make clean' will clean up stuff you can reconstruct. +clean: + $(RM) *~ + $(MAKE) -C $(PACKAGE) clean + $(MAKE) -C testing clean + + diff --git a/proj1/ReadMe b/proj1/ReadMe new file mode 100644 index 0000000..b2215fe --- /dev/null +++ b/proj1/ReadMe @@ -0,0 +1,57 @@ +This directory contains a skeleton for CS61B Project 1. + +CONTENTS: + +ReadMe This file. + +Makefile A makefile (for the 'make' program) that will compile + your files and run tests. You must turn in a Makefile, + 'make' must compile all your files, and + 'make check' must perform all your tests. Currently, + this makefile is set up to do just that with our + skeleton files. Be sure to keep it up to date. + +staff-version If we update the skeleton, this file will contain a + unique version id indicating what version of the + skeleton is currently in use. + + +db61b A subdirectory containing skeletons for the + db61b package: + + Main.java The main program---entry point to the db61b system. + Database.java Abstraction for an entire collection of tables. + Table.java Abstraction for one table. + Row.java Abstraction for one row of a table. + +testing Subdirectory holding files for integration testing: + + Makefile A makefile containing instructions for performing + tests on your project. + + students.db, enrolled.db, courses.db + Sample database tables from the project handout. + + test1.in, test2.in Input files for testing. The makefile will respond + to 'make check' by running these files through your + program, filtering the output through + testing/test-filter, and comparing the results with + the corresponding .out files. You should add more + files to the list in Makefile. + REMINDER: These are samples only. They DON'T + constitute adequate tests. + + test1.out, test2.out Output that is supposed to result from test1.in + and test2.in, with the first line, all prompts, + and all blank lines removed (which is what + test-filter does). + + testing.py A Python 3 module containing a framework for integration + testing. Used by tester.py. + + tester.py A Python 3 program that tests your project. It runs + your program with each .in file, comparing the output + with the corresponding .out file and producing a report + of the result. + + diff --git a/proj1/db61b/.DS_Store b/proj1/db61b/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/proj1/db61b/.DS_Store differ diff --git a/proj1/db61b/Column.java b/proj1/db61b/Column.java new file mode 100644 index 0000000..b5d1354 --- /dev/null +++ b/proj1/db61b/Column.java @@ -0,0 +1,48 @@ +package db61b; + +import static db61b.Utils.*; + +/** A Column is effectively an index of a specific, named column + * in a list of Rows. Given a sequence of [t0,...,tn] of Tables, + * and a column name, c, a Column can retrieve the value of that column of + * the first ti that contains it from an array of rows [r0,...,rn], + * where each ri comes from ti. + * @author P. N. Hilfinger +*/ +class Column { + /** Selects column named NAME from a row of one of the given TABLES. */ + Column(String name, Table... tables) { + _name = name; + for (_table = 0; _table < tables.length; _table += 1) { + _column = tables[_table].findColumn(name); + if (_column != -1) { + return; + } + } + throw error("unknown column: %s", name); + } + + /** Return my name. */ + String getName() { + return _name; + } + + + /** Returns the value of this Column from ROWS[_table]. Assumes that + * ROWS[_table] is from the same table that was provided to the + * constructor of this Column. More generally, this method is intended + * such that ROWS[k] coresponds to the kth table that was supplied to + * the constructor for this method. + * + * Despite the fact that many rows are passed to this function, this + * function returns only one value. + */ + String getFrom(Row... rows) { + return rows[_table].get(_column); + } + + /** Column name denoted by THIS. */ + private String _name; + /** Index of the table and column from which to extract a value. */ + private int _table, _column; +} diff --git a/proj1/db61b/CommandInterpreter.java b/proj1/db61b/CommandInterpreter.java new file mode 100644 index 0000000..6a7c95e --- /dev/null +++ b/proj1/db61b/CommandInterpreter.java @@ -0,0 +1,627 @@ +// This is a SUGGESTED skeleton for a class that parses and executes database +// statements. Be sure to read the STRATEGY section, and ask us if you have any +// questions about it. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution adds or changes about 50 +// lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Scanner; +import java.util.Collections; + +import static db61b.Utils.*; +import static db61b.Tokenizer.*; + +/** An object that reads and interprets a sequence of commands from an + * input source. + * @author */ +class CommandInterpreter { + + /* STRATEGY. + * + * This interpreter parses commands using a technique called + * "recursive descent." The idea is simple: we convert the BNF grammar, + * as given in the specification document, into a program. + * + * First, we break up the input into "tokens": strings that correspond + * to the "base case" symbols used in the BNF grammar. These are + * keywords, such as "select" or "create"; punctuation and relation + * symbols such as ";", ",", ">="; and other names (of columns or tables). + * All whitespace and comments get discarded in this process, so that the + * rest of the program can deal just with things mentioned in the BNF. + * The class Tokenizer performs this breaking-up task, known as + * "tokenizing" or "lexical analysis." + * + * The rest of the parser consists of a set of functions that call each + * other (possibly recursively, although that isn't needed for this + * particular grammar) to operate on the sequence of tokens, one function + * for each BNF rule. Consider a rule such as + * + * ::= create table
; + * + * We can treat this as a definition for a function named (say) + * createStatement. The purpose of this function is to consume the + * tokens for one create statement from the remaining token sequence, + * to perform the required actions, and to return the resulting value, + * if any (a create statement has no value, just side-effects, but a + * select clause is supposed to produce a table, according to the spec.) + * + * The body of createStatement is dictated by the right-hand side of the + * rule. For each token (like create), we check that the next item in + * the token stream is "create" (and report an error otherwise), and then + * advance to the next token. For a metavariable, like
, + * we consume the tokens for
, and do whatever is + * appropriate with the resulting value. We do so by calling the + * tableDefinition function, which is constructed (as is createStatement) + * to do exactly this. + * + * Thus, the body of createStatement would look like this (_input is + * the sequence of tokens): + * + * _input.next("create"); + * _input.next("table"); + * String name = name(); + * Table table = tableDefinition(); + * _input.next(";"); + * + * plus other code that operates on name and table to perform the function + * of the create statement. The .next method of Tokenizer is set up to + * throw an exception (DBException) if the next token does not match its + * argument. Thus, any syntax error will cause an exception, which your + * program can catch to do error reporting. + * + * This leaves the issue of what to do with rules that have alternatives + * (the "|" symbol in the BNF grammar). Fortunately, our grammar has + * been written with this problem in mind. When there are multiple + * alternatives, you can always tell which to pick based on the next + * unconsumed token. For example,
has two alternative + * right-hand sides, one of which starts with "(", and one with "as". + * So all you have to do is test: + * + * if (_input.nextIs("(")) { + * _input.next("("); + * // code to process ", )" + * } else { + * // code to process "as " + * } + * + * combining the calls to .nextIs and .next. + * + * You can handle the list of s in the preceding in a number + * of ways, but personally, I suggest a simple loop: + * + * ... = columnName(); + * while (_input.nextIs(",")) { + * _input.next(","); + * ... = columnName(); + * } + * + * or if you prefer even greater concision: + * + * ... = columnName(); + * while (_input.nextIf(",")) { + * ... = columnName(); + * } + * + * (You'll have to figure out what do with the names you accumulate, of + * course). + */ + + + /** A new CommandInterpreter executing commands read from INP, writing + * prompts on PROMPTER, if it is non-null. */ + + CommandInterpreter(Scanner inp, PrintStream prompter) { + _input = new Tokenizer(inp, prompter); + _database = new Database(); + } + + /** Parse and execute one statement from the token stream. Return true + * iff the command is something other than quit or exit. */ + boolean statement() { + switch (_input.peek().toLowerCase()) { + case "create": + createStatement(); + break; + case "load": + loadStatement(); + break; + case "exit": case "quit": + exitStatement(); + return false; + case "*EOF*": + return false; + case "insert": + insertStatement(); + break; + case "print": + printStatement(); + break; + case "select": + selectStatement(); + break; + case "store": + storeStatement(); + break; + default: + throw error("unrecognizable command"); + } + return true; + } + + /** Parse and execute a create statement from the token stream. */ + void createStatement() { + _input.next("create"); + _input.next("table"); + String name = name(); + Table table = tableDefinition(); + _input.next(";"); + _database.put(name, table); + + } + + /** Parse and execute an exit or quit statement. Actually does nothing + * except check syntax, since statement() handles the actual exiting. */ + void exitStatement() { + if (!_input.nextIf("quit")) { + _input.next("exit"); + } + _input.next(";"); + } + + /** Parse and execute an insert statement from the token stream. */ + void insertStatement() { + _input.next("insert"); + _input.next("into"); + Table table = tableName(); + _input.next("values"); + + ArrayList values = new ArrayList<>(); + values.add(literal()); + while (_input.nextIf(",")) { + values.add(literal()); + } + + table.add(new Row(values.toArray(new String[values.size()]))); + _input.next(";"); + } + +/* Parse and execute a load statement from the token stream. */ + void loadStatement() { + _input.next("load"); + String name = name(); + ArrayList loadlst = new ArrayList(); + while (_input.nextIf(",")){ + loadlst.add(name()); + } + // _input.next(";"); + Table content = Table.readTable(name); + // System.out.printf("Loaded %s.db%n", name); + _database.put(name, content); + System.out.printf("Loaded %s.db%n", name); + while (!(loadlst.isEmpty())){ + Table content1 = Table.readTable(loadlst.get(0)); + _database.put(loadlst.get(0), content1); + System.out.printf("Loaded %s.db%n", loadlst.get(0)); + loadlst.remove(0); + // } + } + _input.next(";"); + loadlst.clear(); + // _input.next(";"); + // Table content = Table.readTable(name); + // _database.put(name, content); + // System.out.printf("Loaded %s.db%n", name); + } + + + /** Parse and execute a store statement from the token stream. */ + void storeStatement() { + _input.next("store"); + String name = _input.peek(); + Table table = tableName(); + table.writeTable(name); + System.out.printf("Stored %s.db%n", name); + _input.next(";"); + } + + /** Parse and execute a print statement from the token stream. */ + void printStatement() { + _input.next("print"); + String name = _input.peek(); + Table table = tableName(); + _input.next(";"); + System.out.printf("Contents of %s:%n", name); + table.print(); + } + + /** Parse and execute a select statement from the token stream. */ + void selectStatement() { + Table table = selectClause(); + ArrayList columnNames = new ArrayList(); + if (_input.nextIf("order")) { + _input.next("by"); + columnNames.add(columnName()); + while (_input.nextIf(",")) { + columnNames.add(columnName()); + } + if (_input.nextIf("desc")) { + table = orderTable_desc(table, columnNames); + } + else if (_input.nextIf("asc")) { + table = orderTable_asc(table, columnNames); + } + else { + table = orderTable_asc(table, columnNames); + } + } + System.out.println("Search results:"); + table.print(); + _input.next(";"); + } + + /* Reorder the table in ascending order. If two rows have the same value, + it will keep the original sequence. */ + Table orderTable_asc(Table table, ArrayList columnNames) { + ArrayList orderedlist = new ArrayList(); + if (columnNames.size() == 1) { + orderedlist = table.selectcolumn(columnNames.get(0)); + Collections.sort(orderedlist); + table = table.sort(orderedlist, columnNames.get(0)); + } + else { + orderedlist = table.selectcolumns(columnNames); + Collections.sort(orderedlist); + table = table.sort(orderedlist, columnNames); + } + return table; + } + + /* Reorder the table in descending order. If two rows have the same value, + it will keep the original sequence. */ + Table orderTable_desc(Table table, ArrayList columnNames) { + ArrayList orderedlist = new ArrayList(); + if (columnNames.size() == 1) { + orderedlist = table.selectcolumn(columnNames.get(0)); + Collections.sort(orderedlist); + Collections.reverse(orderedlist); + table = table.sort(orderedlist, columnNames.get(0)); + } + else { + orderedlist = table.selectcolumns(columnNames); + Collections.sort(orderedlist); + Collections.reverse(orderedlist); + table = table.sort(orderedlist, columnNames); + } + return table; + } + + /** Parse and execute a table definition, returning the specified + * table. */ + Table tableDefinition() { + Table table; + if (_input.nextIf("(")) { + ArrayList lst = new ArrayList(); + lst.add(columnName()); + while (_input.nextIf(",")) { + lst.add(columnName()); + } + _input.next(")"); + table = new Table(lst); + } else { + _input.next("as"); + table = selectClause(); + } + return table; + } + + String gb_exist(Tokenizer input) { + while (! input.nextIs(";")) { + if (input.next() == "group" && input.next("by") == "by") { + return input.next(); + } + } + return ""; + } + + /** Parse and execute a select clause from the token stream, returning the + * resulting table. */ + Table selectClause() { + _input.next("select"); + ArrayList col = new ArrayList(); + ArrayList
tab = new ArrayList
(); + // Tokenizer _input_dup = (Tokenizer) _input.clone(); + // System.out.println(_input_dup.nextIf("*")); + // String gb = gb_exist(_input_dup); /* which one column to be grouped by */ + // System.out.println(gb.isEmpty()); + + // if (gb.isEmpty()) { + /* Select all */ + // System.out.println("222"); + // System.out.println(_input.nextIs(";")); + + if (_input.nextIf("*")) { + // System.out.println("333"); + _input.next("from"); + /* We need to get all the columns from the required tables.*/ + Table tab1 = tableName(); + tab.add(tab1); + while (_input.nextIf(",")) { + Table tab2 = tableName(); + tab.add(tab2); + } + for (int i = 0; i < tab.size(); i++) { + // System.out.println(tab.get(i)); + Table tab_item = tab.get(i); + ArrayList col_list = getAllCol(tab_item); + col.addAll(col_list); + } + /* Eliminate the duplicate column names */ + ArrayList listTemp = new ArrayList(); + for(int j=0; j con_aux = new ArrayList(); + Table t1 = tab.get(0); Table t2 = tab.get(1); + joined_table = t1.select(t2, rm_dup(getAllCol(t1), getAllCol(t2)), con_aux); + for (int i = 2; i < tab.size(); i++) { + joined_table = joined_table.select(tab.get(i), + rm_dup(getAllCol(joined_table), getAllCol(tab.get(i))), con_aux); + } + } + ConditionClause con = new ConditionClause(); + if (_input.nextIf("where")) { + con = conditionClause(joined_table); + } + + String aggregate = null; + if (col.size() == 1) { + aggregate = get_aggregate(col.get(0)); + } + if (aggregate == null) { + // System.out.println("aggregate is null"); + // System.out.println("111"); + // joined_table.print(); + // System.out.println("\n\n"); + + return joined_table.select(col, con.conList, con.operations); + } else { /* If not null, col must have only one item */ + // System.out.println("aggregate is not null"); + String agg_col = get_aggregate_col(col.get(0)); + if (agg_col.equals("*") && aggregate.equals("count")) { + agg_col = joined_table.getTitle(0); + } else if (agg_col.equals("*") && !aggregate.equals("count")) { + throw error("Aggregate function imcompatible with attribute"); + } + col.set(0, agg_col); + Table t = joined_table.select(col, con.conList, con.operations); + + // System.out.println("222"); + // t.print(); + // System.out.println("\n\n"); + return t.aggregate_(aggregate); + } + // return null; + // return joined_table.select(col, con.conList, con.operations); + // } else { /* group by */ + // ArrayList gb_col = new ArrayList(); + // for (Row r : _rows) { + // } + // Table joined_table = null; + // return joined_table; + // } + + } + + ArrayList select_aux(ArrayList col) { + String aggregate_func = null; + if (_input.nextIs("avg")) { + aggregate_func = "avg"; + } else if (_input.nextIs("count")) { + aggregate_func = "count"; + } else if (_input.nextIs("min")) { + aggregate_func = "min"; + } else if (_input.nextIs("max")) { + aggregate_func = "max"; + } + if (aggregate_func != null) { + _input.next(aggregate_func); + _input.next("("); + if (_input.nextIf("*")) { + col.add(aggregate_func + "(*)"); + } else { + col.add(aggregate_func + "(" + columnName() + ")"); + } + _input.next(")"); + } else { + col.add(columnName()); + } + return col; + } + + /* So far we only support one aggregate function with no other columns + * Return true if not compatible. Then an error will be generated + */ + Boolean test_compatibility (ArrayList col) { + int flag = 0; + for (int i = 0; i < col.size(); i++) { + if (get_aggregate(col.get(i)) != null) { + flag = 1; + break; + } + } + return (col.size() > 1) && (flag == 1); + } + + /* get the name of aggregate function */ + String get_aggregate (String s) { + int i = s.indexOf("("); + if (i == -1) { + return null; + } else { + return s.substring(0, i); + } + } + + String get_aggregate_col (String s) { + return s.substring(s.indexOf("(")+1, s.indexOf(")")); + } + + /* Combine two array lists of String type, then remove any duplicated items in the list */ + ArrayList rm_dup(ArrayList l1, ArrayList l2) { + ArrayList result = new ArrayList(); + result.addAll(l1); + for (int i = 0; i < l2.size(); i++) { + if (!l1.contains(l2.get(i))) { + result.add(l2.get(i)); + } + } + return result; + } + + /* Given a table a, return its column names as an Arraylist */ + ArrayList getAllCol(Table t) { + ArrayList result = new ArrayList(); + for (int i = 0; i < t.columns(); i++) { + result.add(t.getTitle(i)); + } + return result; + } + + /** Parse and return a valid name (identifier) from the token stream. */ + String name() { + return _input.next(Tokenizer.IDENTIFIER); + } + + /** Parse and return a valid column name from the token stream. Column + * names are simply names; we use a different method name to clarify + * the intent of the code. */ + String columnName() { + return name(); + } + + /** Parse a valid table name from the token stream, and return the Table + * that it designates, which must be loaded. */ + Table tableName() { + String name = name(); + Table table = _database.get(name); + if (table == null) { + throw error("unknown table: %s", name); + } + return table; + } + + /** Parse a literal and return the string it represents (i.e., without + * single quotes). */ + String literal() { + String lit = _input.next(Tokenizer.LITERAL); + return lit.substring(1, lit.length() - 1).trim(); + } + + /** Parse and return a list of Conditions that apply to TABLES from the + * token stream. This denotes the conjunction (`and') zero + * or more Conditions. */ + public class ConditionClause{ + ArrayList conList; + String operations; + public ConditionClause() { + this.conList = null; + this.operations = ""; + } + public ConditionClause(ArrayList conList, String operations) { + this.conList = conList; + this.operations = operations; + } + } + + ConditionClause conditionClause(Table... tables) { + ArrayList conList = new ArrayList(); + conList.add(condition(tables)); + StringBuffer operation = new StringBuffer(""); + while (_input.nextIs("and") || _input.nextIs("or")){ + if (_input.nextIf("and")) + operation.append("0"); + if (_input.nextIf("or")) + operation.append("1"); + conList.add(condition(tables)); + } + return new ConditionClause(conList, operation.toString()); + } + + /** Parse and return a Condition that applies to TABLES from the + * token stream. */ + Condition condition(Table... tables) { + Column leftCol = new Column(columnName(), tables); + String relation = _input.next(RELATION); + if (_input.nextIs(LITERAL)){ + return new Condition(leftCol, relation, literal()); + } + else{ + Column rightCol = new Column(columnName(), tables); + return new Condition(leftCol, relation, rightCol); + } + + } + + /** Advance the input past the next semicolon. */ + void skipCommand() { + while (true) { + try { + while (!_input.nextIf(";") && !_input.nextIf("*EOF*")) { + _input.next(); + } + return; + } catch (DBException excp) { + /* No action */ + } + } + } + + /** The command input source. */ + private Tokenizer _input; + /** Database containing all tables. */ + private Database _database; +} diff --git a/proj1/db61b/Condition.java b/proj1/db61b/Condition.java new file mode 100644 index 0000000..dc15582 --- /dev/null +++ b/proj1/db61b/Condition.java @@ -0,0 +1,102 @@ +// This is a SUGGESTED skeleton for a class that describes a single +// Condition (such as CCN = '99776'). You can throw this away if you +// want, but it is a good idea to try to understand it first. +// Our solution changes or adds about 30 lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.util.List; + +/** Represents a single 'where' condition in a 'select' command. + * @author */ +class Condition { + + /** A Condition representing COL1 RELATION COL2, where COL1 and COL2 + * are column designators. and RELATION is one of the + * strings "<", ">", "<=", ">=", "=", or "!=". */ + Condition(Column col1, String relation, Column col2) { + _col1 = col1; + _col2 = col2; + _relation = relation; + } + + /** A Condition representing COL1 RELATION 'VAL2', where COL1 is + * a column designator, VAL2 is a literal value (without the + * quotes), and RELATION is one of the strings "<", ">", "<=", + * ">=", "=", or "!=". + */ + Condition(Column col1, String relation, String val2) { + this(col1, relation, (Column) null); + _val2 = val2; + } + + /** Assuming that ROWS are rows from the respective tables from which + * my columns are selected, returns the result of performing the test I + * denote. */ + boolean test(Row... rows) { + String value1 = _col1.getFrom(rows); + String value2; + if (_val2 == null) + value2 = _col2.getFrom(rows); + else + value2 = _val2; + int compare = value1.compareTo(value2); + if (compare == 0) + return (_relation.equals("=") || _relation.equals(">=") || _relation.equals("<=")); + else if (compare > 0) + return (_relation.equals(">") || _relation.equals(">=") || _relation.equals("!=")); + else if (compare < 0) + return (_relation.equals("<") || _relation.equals("<=") || _relation.equals("!=")); + else + return false; + } + + /** AND case: Return true iff ROWS satisfies all CONDITIONS. */ + static boolean test(List conditions, String operations, Row... rows) { + /*Single case + if (operations.charAt(0) == '0') return testAnd(conditions, rows); + if (operations.charAt(0) == '1') return testOr(conditions, rows); + */ + //Complex case + int n = conditions.size(); + if (n == 0) return true; + boolean result = conditions.get(0).test(rows); + for (int i = 0; i < n-1; i++){ + if (operations.charAt(i) == '1'){ + if (result) return true; + else result = conditions.get(i+1).test(rows); + } + else{ + result = result && conditions.get(i+1).test(rows); + } + } + return result; + } + + /* All AND/OR case: Return false iff ROWs satisfies any one CONDITION. + static boolean testAnd(List conditions, Row... rows) { + for (Condition cond : conditions) { + if (!cond.test(rows)) { + return false; + } + } + return true; + } + static boolean testOr(List conditions, Row... rows) { + for (Condition cond : conditions) { + if (cond.test(rows)) { + return true; + } + } + return false; + } + */ + /** The operands of this condition. _col2 is null if the second operand + * is a literal. */ + private Column _col1, _col2; + /** Second operand, if literal (otherwise null). */ + private String _val2; + private String _relation; +} diff --git a/proj1/db61b/DBException.java b/proj1/db61b/DBException.java new file mode 100644 index 0000000..aea27ed --- /dev/null +++ b/proj1/db61b/DBException.java @@ -0,0 +1,14 @@ +package db61b; + +/** Indicates some kind of user error. + * @author P. N. Hilfinger */ +class DBException extends RuntimeException { + /** A new exception without message. */ + public DBException() { + } + + /** A new exception with message MSG. */ + public DBException(String msg) { + super(msg); + } +} diff --git a/proj1/db61b/Database.java b/proj1/db61b/Database.java new file mode 100644 index 0000000..fbe126a --- /dev/null +++ b/proj1/db61b/Database.java @@ -0,0 +1,44 @@ +// This is a SUGGESTED skeleton for a class that contains the Tables your +// program manipulates. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution changes about 6 +// lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +// FILL IN (WITH IMPORTS)? +import java.util.*; + +/** A collection of Tables, indexed by name. + * @author */ +class Database { + /** An empty database. */ + public Database() { + _database = new HashMap(); // FILL IN + } + + /** Return the Table whose name is NAME stored in this database, or null + * if there is no such table. */ + public Table get(String name) { + // return _database.get(name); // REPLACE WITH SOLUTION + for (Map.Entry entry : _database.entrySet()) { + if (entry.getKey().toLowerCase().equals(name.toLowerCase())) { + return _database.get(entry.getKey()); + } + } + return null; + } + + /** Set or replace the table named NAME in THIS to TABLE. TABLE and + * NAME must not be null, and NAME must be a valid name for a table. */ + public void put(String name, Table table) { + if (name == null || table == null) { + throw new IllegalArgumentException("null argument"); + } + _database.put(name, table); + } + + // FILL IN? + private HashMap _database; +} diff --git a/proj1/db61b/Main.java b/proj1/db61b/Main.java new file mode 100644 index 0000000..7ec0456 --- /dev/null +++ b/proj1/db61b/Main.java @@ -0,0 +1,36 @@ +package db61b; + +import java.util.Scanner; + +/** The main program for db61b. + * @author P. N. Hilfinger + */ +public class Main { + + /** Version designation for this program. */ + private static final String VERSION = "2.4"; + + /** Starting with an empty database, read and execute commands from + * System.in until receiving a 'quit' ('exit') command or until + * reaching the end of input. */ + public static void main(String[] unused) { + System.out.printf("Microhard Database System. Version %s.%n", VERSION); + + Scanner input = new Scanner(System.in); + CommandInterpreter interpreter = + new CommandInterpreter(input, System.out); + + while (true) { + try { + if (!interpreter.statement()) { + break; + } + } catch (DBException e) { + System.out.printf("Error: %s%n", e.getMessage()); + interpreter.skipCommand(); + } + } + } + +} + diff --git a/proj1/db61b/Makefile b/proj1/db61b/Makefile new file mode 100644 index 0000000..59b6b52 --- /dev/null +++ b/proj1/db61b/Makefile @@ -0,0 +1,52 @@ +# This makefile is defined to give you the following targets: +# +# default: The default target: Compiles $(PROG) and whatever it +# depends on. +# style: Run our style checker on the project source files. Requires that +# the source files compile. +# check: Compile $(PROG), if needed, and then for each file, F.in, in +# directory testing, use F.in as input to "java $(MAIN_CLASS)" and +# compare the output to the contents of the file names F.out. +# Report discrepencies. +# clean: Remove all the .class files produced by java compilation, +# all Emacs backup files, and testing output files. +# +# In other words, type 'gmake' to compile everything; 'gmake check' to +# compile and test everything, and 'gmake clean' to clean things up. +# +# You can use this file without understanding most of it, of course, but +# I strongly recommend that you try to figure it out, and where you cannot, +# that you ask questions. The Lab Reader contains documentation. + +.PHONY: default check clean style + +STYLEPROG = style61b + +JFLAGS = -g -Xlint:unchecked + +# All .java files in this directory. +SRCS := $(wildcard *.java) + +# As a convenience, you can compile a single Java file X.java in this directory +# with 'make X.class' +%.class: %.java + javac $(JFLAGS) -cp ..:$(CLASSPATH) $< + +# First, and therefore default, target. +default: sentinel + +style: default + $(STYLEPROG) $(SRCS) + +check: + $(MAKE) -C .. check + +# 'make clean' will clean up stuff you can reconstruct. +clean: + $(RM) *~ *.class sentinel + +### DEPENDENCIES ### + +sentinel: $(SRCS) + javac $(JFLAGS) -cp ..:$(CLASSPATH) $(SRCS) + touch sentinel diff --git a/proj1/db61b/Row.java b/proj1/db61b/Row.java new file mode 100644 index 0000000..6d01ce1 --- /dev/null +++ b/proj1/db61b/Row.java @@ -0,0 +1,89 @@ +package db61b; + +import java.util.Arrays; +import java.util.List; +import static db61b.Utils.*; + +/** A single row of a database. + * @author + */ +class Row { + /** A Row whose column values are DATA. The array DATA must not be altered + * subsequently. */ + Row(String[] data) { + _data = data; + } + + /** Given M COLUMNS that were created from a sequence of Tables + * [t0,...,tn] as well as ROWS [r0,...,rn] that were drawn from those + * same tables [t0,...,tn], constructs a new Row containing M values, + * where the ith value of this new Row is taken from the location given + * by the ith COLUMN (for each i from 0 to M-1). + * + * More specifically, if _table is the Table number corresponding to + * COLUMN i, then the ith value of the newly created Row should come from + * ROWS[_table]. + * + * Even more specifically, the ith value of the newly created Row should + * be equal to ROWS[_table].get(_column), where _column is the column + * number in ROWS[_table] corresponding to COLUMN i. + * + * There is a method in the Column class that you'll need to use, see + * {@link db61b.Column#getFrom}). you're wondering why this looks like a + * potentially clickable link it is! Just not in source. You might + * consider converting this spec to HTML using the Javadoc command. + */ + Row(List columns, Row... rows) { + int M = columns.size(); + String[] new_row = new String[M]; + for (int i = 0; i < M; i++) { + new_row[i] = columns.get(i).getFrom(rows); + } + _data = new_row; + } + + /** Return my number of columns. */ + int size() { + return _data.length; + } + + /** Return the value of my Kth column. Requires that 0 <= K < size(). */ + String get(int k) { + if (k >= 0 && k < size()) { + return _data[k]; + } + else { + throw error("IndexOutOfBounds"); + } + } + + @Override + public boolean equals(Object obj) { + if (this.size() != ((Row) obj).size()) { + return false; + } + for (int i = 0; i < this.size(); i++) { + if (!(this.get(i).equals(((Row) obj).get(i)))) { + return false; + } + } + return true; + } + + /* NOTE: Whenever you override the .equals() method for a class, you + * should also override hashCode so as to insure that if + * two objects are supposed to be equal, they also return the same + * .hashCode() value (the converse need not be true: unequal objects MAY + * also return the same .hashCode()). The hash code is used by certain + * Java library classes to expedite searches (see Chapter 7 of Data + * Structures (Into Java)). */ + + @Override + public int hashCode() { + return Arrays.hashCode(_data); + } + + /** Contents of this row. */ + private String[] _data; +} + diff --git a/proj1/db61b/Table.java b/proj1/db61b/Table.java new file mode 100644 index 0000000..3632870 --- /dev/null +++ b/proj1/db61b/Table.java @@ -0,0 +1,461 @@ +// This is a SUGGESTED skeleton for a class that represents a single +// Table. You can throw this away if you want, but it is a good +// idea to try to understand it first. Our solution changes or adds +// about 100 lines in this skeleton. + +// Comments that start with "//" are intended to be removed from your +// solutions. +package db61b; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.HashMap; +import java.util.Map; + +import static db61b.Utils.*; + +/** A single table in a database. + * @author P. N. Hilfinger + */ +class Table implements Iterable { + /** A new Table whose columns are given by COLUMNTITLES, which may + * not contain dupliace names. */ + Table(String[] columnTitles) { + for (int i = columnTitles.length - 1; i >= 1; i -= 1) { + for (int j = i - 1; j >= 0; j -= 1) { + if (columnTitles[i].equals(columnTitles[j])) { + throw error("duplicate column name: %s", + columnTitles[i]); + } + } + } + // FILL IN + table_titles = columnTitles; + // System.out.printf("title length: %d%n", table_titles.length); + } + + /** A new Table whose columns are give by COLUMNTITLES. */ + Table(List columnTitles) { + this(columnTitles.toArray(new String[columnTitles.size()])); + } + + /** Return the number of columns in this table. */ + public int columns() { + int numOfColumns = table_titles.length; + return numOfColumns; // REPLACE WITH SOLUTION + } + + /** Return the title of the Kth column. Requires 0 <= K < columns(). */ + public String getTitle(int k) { + if (k < 0 || k >= this.columns()) { + throw error("index out of range: %d", k); + } + String titleName = table_titles[k]; + return titleName; // REPLACE WITH SOLUTION + } + + /** Return the number of the column whose title is TITLE, or -1 if + * there isn't one. */ + public int findColumn(String title) { + int columnIndex = -1; + for (int i = 0; i < table_titles.length; i++) { + if (title.toLowerCase().equals(table_titles[i].toLowerCase())) { + columnIndex = i; + break; + } + } + return columnIndex; // REPLACE WITH SOLUTION + } + + /** Return the number of Rows in this table. */ + public int size() { + int size; + size = _rows.size(); + return size; // REPLACE WITH SOLUTION + } + + /** Returns an iterator that returns my rows in an unspecfied order. */ + @Override + public Iterator iterator() { + return _rows.iterator(); + } + + /** Add ROW to THIS if no equal row already exists. Return true if anything + * was added, false otherwise. */ + public boolean add(Row row) { + if (row.size() != table_titles.length) { + throw error("The size of row is inconsistant with the number of the columns: %d", row.size()); + } else { + // apply add method in HashSet class + return _rows.add(row); + } + // REPLACE WITH SOLUTION + } + + /** Read the contents of the file NAME.db, and return as a Table. + * Format errors in the .db file cause a DBException. */ + static Table readTable(String name) { + BufferedReader input; + Table table; + input = null; + table = null; + try { + // input = new BufferedReader(new FileReader(name + ".db")); + input = new BufferedReader(new FileReader("proj1/testing/" + name + ".db")); + String header = input.readLine(); + if (header == null) { + throw error("missing header in DB file"); + } + String[] columnNames = header.split(","); + table = new Table(columnNames); + String data = input.readLine(); + while (data != null) { + table.add(new Row(data.split(","))); + data = input.readLine(); + } + } catch (FileNotFoundException e) { + throw error("could not find %s.db", name); + } catch (IOException e) { + throw error("problem reading from %s.db", name); + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + /* Ignore IOException */ + } + } + } + return table; + } + + /** Write the contents of TABLE into the file NAME.db. Any I/O errors + * cause a DBException. */ + void writeTable(String name) { + PrintStream output; + output = null; + try { + String sep; + sep = ""; + output = new PrintStream(name + ".db"); + // FILL THIS IN + // print the title of the table + for (int i = 0; i < table_titles.length - 1; i++) { + output.print(table_titles[i]); + output.print(","); + } + output.println(table_titles[table_titles.length - 1]); + // print the data of the table + for (Row r : _rows) { + for (int j = 0; j < this.columns() - 1; j++) { + output.print(r.get(j)); + output.print(","); + } + output.println(r.get(this.columns() - 1)); + } + } catch (IOException e) { + throw error("trouble writing to %s.db", name); + } finally { + if (output != null) { + output.close(); + } + } + } + + /** Print my contents on the standard output. */ + void print() { + // FILL IN + Map map = new HashMap<>(); + int i = 0; + for (Row r : _rows) { + if (map.get(r) == null) { + map.put(r,true); + for (i = 0; i < table_titles.length; i++) { + System.out.print(" "); + System.out.print(r.get(i)); + } + System.out.print("\n"); + } + } + } + + /** Return a list which contains a column of the table. */ + ArrayList selectcolumn(String columnName) { + ArrayList result = new ArrayList(); + int columnnumber = this.findColumn(columnName); + for (Row r : _rows) { + result.add(r.get(columnnumber)); + } + return result; + } + + /** Return a list which contains columns of the table. */ + ArrayList selectcolumns(ArrayList columnNames) { + ArrayList result = new ArrayList(); + ArrayList columnnumber = new ArrayList(); + for (String c : columnNames) { + columnnumber.add(this.findColumn(c)); + } + for (Row r : _rows) { + String tem = ""; + for (int i = 0; i < columnnumber.size(); i++) { + tem += r.get(columnnumber.get(i)); + } + result.add(tem); + } + return result; + } + + /** Reorder the sequence of rows in a table. */ + Table sort(ArrayList order, String columnName) { + ArrayList columnNames = new ArrayList(); + for (int i = 0; i < this.columns(); i++) { + columnNames.add(this.getTitle(i)); + } + Table result = new Table(columnNames); + ArrayList memory = new ArrayList(); + ArrayList newColumns = new ArrayList(); + int columnnumber = this.findColumn(columnName); + for (String c : columnNames) { + newColumns.add(new Column(c, this)); + } + for (int i = 0; i < order.size(); i++) { + int count = 0; + for (Row r : _rows) { + if ((order.get(i) == r.get(columnnumber)) && (!memory.contains(count))) { + result.add(new Row(newColumns, r)); + memory.add(count); + break; + } + count++; + } + } + return result; + } + + /** Reorder the sequence of rows in a table. */ + Table sort(ArrayList order, ArrayList columnName) { + ArrayList columnNames = new ArrayList(); + for (int i = 0; i < this.columns(); i++) { + columnNames.add(this.getTitle(i)); + } + Table result = new Table(columnNames); + ArrayList memory = new ArrayList(); + ArrayList newColumns = new ArrayList(); + ArrayList columnnumber = new ArrayList(); + for (String c : columnName) { + columnnumber.add(this.findColumn(c)); + } + for (String c : columnNames) { + newColumns.add(new Column(c, this)); + } + for (int i = 0; i < order.size(); i++) { + int count = 0; + for (Row r : _rows) { + int start = 0; + int end = 0; + int flag = 1; + for (int j = 0; j < columnnumber.size(); j++) { + start += end; + end += r.get(columnnumber.get(j)).length(); + if (memory.contains(count)) { + flag = 0; + // System.out.println("countbreak"); + break; + } + String left = order.get(i).substring(start, end); + String right = r.get(columnnumber.get(j)); + if (!left.equals(right)) { + // System.out.printf("start is %d, end is %d, left is %s, right is %s\n", start, end, left, right); + flag = 0; + // System.out.println("stringbreak"); + break; + } + } + if ((flag == 1) && (!memory.contains(count))) { + result.add(new Row(newColumns, r)); + memory.add(count); + break; + } + count++; + } + } + return result; + } + + /* Return the aggregate function result based on func type */ + Table aggregate_(String func) { + Table t; + String result = null, colName; + // System.out.printf("test\n"); + if (func.equals("avg")) { + // System.out.printf("test\n"); + int sum = 0; + int cnt = 0; + for (Row r : _rows) { + try { + int i = Integer.parseInt(r.get(0)); + sum = sum + i; + cnt++; + } catch (Exception e) { + throw error("There seem to be problems with the column. Try select another one!"); + } + // System.out.printf("%d\n", sum); + } + int res = sum/cnt; + // System.out.printf("%.1f\n", res); + result = Integer.toString(res); + } else if (func.equals("count")) { + int cnt = 0; + for (Row r : _rows) { + cnt++; + } + result = Integer.toString(cnt); + } else if (func.equals("min")) { + int min = Integer.MAX_VALUE; + for (Row r : _rows) { + try { + int i = Integer.parseInt(r.get(0)); + if (i < min) min = i; + } catch (Exception e) { + throw error("There seem to be problems with the column. Try select another one!"); + } + } + result = Integer.toString(min); + } else if (func.equals("max")) { + int max = Integer.MIN_VALUE; + for (Row r : _rows) { + try { + int i = Integer.parseInt(r.get(0)); + if (i > max) max = i; + } catch (Exception e) { + throw error("There seem to be problems with the column. Try select another one!"); + } + } + result = Integer.toString(max); + } + colName = func + "(" + this.getTitle(0) + ")"; + String[] col = {colName}; + String[] row = {result}; + t = new Table(col); + t.add(new Row(row)); + return t; + } + + /** Return a new Table whose columns are COLUMNNAMES, selected from + * rows of this table that satisfy CONDITIONS. */ + Table select(List columnNames, List conditions, String operations) { + Table result = new Table(columnNames); + // FILL IN + ArrayList newColumns = new ArrayList(); + for (String c : columnNames) { + newColumns.add(new Column(c , this)); + } + if (conditions == null) { + for (Row r : _rows) { + result.add(new Row(newColumns, r)); + } + } else { + for (Row r : _rows) { + if (Condition.test(conditions, operations, r)) { + result.add(new Row(newColumns, r)); + } + } + } + return result; + } + + /** Return a new Table whose columns are COLUMNNAMES, selected + * from pairs of rows from this table and from TABLE2 that match + * on all columns with identical names and satisfy CONDITIONS. */ + Table select(Table table2, List columnNames, + List conditions) { + Table result = new Table(columnNames); + + StringBuffer operations = new StringBuffer(conditions.size()); + for (int i = 1; i < conditions.size(); i++) operations.append("0"); + + if (table2 == null) { + result = this.select(columnNames, conditions, operations.toString()); + return result; + } + List common_column_name; + // FILL IN + // comparing the name of each title in this table and table2, find the common column name + common_column_name = new ArrayList(); + for (int i = 0; i < table_titles.length; i++) { + for (int j = 0; j < table2.columns(); j++) { + if (this.getTitle(i).equals(table2.getTitle(j))) { + common_column_name.add(this.getTitle(i)); + break; + } + } + } + List common1, common2; + common1 = new ArrayList(); + common2 = new ArrayList(); + + for (int k = 0; k < common_column_name.size(); k++) { + common1.add(new Column(common_column_name.get(k), this)); + common2.add(new Column(common_column_name.get(k), table2)); + } + + List common = new ArrayList(); + for (String n : columnNames) { + common.add(new Column(n, this, table2)); + } + + if (conditions == null) { + for (Row thisRow : this) { + for (Row table2Row : table2) { + if (equijoin(common1, common2, thisRow, table2Row)) { + result.add(new Row(common, thisRow, table2Row)); + } + } + } + } else { + for (Row thisRow : this) { + for (Row table2Row : table2) { + if (equijoin(common1, common2, thisRow, table2Row) && Condition.test(conditions, operations.toString(), thisRow, table2Row)) { + result.add(new Row(common, thisRow, table2Row)); + } + } + } + } + return result; + } + + /** Return true if the columns COMMON1 from ROW1 and COMMON2 from + * ROW2 all have identical values. Assumes that COMMON1 and + * COMMON2 have the same number of elements and the same names, + * that the columns in COMMON1 apply to this table, those in + * COMMON2 to another, and that ROW1 and ROW2 come, respectively, + * from those tables. */ + private static boolean equijoin(List common1, List common2, + Row row1, Row row2) { + String str1, str2; + for (int i = 0; i < common1.size(); i++) { + str1 = common1.get(i).getFrom(row1); + str2 = common2.get(i).getFrom(row2); + if (!str1.equals(str2)) { + return false; + } + } + return true; // REPLACE WITH SOLUTION + } + + /** My rows. */ + // private HashSet _rows = new HashSet<>(); + private ArrayList _rows = new ArrayList<>(); + // FILL IN + /** My titles */ + private String table_titles[]; +} diff --git a/proj1/db61b/Tokenizer.java b/proj1/db61b/Tokenizer.java new file mode 100644 index 0000000..b899e4d --- /dev/null +++ b/proj1/db61b/Tokenizer.java @@ -0,0 +1,235 @@ +package db61b; + +import java.io.PrintStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.ArrayList; +import java.util.Scanner; + +import static db61b.Utils.*; + +/** Represents a stream of db61b tokens read from a given Scanner. + * @author P. N. Hilfinger. */ +public class Tokenizer implements Cloneable{ + + /** Text of regular expressions that represent literals (possibly + * unterminated), identifiers, and comments (possibly + * unterminated). */ + private static final String + LITERAL_TEXT = "'(?:[^,'\n\r]*)'?", + IDENTIFIER_TEXT = "[\\p{Alpha}_]\\w*", + COMMENT_TEXT = "(?:/\\*.*?\\*/|/\\*.*)"; + /** Matches potential tokens, including valid or unterminated + * literals, valid or unterminated comments, identifiers, + * relation symbols (=, <, <=, >=, and !=), end-of-line + * sequences, or other single characters. The pattern matches a + * prefix of any string. */ + private static final Pattern + TOKEN_PATN = mkPatn("(?s)[<>!]?=|%s|%s|%s|\r?\n|\\S", + LITERAL_TEXT, IDENTIFIER_TEXT, COMMENT_TEXT); + + /** Patterns matching specific kinds of token. These are intended + * to be used with methods such as CommandInterpreter.name. */ + static final Pattern + IDENTIFIER = mkPatn(IDENTIFIER_TEXT), + LITERAL = mkPatn("'.*"), + RELATION = mkPatn("[<>!]?=|[<>]"); + + /** A Tokenizer that reads tokens from S, and prompts on PROMPTER, + * if it is non-null. */ + Tokenizer(Scanner s, PrintStream prompter) { + _input = s; + _buffer = new ArrayList<>(); + _prompter = prompter; + _continued = false; + _shouldPrompt = true; + _k = 0; + _mat = Pattern.compile(".").matcher(""); + } + + /** Read the next potential token and add it to _buffer. Filters + * out comments and ends-of-line. Adds "*EOF*" at the end of input. + * Throws DBException on encountering a lexical error (such as an + * unterminated literal). */ + private void readToken() { + while (true) { + prompt(); + String token = _input.findWithinHorizon(TOKEN_PATN, 0); + if (token == null) { + token = "*EOF*"; + } else if (token.startsWith("'")) { + if (token.length() == 1 || !token.endsWith("'")) { + throw error("unterminated literal constant"); + } + } else if (token.startsWith("/*")) { + if (token.length() < 4 || !token.endsWith("*/")) { + throw error("unterminated comment"); + } + continue; + } else if (token.endsWith("\n")) { + _shouldPrompt = true; + continue; + } + _buffer.add(token); + _continued = !token.equals(";"); + return; + } + } + + /** Print an appropriate prompt, if there is a prompter: either ">" + * when expecting the start of a new command, or "..." + * otherwise. */ + private void prompt() { + if (_shouldPrompt && _prompter != null) { + if (_continued) { + _prompter.print("..."); + } else { + _prompter.print("> "); + } + _prompter.flush(); + _shouldPrompt = false; + } + } + + /** Indicate that a prompt for a token (if needed) should indicate + * that a new command is expected. */ + void newCommand() { + _continued = true; + } + + /** Read and return the next token, if it matches P. Otherwise throw + * DBException */ + String next(Pattern p) { + if (!nextIs(p)) { + if (nextIs("*EOF*")) { + throw error("unexpected end of input"); + } else { + throw error("unexpected token: '%s'", peek()); + } + } + return next(); + } + + /** Read and return the next token, if it equals P. Otherwise throw + * DBException */ + String next(String p) { + String tmp = p.toLowerCase(); + if (!nextIs(tmp)) { + if (nextIs("*EOF*")) { + throw error("unexpected end of input"); + } else { + throw error("unexpected token: '%s'", peek()); + } + } + return next(); + } + + /** Read the next token, if it matches P, and return true. Otherwise + * return false. Still throws DBException on detecting lexical errors. */ + boolean nextIf(Pattern p) { + if (nextIs(p)) { + next(); + return true; + } + return false; + } + + /** Read the next token, if it equals P, and return true. Otherwise + * return false. Still throws DBException on detecting lexical errors. */ + boolean nextIf(String p) { + if (nextIs(p)) { + next(); + return true; + } + return false; + } + + + /** Return true iff the next token matches P. Throws DBException on + * encountering a lexical error. */ + boolean nextIs(Pattern p) { + String token = peek(); + return _mat.usePattern(p).reset(token).matches(); + } + + /** Return true iff the next token equals P. Throws DBException on + * encountering a lexical error. */ + boolean nextIs(String p) { + String token = peek().toLowerCase(); + return token.equals(p); + } + + /** Return and read past the next token. */ + String next() { + if (_k == _buffer.size()) { + readToken(); + } + _k += 1; + return _buffer.get(_k - 1); + } + + /** Returns the next token without changing the position of THIS. */ + String peek() { + while (_k >= _buffer.size()) { + readToken(); + } + return _buffer.get(_k); + } + + /** Return THIS to its position just after the last flush or flushToSemi + * operation(or its initial position if there have been no flush... + * operations). */ + void rewind() { + _k = 0; + } + + /** Set the rewind point to the current position. */ + void flush() { + _buffer.subList(0, _k).clear(); + _k = 0; + } + + /** Set the rewind point and the current position to just after the + * next semicolon after the previous rewind point(or the end of input, + * whichever comes first). */ + void flushToSemi() { + rewind(); + while (true) { + try { + newCommand(); + String token = next(); + if (token == null || token.equals(";")) { + break; + } + } catch (DBException e) { + /* Ignore DBException */ + } + } + flush(); + newCommand(); + } + + protected Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + return null; + } + } + + /** Matcher used for pattern matching. */ + private Matcher _mat; + /** The character input source. */ + private Scanner _input; + /** All tokens read since the last flush or beginning of input. */ + private ArrayList _buffer; + /** Output for prompts. Null if prompts not used. */ + private PrintStream _prompter; + /** False iff the next token is expected to start a command. */ + private boolean _continued; + /** True iff prompt is needed for the next token. */ + private boolean _shouldPrompt; + /** Current position in the token stream as an offset within _buffer. */ + private int _k; +} diff --git a/proj1/db61b/Utils.java b/proj1/db61b/Utils.java new file mode 100644 index 0000000..6ddb074 --- /dev/null +++ b/proj1/db61b/Utils.java @@ -0,0 +1,30 @@ +package db61b; + +import java.util.regex.Pattern; + +/** Various utilities used by other sources. + * @author P. N. Hilfinger + */ +class Utils { + + /** Return a pattern created by formatting S with arguments ARGS (as for + * String.format). */ + static Pattern mkPatn(String s, Object ... args) { + s = s.replace(" *", "\\s*").replace(" ", "\\s+"); + return Pattern.compile(String.format(s, args)); + } + + /** Shorthand that returns String.format(S, ARGS). */ + static String format(String s, Object ... args) { + return String.format(s, args); + } + + /** Return a DBException whose message is formed from S and ARGS as for + * String.format. */ + static DBException error(String s, Object ... args) { + return new DBException(format(s, args)); + } + +} + + diff --git a/proj1/staff-version b/proj1/staff-version new file mode 100644 index 0000000..dd47df8 --- /dev/null +++ b/proj1/staff-version @@ -0,0 +1 @@ +proj1-7 diff --git a/proj1/testing/.DS_Store b/proj1/testing/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/proj1/testing/.DS_Store differ diff --git a/proj1/testing/Makefile b/proj1/testing/Makefile new file mode 100644 index 0000000..102a0ed --- /dev/null +++ b/proj1/testing/Makefile @@ -0,0 +1,49 @@ +# This makefile is defined to give you the following targets: +# +# default: Same as check +# check: For each file, F.in, use F.in as input to "java $(MAIN_CLASS)" and +# compare the output to the contents of the file names F.out. +# Report discrepencies. +# clean: Remove all the .class files produced by java compilation, +# all Emacs backup files, and testing output files. +# +# You can use this file without understanding most of it, of course, but +# I strongly recommend that you try to figure it out, and where you cannot, +# that you ask questions. The Lab Reader contains documentation. + +SHELL = /bin/bash + +# Flags to Java interpreter: check assertions +JFLAGS = -ea + +# Name of class containing main procedure +MAIN = db61b.Main + +TESTER_FLAGS = + +TESTS := $(wildcard *.in) + +.PHONY: default check clean + +# First, and therefore default, target. +default: + $(MAKE) -C .. + $(MAKE) check + +# 'make check' will test run the tests in the testing directory. +# For each .in file, it will "pipe" that file through your program +# (that is, cause the .in file to be the standard input, rather as if it +# were typed into the terminal), and compare the result with the +# corresponding .out file. It will report any failures. +# The "CLASSPATH=..." clause in front of the command below temporarily +# changes the location in which Java searches for classes to be the directory +# that contains your project. +check: $(CLASSES) + @echo "Testing application $(MAIN)..." + @CLASSPATH=..:$(CLASSPATH) python3 tester.py $(TESTER_FLAGS) $(TESTS) + +# 'make clean' will clean up stuff you can reconstruct. +clean: + $(RM) -r *~ OUTPUT* *.log *.diff __pycache__ + + diff --git a/proj1/testing/blank.db b/proj1/testing/blank.db new file mode 100644 index 0000000..55096df --- /dev/null +++ b/proj1/testing/blank.db @@ -0,0 +1 @@ +First,Second,Third diff --git a/proj1/testing/enrolled.db b/proj1/testing/enrolled.db new file mode 100644 index 0000000..387a69f --- /dev/null +++ b/proj1/testing/enrolled.db @@ -0,0 +1,20 @@ +SID,CCN,Grade +101,21228,B +101,21105,B+ +101,21232,A- +101,21001,B +102,21231,A +102,21105,A- +102,21229,A +102,21001,B+ +103,21105,B+ +103,21005,B+ +104,21228,A- +104,21229,B+ +104,21105,A- +104,21005,A- +105,21228,A +105,21001,B+ +106,21103,A +106,21001,B +106,21231,A diff --git a/proj1/testing/schedule.db b/proj1/testing/schedule.db new file mode 100644 index 0000000..67ecf12 --- /dev/null +++ b/proj1/testing/schedule.db @@ -0,0 +1,9 @@ +CCN,Num,Dept,Time,Room,Sem,Year +21228,61A,EECS,2-3MWF,1 Pimentel,F,2003 +21231,61A,EECS,1-2MWF,1 Pimentel,S,2004 +21229,61B,EECS,11-12MWF,155 Dwinelle,F,2003 +21232,61B,EECS,1-2MWF,2050 VLSB,S,2004 +21103,54,Math,1-2MWF,2050 VLSB,F,2003 +21105,54,Math,1-2MWF,1 Pimentel,S,2004 +21001,1A,English,9-10MWF,2301 Tolman,F,2003 +21005,1A,English,230-5TuTh,130 Wheeler,S,2004 diff --git a/proj1/testing/students.db b/proj1/testing/students.db new file mode 100644 index 0000000..8481453 --- /dev/null +++ b/proj1/testing/students.db @@ -0,0 +1,7 @@ +SID,Lastname,Firstname,SemEnter,YearEnter,Major,Scholarship +101,Knowles,Jason,F,2003,EECS,7000 +102,Chan,Valerie,S,2003,Math,10000 +103,Xavier,Jonathan,S,2004,LSUnd,8500 +104,Armstrong,Thomas,F,2003,EECS,6666 +105,Brown,Shana,S,2004,EECS,11250 +106,Chan,Yangfan,F,2003,LSUnd,9999 diff --git a/proj1/testing/test1.in b/proj1/testing/test1.in new file mode 100644 index 0000000..8e39cd0 --- /dev/null +++ b/proj1/testing/test1.in @@ -0,0 +1,21 @@ +load students; +load enrolled; +load schedule; +/* What are the names and SIDS of all students whose last name + is 'Chan'? */ +select SID, Firstname from students + where Lastname = 'Chan'; +/* Who took the course with CCN 21001, and what were their grades? */ +select Firstname, Lastname, Grade + from students, enrolled where CCN = '21001' + and SID = SID; +/* Who has taken the course named 61A from EECS? */ +/* First, create a table that contains SIDs and course names */ +create table enrolled2 as + select SID + from enrolled, schedule + where Dept = 'EECS' and Num = '61A'; +/* Now print the names of the students in this list */ +select Firstname, Lastname from students, enrolled2; +quit; + diff --git a/proj1/testing/test1.out b/proj1/testing/test1.out new file mode 100644 index 0000000..74041c7 --- /dev/null +++ b/proj1/testing/test1.out @@ -0,0 +1,18 @@ +DB61B System. Version 1.0 +Loaded students.db +Loaded enrolled.db +Loaded schedule.db +Search results: + 102 Valerie + 106 Yangfan +Search results: + Jason Knowles B + Shana Brown B+ + Yangfan Chan B + Valerie Chan B+ +Search results: + Jason Knowles + Valerie Chan + Thomas Armstrong + Shana Brown + Yangfan Chan diff --git a/proj1/testing/test2.in b/proj1/testing/test2.in new file mode 100644 index 0000000..401311d --- /dev/null +++ b/proj1/testing/test2.in @@ -0,0 +1,27 @@ +load students; load enrolled; load schedule; + +/* Add a new course to the schedule. */ +insert into schedule values '22100', '111', 'Math', '10-11:30TTh', '60 Evans', + 'S', '2004'; +/* Have a student add the new course. */ +insert into enrolled values '102', '22100', 'B+'; + +/* Print that student's courses for the spring. */ +select Num, Dept, Time, Room from schedule, enrolled where + SID = '102' and Sem = 'S' and Year = '2004'; + +/* Delete a student */ + +create table enrolled as select SID, CCN, Grade from enrolled + where SID != '103'; + +create table students + as select SID, Lastname, Firstname, SemEnter, YearEnter, Major + from students where SID != '103'; + +/* And print the SIDs of those remaining. */ +select SID from enrolled; + +select SID from students; + +quit; diff --git a/proj1/testing/test2.out b/proj1/testing/test2.out new file mode 100644 index 0000000..e43d5d6 --- /dev/null +++ b/proj1/testing/test2.out @@ -0,0 +1,20 @@ +DB61B System. Version 1.0 +Loaded students.db +Loaded enrolled.db +Loaded schedule.db +Search results: + 61A EECS 1-2MWF 1 Pimentel + 54 Math 1-2MWF 1 Pimentel + 111 Math 10-11:30TTh 60 Evans +Search results: + 101 + 102 + 104 + 105 + 106 +Search results: + 101 + 102 + 104 + 105 + 106 diff --git a/proj1/testing/tester.py b/proj1/testing/tester.py new file mode 100644 index 0000000..a2ac92a --- /dev/null +++ b/proj1/testing/tester.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python +# -*-Python-*- + +import testing +import sys, re +import io +import getopt +from os.path import join + +class Proj1_Tester(testing.Tester): + def output_filter(self, id, content): + """This is a simple filtering script, written in Python, that filters + unimportant potential differences between the output of your program + and a standard test file so that the two can be compared meaningfully. + + Output filter for project 1: + 1. Drops the first line. + 2. Removes all prompts + 3. Removes trailing blanks and converts multiple + blanks to single blanks. + 4. Removes blank lines + 5. Converts any error line to the single word ERROR. + 6. Sorts any cluster of indented lines.""" + + out = io.StringIO() + indented = [] + + # Sort and print any accumulated lines in the array @indented. + def flushIndented(): + if indented: + indented.sort() + for line in indented: + print(line, file=out) + indented.clear() + + for line in re.split(r'\r?\n', content)[1:]: + + line = re.sub(r'^(> |\.\.\.)*', '', line) + + line = line.rstrip() + line = re.sub(r' +', ' ', line) + if line == '': + continue + if re.search(r'(?i)error', line): + flushIndented() + print('ERROR', file=out) + break + if re.match(r'\s', line): + indented.append(line) + else: + flushIndented() + print(line, file=out) + flushIndented() + + return out.getvalue() + + def input_files(self, id): + result = list(super().input_files(id)) + content = testing.contents(self.standard_input_file(id)) + for db in re.findall(r'(?:^\s*load|;\s*load)\s+(\w+)', content): + result.append((db + ".db", + join(self.base_dir(id), db + ".db"), None)) + return result + +show=None +try: + opts, args = getopt.getopt(sys.argv[1:], '', ['show=']) + for opt, val in opts: + if opt == '--show': + show = int(val) + else: + assert False +except: + print("Usage: python3 tester.py [--show=N] TEST.in...", + file=sys.stderr) + sys.exit(1) + +tester = Proj1_Tester(tested_program="java -ea db61b.Main", + report_limit=show) + +sys.exit(0 if tester.test_all(args) else 1) + diff --git a/proj1/testing/testing.py b/proj1/testing/testing.py new file mode 100644 index 0000000..509e398 --- /dev/null +++ b/proj1/testing/testing.py @@ -0,0 +1,475 @@ +"""This module defines the Tester class, which provides automated +services for performing integration tests. Here is the outline of a +typical testing script: + + import testing + import sys, getopt + + class Proj1_Tester(testing.Tester): + '''customize methods as needed.''' + + show=None + try: + opts, args = getopt.getopt(sys.argv[1:], '', ['show=']) + for opt, val in opts: + if opt == '--show': + show = int(val) + except: + Usage() + + # A tester that tests the program run by the command 'java -ea proj1.Main', + # gives detailed reports of the first 5 erroneous results, + # and reports later errors without detail. + tester = Proj1_Tester(tested_program="java -ea proj1.Main", + report_limit=5) + + # Run all tests specified in the command-line arguments and exit + sys.exit(0 if tester.test_all(args) else 1) + +DEFAULT BEHAVIOR +================ + +By default, testing (via the method .test_all()) consists of +the following steps: + + 1. For each test T of the form B.EXT in TESTS: + a. Execute the program with the command + < B.in + collecting its standard output, standard error, and exit status. + b. Check that the exit status is 0 (normal Unix exit), the standard + output is equal to the contents of B.out (if that file exists), + and the standard error output compares equal to B.err (if that + file exists). + c. If part b fails, print a report containing the contents of + B.in, B.out, and B.err (the parameter report_limit optionally allows + limiting the number of erroneous tests that get this full report). + + 2. After running all tests, report summary statistics, and return a + true value iff all of them pass. + +CUSTOMIZATION +============= + +A good deal of customization is possible. First, there are various +keyword parameters that you may pass to the constructor for Tester: + + * tested_program: Used in step 1a---the command to run the tested program. + * time_limit: The time limit (in seconds) for executing the tested program + for each test [default: 10 seconds] + * file_size_limit: The limit on the total output of the tested program + (in 512-byte blocks) [default: 2000] + * heap_size_limit: Limit on the virtual memory of the tested program (in + Kbytes) [default: 500000] + * report_limit: Number of failing test cases to report in detail. + [default: no limit] + * report_char_limit: Limit on the number of characters reported for each + file reported as a result of an erroneous test. [default: 1000]. + +Second, you may override several methods to affect the test procedure. +For a tester P, the actual test performed for a test that is +designated by T is as follows: + + P.run_program(T) + P.output_compare(T) + + P.cleanup(T) + +By default, the run_program method acts as follows: + + 1. Run the command P.command_args(T) (as a shell command), + with the empty string as the standard input, first using ulimit to + set the time limit, output limit, and VM limit. + 2. Set P.stdout and P.stderr, respectively, to the standard output and + standard error of the program. + 3. Set P.rc to the Unix exit status of the program. + +You can override the command_args method as desired. By default, it returns +the string + < P.standard_input_file(T) + +The standard_input_file method is also overridable, and defaults to +returning B.in, where T is B.EXT for some extension EXT (.EXT is +optional), if that file exists, and otherwise returns None. + +The output_compare method is supposed to determine the correctness of +P.stdout, P.stderr, and P.rc (as set by run_program) given the test +designation T. It sets P.reason either to True (indicating the test +passes) or to a string giving a reason for failure. By default, it +checks P.rc to see that it is 0, compares P.stdout against the +contents of the file named P.stahdard_output_file(T) if the latter +exists (by default, B.out), and P.stderr against the contents of +P.standard_error_file(T) if it exists (by default B.err). Before the +comparison, it filters P.stdout and P.standard_output_file(T) through +P.output_filter (the identity function by default, and P.stderr and +P.standard_error_file(T) through P.error_filter (the identity by +default). You can override the standard_output_file, +standard_error_file, output_filter, and error_filter methods. + +The cleanup method is provided in case a test produces output files +that should be removed. By default, it does nothing. + + +REPORTING +========= + +When a test fails, then (unless the report_limit is set and has been +reached), the module will produce a report containing the contents of +certain files, as indicated by several overridable methods. Each of +these methods returns a list of triples of the form (, +, ) that describe an input or output of a test +program. is used to identify the file on a report. + is the full pathname of the contents of the file. +Alternatively, if is None, is the actual +contents of the file (it is otherwise ignored). + +For a testing object P, and test T, the methods are + + * P.input_files(T): Input files. By default, one entry containing the + the default value of standard_input_file. + * P.output_files(T): Output files produced by the test program. + By default, one entry containing the contents of P.stdout. + * P.error_files(T): Error files produced by the test program. + By default, one entry containing the contents of P.stderr. + * P.standard_output_files(T): The standard (correct) output files. + By default, one entry containing the default value of + standard_output_file. + * P.standard_error_files(T): The standard (correct) error files. + By default, one entry containing the default value of + standard_error_file. +""" + + +import sys, re +from subprocess import Popen, PIPE +from os.path import splitext, basename, dirname, join, exists +from signal import * + +def contents(filename): + """The contents of FILENAME, or the empty string if the file does not + exist or is unreadable.""" + try: + with open(filename) as inp: + return inp.read() + except: + return '' + +def interpret_problem(rc, error_output): + if rc == 1: + mat = re.search(r'(?m)^Exception in thread ".*" (.*)\s*(at .*)', + error_output) + if mat: + return "java exception ({exception}) {where}" \ + .format(exception=mat.group(1), where=mat.group(2)) + if 0 < rc < 128: + return "terminated with non-zero exit code ({rc})"\ + .format(rc=rc) + elif rc < 0: + if rc == SIGKILL: + return "terminated by kill signal" + elif rc == SIGTERM: + return "terminated by ^C" + elif rc == SIGXCPU: + return "CPU time exceeded" + elif rc == SIGXFSZ: + return "file size limit exceeded (too much data written)" + for key in signal.__dict__: + if re.match(r'SIG', key) and signal.__dict__[key] == -rc: + return "terminated on Unix signal " + key + return "terminated on Unix signal " + str(rc) + +DEFAULT_PARAMS = { + 'tested_program' : None, + 'time_limit' : 10, # seconds + 'file_size_limit' : 2000, # 512-byte blocks + 'heap_size_limit' : 500000, # KB + 'report_limit' : None, + 'report_char_limit' : 1000, +} + +class Tester: + + def __init__(self, **params): + self.params = DEFAULT_PARAMS.copy() + self.params.update(params) + self.clear() + + def __getattr__(self, name): + if name in self.params: + return self.params[name] + else: + raise AttributeError(name) + + def clear(self): + self.passed = self.count = 0 + self.files_shown = set() + + @property + def failed(self): + return self.num - self.passed + + def test_all(self, tests): + self.clear() + for id in tests: + self._perform_test(id) + self._report_summary() + return self.passed == self.count + + def base_id(self, id): + """The root name of the test identified by ID. By default, this is + ID with any parent directory names and any extension stripped.""" + return splitext(basename(id))[0] + + def base_dir(self, id): + """The directory containing the test file identified by ID. By + default, this is the directory segment of ID.""" + return dirname(id) + + def standard_input_file(self, id): + name = self.base_id(id) + ".in" + fullname = join(self.base_dir(id), name) + if exists(fullname): + return fullname + else: + return None + + def standard_output_file(self, id): + """The standard output file for test ID, if any, else None. + By default, this is the file in base_dir(ID) with the same simple name + as ID but with extension .out, if it exists.""" + name = self.base_id(id) + ".out" + fullname = join(self.base_dir(id), name) + if exists(fullname): + return fullname + else: + return None + + def standard_error_file(self, id): + """The standard error file for test ID, if any, else None. + By default, this is the file in base_dir(ID) with the same simple name + as ID but with extension .err, if it exists.""" + name = self.base_id(id) + ".err" + fullname = join(self.base_dir(id), name) + if exists(fullname): + return fullname + else: + return None + + # Reportable Files + + # Each of the following methods returns a list of triples of the form + # (, , ) that describe an input or output + # of a test program. is used to identify the file on a + # report. is the full pathname of the contents of the file. + # Alternatively, if is None, is the actual contents + # of the file (it is otherwise ignored). + + def input_files(self, id): + """The inputs reportable for test ID. By default, this contains + the single file in base_dir(ID) with the same simple name + as ID but with extension .in, if it exists, and otherwise nothing.""" + name = self.base_id(id) + ".in" + fullname = join(self.base_dir(id), name) + if exists(fullname): + return ((name, fullname, None), ) + else: + return () + + def output_files(self, id): + """A list of program outputs to be reported for test ID. STDOUT is + the contents of the standard output of the program. By default + the result contains the single item ("", None, STDOUT) + unless STDOUT is None, in which case the result is empty.""" + return (() if self.stdout is None + else (("", None, self.stdout),)) + + def error_files(self, id): + """A list of program error outputs to be reported for test ID. STDERR is + the contents of the standard error of the program. By default + the result contains the single item ("", None, STDERR) + unless STDERR is None, in which case the result is empty.""" + return (() if self.stderr is None + else (("", None, self.stderr),)) + + def standard_output_files(self, id): + """The standard output files to be reported for test ID. By default, + this contains the single file in base_dir(ID) with the same simple name + as ID but with extension .out, if it exists, and otherwise nothing.""" + name = self.base_id(id) + ".out" + fullname = join(self.base_dir(id), name) + if exists(fullname): + return ((name, fullname, None), ) + else: + return () + + def standard_error_files(self, id): + """The standard error files to be reported for test ID. By default, + this contains the single file in base_dir(ID) with the same simple name + as ID but with extension .err, if it exists, and otherwise nothing.""" + name = self.base_id(id) + ".err" + fullname = join(self.base_dir(id), name) + if exists(fullname): + return ((name, fullname, None), ) + else: + return () + + # End of Reportable Files + + def run_program(self, id): + """Run the test program for test ID, placing the standard output in + SELF.stdout the standard error output in SELF.stderr, and the return + code in SELF.rc. By default, runs SELF._command_line(ID), pipes + the results to SELF.stdout and SELF.stderr, and places the return code + in SELF.rc. Overridings may set .stdout or .stderr to None, indicating + that they are irrelevant.""" + proc = Popen(self._command_line(id), + shell=True, universal_newlines=True, + stdout=PIPE, stderr=PIPE, stdin=PIPE) + self.stdout, self.stderr = proc.communicate('') + self.rc = proc.returncode + + def _perform_test(self, id): + self.reason = None + self.count += 1 + self.run_program(id) + self.output_compare(id) + if self.reason is True: + self.passed += 1 + self._report_pass(id) + else: + self._report_fail(id) + self.cleanup(id) + + def command_args(self, testid): + """The default command-line arguments that follow the shell syntax + to invoke the tested program. The default is simply a redirect of + the standard input from the input file for TESTID, as determined by + .standard_input_file.""" + return " <" + self.standard_input_file(testid) + " " + + def _command_line(self, id): + return "ulimit -t {timeout}; ulimit -f {filesize}; \ + ulimit -d {memsize}; \ + {command} {args}" \ + .format(command=self.tested_program, + args=self.command_args(id), + timeout=self.time_limit, filesize=self.file_size_limit, + memsize=self.heap_size_limit) + + def output_compare(self, testid): + """The default comparison procedure to use with test TESTID. Sets + self.reason to the result of the test. In what follows, STDOUT + and STDERR are the output sent by the tested program to the standard + output and standard error, respectively. RC is the return code + returned by the process. + + Sets .reason to True iff + (1) RC is 0; and + (2) .stdout compares equal against the contents of file + .standard_output_file(TESTID), after first converting both + with .output_filter. + (3) .stderr compares equal against the contents of file + .standard_error_file(TESTID), after first converting both + with .error_filter. + Otherwise, sets .reason to message describing error.""" + + if self.rc != 0: + self.reason = "Program exited abnormally: {}" \ + .format(interpret_problem(self.rc, self.stderr)) + elif self.stdout is not None and self.standard_output_file(testid) \ + and self.output_filter(testid, self.stdout) \ + != self.output_filter(testid, + contents(self.standard_output_file(testid))): + self.reason = "Output does not match expected output." + elif self.stderr is not None and self.standard_error_file(testid) \ + and self.error_filter(testid, self.stderr) \ + != self.error_filter(testid, + contents(self.standard_error_file(testid))): + self.reason = "Error output does not match expected output." + else: + self.reason = True + + def output_filter(self, id, content): + """A filter applied to the standard output before comparing with + expected output for test ID.""" + return content + + def error_filter(self, id, content): + """A filter applied to the standard error before comparing with + expected error output for test ID.""" + return content + + def cleanup(self, id): + """Clean up any files or resources used in this test. The default + implementation does nothing.""" + pass + + def _report_pass(self, id): + print("** {id} PASSED.".format(id=self.base_id(id))) + + def _report_fail(self, id): + base = self.base_id(id) + print("** {id} FAILED ({reason})".format(id=base, reason=self.reason)) + suppress = (self.report_limit is not None and + self.count - self.passed > self.report_limit) + if suppress and self.count - self.passed == self.report_limit + 1: + print + print("*** Encountered more than {limit} errors. " + "Further detailed reports suppressed. ***" + .format(limit=self.report_limit)) + print + if not suppress: + for title, name_pairs in ( + ("**** INPUT FILES:", self.input_files(id)), + ("**** OUTPUTS FROM TEST PROGRAM:", self.output_files(id)), + ("**** EXPECTED OUTPUTS:", self.standard_output_files(id)), + ("**** ERROR OUTPUTS FROM TEST PROGRAM:", self.error_files(id)), + ("**** EXPECTED ERROR OUTPUTS:", self.standard_error_files(id))): + + self._print_report(title, name_pairs) + print("** End of {id} error report **".format(id=base)) + print() + + def _print_report(self, title, name_pairs): + if not name_pairs: + return + print() + print(title) + limit = self.report_char_limit + + for name, filename, content in name_pairs: + if filename is not None: + if filename in self.files_shown: + print("[{name} shown previously]".format(name=name)) + continue + content = contents(filename) + self.files_shown.add(filename) + truncated = False + if limit and len(content) > limit: + end = content.rfind("\n", 0, limit) + if end == -1: + segment = content[:limit] + "\n" + truncated = limit + else: + segment = content[:end+1] + truncated = end+1 + else: + segment = content + if segment != "" and not segment.endswith('\n'): + segment += "\n\n" + if name: + print("+--- " + name + " " + "-" * (60-len(name)) + "+") + else: + print("+" + "-" * 65 + "+") + sys.stdout.write(segment) + if truncated: + print("... + {} more characters [listing truncated]"\ + .format(truncated)) + print("+" + "-" * 65 + "+") + + def _report_summary(self): + print() + if self.passed == self.count: + print("Passed all {num} tests.".format(num=self.count)) + else: + print("Passed {passed} out of {num} tests." + .format(passed=self.passed, num=self.count)) diff --git a/videolink.txt b/videolink.txt new file mode 100644 index 0000000..5f65c9b --- /dev/null +++ b/videolink.txt @@ -0,0 +1 @@ +https://cuhko365-my.sharepoint.com/:v:/g/personal/120090609_link_cuhk_edu_cn/Ed3DS1e8eoVPqPzizAdmyNQBEqQWCmFjDLqoRN7TOhw2nw?e=cVpTYw