diff --git a/docs/design/specs/dbMigration/.gitkeep b/.github/.keep similarity index 100% rename from docs/design/specs/dbMigration/.gitkeep rename to .github/.keep diff --git a/.github/classroom/autograding.json b/.github/classroom/autograding.json deleted file mode 100644 index d045c04a9..000000000 --- a/.github/classroom/autograding.json +++ /dev/null @@ -1,203 +0,0 @@ -{ - "tests": [ - { - "name": "STEP-1 Test", - "setup": "npm install", - "run": "npm run test:1", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-2 Test", - "setup": "npm install", - "run": "npm run test:2", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-3 Test", - "setup": "npm install", - "run": "npm run test:3", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-4 Test", - "setup": "npm install", - "run": "npm run test:4", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-5 Test", - "setup": "npm install", - "run": "npm run test:5", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-6 Test", - "setup": "npm install", - "run": "npm run test:6", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-7 Test", - "setup": "npm install", - "run": "npm run test:7", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-8 Test", - "setup": "npm install", - "run": "npm run test:8", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-9 Test", - "setup": "npm install", - "run": "npm run test:9", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-10 Test", - "setup": "npm install", - "run": "npm run test:10", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-11 Test", - "setup": "npm install", - "run": "npm run test:11", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-12 Test", - "setup": "npm install", - "run": "npm run test:12", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-13 Test", - "setup": "npm install", - "run": "npm run test:13", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-14 Test", - "setup": "npm install", - "run": "npm run test:14", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-15 Test", - "setup": "npm install", - "run": "npm run test:15", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-16 Test", - "setup": "npm install", - "run": "npm run test:16", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-17 Test", - "setup": "npm install", - "run": "npm run test:17", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-18 Test", - "setup": "npm install", - "run": "npm run test:18", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - }, - { - "name": "STEP-19 Test", - "setup": "npm install", - "run": "npm run test:19", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - },{ - "name": "STEP-20 Test", - "setup": "npm install", - "run": "npm run test:20", - "input": "", - "output": "", - "comparison": "exact", - "timeout": 10, - "points": 10 - } - ] -} \ No newline at end of file diff --git a/.github/workflows/classroom.yml b/.github/workflows/classroom.yml index dca83b024..8c4fa1b7e 100644 --- a/.github/workflows/classroom.yml +++ b/.github/workflows/classroom.yml @@ -1,19 +1,220 @@ -name: GitHub Classroom Workflow - -on: - - push - - workflow_dispatch - +name: Autograding Tests +'on': +- push +- workflow_dispatch +- repository_dispatch permissions: checks: write actions: read contents: read - jobs: - build: - name: Autograding + run-autograding-tests: runs-on: ubuntu-latest if: github.actor != 'github-classroom[bot]' steps: - - uses: actions/checkout@v4 - - uses: education/autograding@v1 + - name: Checkout code + uses: actions/checkout@v4 + - name: Step-1 Test + id: step-1-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-1 Test + setup-command: npm install + command: npm run test:1 + timeout: 10 + max-score: 10 + - name: Step-2 Test + id: step-2-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-2 Test + setup-command: npm install + command: npm run test:2 + timeout: 10 + max-score: 10 + - name: Step-3 Test + id: step-3-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-3 Test + setup-command: npm install + command: npm run test:3 + timeout: 10 + max-score: 10 + - name: Step-4 Test + id: step-4-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-4 Test + setup-command: npm install + command: npm run test:4 + timeout: 10 + - name: Step-5 Test + id: step-5-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-5 Test + setup-command: npm install + command: npm run test:5 + timeout: 10 + max-score: 10 + - name: Step-6 Test + id: step-6-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-6 Test + setup-command: npm install + command: npm run test:6 + timeout: 10 + max-score: 10 + - name: Step-7 Test + id: step-7-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-7 Test + setup-command: npm install + command: npm run test:7 + timeout: 10 + max-score: 10 + - name: Step-8 Test + id: step-8-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-8 Test + setup-command: npm install + command: npm run test:8 + timeout: 10 + max-score: 10 + - name: Step-9 Test + id: step-9-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-9 Test + setup-command: npm install + command: npm run test:9 + timeout: 10 + max-score: 10 + - name: Step-10 Test + id: step-10-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-10 Test + setup-command: npm install + command: npm run test:10 + timeout: 10 + max-score: 10 + - name: Step-11 Test + id: step-11-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-11 Test + setup-command: npm install + command: npm run test:11 + timeout: 10 + max-score: 10 + - name: Step-12 Test + id: step-12-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-12 Test + setup-command: npm install + command: npm run test:12 + timeout: 10 + max-score: 10 + - name: Step-13 Test + id: step-13-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-13 Test + setup-command: npm install + command: npm run test:13 + timeout: 10 + max-score: 10 + - name: Step-14 Test + id: step-14-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-14 Test + setup-command: npm install + command: npm run test:14 + timeout: 10 + max-score: 10 + - name: Step-15 Test + id: step-15-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-15 Test + setup-command: npm install + command: npm run test:15 + timeout: 10 + max-score: 10 + - name: Step-16 Test + id: step-16-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-16 Test + setup-command: npm install + command: npm run test:16 + timeout: 10 + max-score: 10 + - name: Step-17 Test + id: step-17-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-17 Test + setup-command: npm install + command: npm run test:17 + timeout: 10 + max-score: 10 + - name: Step-18 Test + id: step-18-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-18 Test + setup-command: npm install + command: npm run test:18 + timeout: 10 + max-score: 10 + - name: Step-19 Test + id: step-19-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-19 Test + setup-command: npm install + command: npm run test:19 + timeout: 10 + max-score: 10 + - name: Step-20 Test + id: step-20-test + uses: education/autograding-command-grader@v1 + with: + test-name: Step-20 Test + setup-command: npm install + command: npm run test:20 + timeout: 10 + max-score: 10 + - name: Autograding Reporter + uses: education/autograding-grading-reporter@v1 + env: + STEP-1-TEST_RESULTS: "${{steps.step-1-test.outputs.result}}" + STEP-2-TEST_RESULTS: "${{steps.step-2-test.outputs.result}}" + STEP-3-TEST_RESULTS: "${{steps.step-3-test.outputs.result}}" + STEP-4-TEST_RESULTS: "${{steps.step-4-test.outputs.result}}" + STEP-5-TEST_RESULTS: "${{steps.step-5-test.outputs.result}}" + STEP-6-TEST_RESULTS: "${{steps.step-6-test.outputs.result}}" + STEP-7-TEST_RESULTS: "${{steps.step-7-test.outputs.result}}" + STEP-8-TEST_RESULTS: "${{steps.step-8-test.outputs.result}}" + STEP-9-TEST_RESULTS: "${{steps.step-9-test.outputs.result}}" + STEP-10-TEST_RESULTS: "${{steps.step-10-test.outputs.result}}" + STEP-11-TEST_RESULTS: "${{steps.step-11-test.outputs.result}}" + STEP-12-TEST_RESULTS: "${{steps.step-12-test.outputs.result}}" + STEP-13-TEST_RESULTS: "${{steps.step-13-test.outputs.result}}" + STEP-14-TEST_RESULTS: "${{steps.step-14-test.outputs.result}}" + STEP-15-TEST_RESULTS: "${{steps.step-15-test.outputs.result}}" + STEP-16-TEST_RESULTS: "${{steps.step-16-test.outputs.result}}" + STEP-17-TEST_RESULTS: "${{steps.step-17-test.outputs.result}}" + STEP-18-TEST_RESULTS: "${{steps.step-18-test.outputs.result}}" + STEP-19-TEST_RESULTS: "${{steps.step-19-test.outputs.result}}" + STEP-20-TEST_RESULTS: "${{steps.step-20-test.outputs.result}}" + with: + runners: step-1-test,step-2-test,step-3-test,step-4-test,step-5-test,step-6-test,step-7-test,step-8-test,step-9-test,step-10-test,step-11-test,step-12-test,step-13-test,step-14-test,step-15-test,step-16-test,step-17-test,step-18-test,step-19-test,step-20-test diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index d1ea222e0..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Run Tests - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [16.x, 18.x] - - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - run: npm i - - run: npm run test:1 - - run: npm run test:2 - - run: npm run test:3 - - run: npm run test:4 - - run: npm run test:5 - - run: npm run test:6 - - run: npm run test:7 - - run: npm run test:8 - - run: npm run test:9 - - run: npm run test:10 - - run: npm run test:11 - - run: npm run test:12 - - run: npm run test:13 - - run: npm run test:14 - - run: npm run test:15 - - run: npm run test:16 - - run: npm run test:17 - - run: npm run test:18 - - run: npm run test:19 - - run: npm run test:20 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..b242572ef --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ] +} \ No newline at end of file diff --git a/docs/design/HLD/HLD.jpg b/docs/design/HLD/HLD.jpg deleted file mode 100644 index 72b064b60..000000000 Binary files a/docs/design/HLD/HLD.jpg and /dev/null differ diff --git a/docs/design/specs/dbSchema/.gitkeep b/docs/design/specs/dbSchema/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/design/specs/sqlParser/delete.json b/docs/design/specs/sqlParser/delete.json deleted file mode 100644 index a7731b472..000000000 --- a/docs/design/specs/sqlParser/delete.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "dialect": "PostgreSQL", - "queryType": "DELETE", - "delete": { - "from": "student", - "where": [ - { - "field": "age", - "operator": ">", - "placeholder": ":age", - "valueType": "integer" - } - ] - }, - "returning": [ - "id", - "name" - ], - "parameters": { - ":age": 25 - } -} \ No newline at end of file diff --git a/docs/design/specs/sqlParser/insert.json b/docs/design/specs/sqlParser/insert.json deleted file mode 100644 index 51ba1b62c..000000000 --- a/docs/design/specs/sqlParser/insert.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "dialect": "PostgreSQL", - "queryType": "INSERT", - "insert": { - "into": "student", - "columns": [ - "name", - "age", - "department" - ], - "values": [ - [ - "John Doe", - 22, - "Science" - ], - [ - "Jane Smith", - 20, - "Mathematics" - ] - ] - }, - "returning": [ - "id", - "name" - ] -} \ No newline at end of file diff --git a/docs/design/specs/sqlParser/select.json b/docs/design/specs/sqlParser/select.json deleted file mode 100644 index d1492488a..000000000 --- a/docs/design/specs/sqlParser/select.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "dialect": "PostgreSQL", - "with": [ - { - "name": "recursiveCTE", - "as": { - "baseQuery": { - "select": [ - "id", - "parent_id", - "name" - ], - "from": [ - { - "table": "categories", - "alias": "c" - } - ], - "where": [ - { - "field": "c.parent_id", - "operator": "IS", - "value": "NULL", - "valueType": "raw" - } - ] - }, - "recursiveQuery": { - "select": [ - "c.id", - "c.parent_id", - "c.name" - ], - "from": [ - { - "table": "categories", - "alias": "c" - } - ], - "join": { - "table": "recursiveCTE", - "alias": "r", - "on": [ - { - "left": "c.parent_id", - "right": "r.id", - "operator": "=" - } - ] - } - } - } - } - ], - "select": [ - { - "field": "student.name", - "alias": "studentName" - }, - { - "field": "enrollment.course", - "alias": "course" - }, - { - "subquery": { - "select": [ - "COUNT(*)" - ], - "from": [ - { - "table": "exam_results", - "alias": "er" - } - ], - "where": [ - { - "field": "er.student_id", - "operator": "=", - "fieldFromOuterQuery": "s.id" - } - ] - }, - "alias": "examCount" - } - ], - "from": [ - { - "table": "student", - "alias": "s", - "indexHint": "USE INDEX (idx_student_name)" - }, - { - "table": "recursiveCTE", - "alias": "r" - } - ], - "joins": [ - { - "type": "INNER", - "table": "enrollment", - "alias": "e", - "on": [ - { - "left": "s.id", - "right": "e.student_id", - "operator": "=" - }, - { - "left": "s.department", - "right": "e.department", - "operator": "=", - "logic": "AND" - } - ], - "indexHint": "FORCE INDEX (idx_enrollment_date)" - } - ], - "where": [ - { - "field": "s.age", - "operator": ">", - "placeholder": ":age", - "valueType": "integer" - } - ], - "groupBy": [ - "e.department" - ], - "having": [ - { - "field": "COUNT(*)", - "operator": ">", - "placeholder": ":count", - "valueType": "integer" - } - ], - "orderBy": [ - { - "field": "studentName", - "direction": "ASC" - } - ], - "limit": ":limit", - "offset": ":offset", - "parameters": { - ":age": 20, - ":count": 5, - ":limit": 10, - ":offset": 0 - }, - "performanceHints": { - "useIndex": [ - "student (idx_student_age)" - ], - "optimizeFor": "speed", - "avoidFullTableScans": true - } -} \ No newline at end of file diff --git a/docs/design/specs/sqlParser/update.json b/docs/design/specs/sqlParser/update.json deleted file mode 100644 index 0a11f83c4..000000000 --- a/docs/design/specs/sqlParser/update.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "dialect": "PostgreSQL", - "queryType": "UPDATE", - "update": { - "table": "student", - "set": [ - { - "field": "name", - "value": ":newName", - "valueType": "string" - }, - { - "field": "age", - "value": ":newAge", - "valueType": "integer" - } - ], - "where": [ - { - "field": "id", - "operator": "=", - "placeholder": ":id", - "valueType": "integer" - } - ] - }, - "returning": [ - "id", - "name", - "age" - ], - "parameters": { - ":newName": "Jane Doe", - ":newAge": 23, - ":id": 1 - } -} \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md deleted file mode 100644 index ed966d64b..000000000 --- a/docs/readme.md +++ /dev/null @@ -1,81 +0,0 @@ -## SQL Query Engine over CSV in JavaScript - -Let's start building a SQL query engine over CSV in JavaScript. We'll use Node.js for this project. - -This project will be complete in 20 steps and will take about 2-20 hours to complete depending on your level of expertise. - -- [x] Step 1: Setting up the Project -- [x] Step 2: Create a CSV Reading Function -- [x] Step 3: Building a Basic SQL Query Parser -- [x] Step 4: Integrating CSV Reading with Query Parsing -- [x] Step 5: Adding Support for WHERE Clauses -- [x] Step 6: Handling multiple conditions in WHERE clause -- [x] Step 7: Handling Different Comparison Operators -- [x] Step 8: Adding INNER JOIN support -- [x] Step 9: Adding LEFT and RIGHT JOIN support -- [x] Step 10: Group By and Aggregate Functions -- [x] Step 11: Implementing ORDER BY Clause -- [x] Step 12: Adding LIMIT Clause -- [x] Step 13: Error Handling and Validation -- [x] Step 14: Implementing DISTINCT Keyword -- [x] Step 15: Adding Support for LIKE Operator -- [x] Step 16: Adding CI Support -- [x] Step 17: Basic INSERT Statement Support -- [x] Step 18: Basic DELETE Statement Support -- [x] Step 19: CLI Integration -- [x] Step 20: Packaging and Publishing - - -## Refactoring and Code Cleanup -This will be done post Step 20 to ensure that the code is clean and readable. This will also be a good time to add tests and documentation. - - -## Next Steps -There is a laundry list of features and improvements that can be added to this project. Here are some ideas for further development. The objective is to always add more features in a similar format - A tutorial-style guide with step-by-step instructions. A lot of these are challenging and should be broken down into smaller steps. - -- [ ] Implementing SET Operations (UNION, INTERSECT, EXCEPT) -- [ ] INSERT Statement Support: Implement the capability to insert new data into existing CSV files. This includes parsing INSERT SQL statements and updating the CSV file while ensuring data integrity. -- [ ] Enhancing the Parser for Subqueries -- [ ] Data Update and Delete Operations: Along with INSERT, support UPDATE and DELETE operations. This allows for full data manipulation capabilities, similar to a traditional database. -- [ ] Schema Definition and Evolution: Provide functionality for defining a schema for CSV files (column names, data types, etc.) and mechanisms to evolve the schema over time (adding/removing columns, changing data types). -- [ ] Schema Validation: Include features to validate data against the defined schema during insertions and updates, ensuring data quality and consistency. -Data Integrity Constraints: Implement constraints like primary keys, unique constraints, and foreign keys. This would require additional logic for constraint enforcement during data modifications. -- [ ] ACID Properties Compliance: Aim to bring the system closer to compliance with ACID (Atomicity, Consistency, Isolation, Durability) properties, enhancing its reliability and robustness. -- [ ] Data Compression and Storage Optimization: Introduce data compression techniques to reduce the storage footprint of CSV files, especially important for large datasets. -- [ ] Bulk Data Insertion and Modification: Develop functionality for handling bulk data operations efficiently, which is crucial for large-scale data processing. -- [ ] Data Partitioning and Sharding: Implement data partitioning and sharding for handling very large CSV files, improving performance and manageability. -- [ ] Row-level Security: Add features for row-level security to restrict data access at a granular level, based on user roles or other criteria. -- [ ] High Availability and Fault Tolerance: Build mechanisms for ensuring high availability and fault tolerance, such as replicating data across multiple locations. -- [ ] Data Auditing Features: Introduce data auditing capabilities to track who made what changes and when, which is important for compliance and security. -- [ ] Disaster Recovery Mechanisms: Develop a system for backing up data and schemas, along with recovery procedures in case of data loss or corruption. This could involve regular snapshots of the CSV files and schema definitions. -- [ ] Transaction Log for Data Recovery: Maintain a transaction log to record all data manipulation operations. This can be used for point-in-time recovery and auditing purposes. -- [ ] Support for Indexing: Develop advanced indexing mechanisms like B-trees or hash indexes for faster query processing. This can significantly improve the performance of SELECT queries, especially on large CSV files. -- [ ] Query Optimization Engine: Implement a query optimizer that rewrites queries for optimal execution. This could include optimizing join orders, using indexes effectively, or simplifying complex queries. -- [ ] Custom Function Support: Allow users to define custom functions in JavaScript that can be used within SQL queries. This would add a layer of flexibility and power to the query engine. -- [ ] Data Type Casting and Conversion: Implement features for automatic or manual data type casting and conversion. This is crucial for handling different data types present in CSV files. -- [ ] Parallel Query Processing: Introduce multi-threading or parallel processing capabilities to handle large datasets more efficiently. This would enable the engine to execute multiple parts of a query in parallel, reducing overall query time. -- [ ] Custom Function Support: Allow users to define custom functions in JavaScript that can be used within SQL queries. This would add a layer of flexibility and power to the query engine. -- [ ] Regular Expression Support in Queries: Add support for using regular expressions in WHERE clauses, providing more powerful data filtering capabilities. -- [ ] Full-text Search Capability: Incorporate a full-text search feature, which is essential for efficiently searching through large text data. -- [ ] Data Import/Export Features: Allow importing data from and exporting data to different formats like JSON, XML, or even other databases. -- [ ] Performance Monitoring: Develop a system for monitoring query performance and logging slow queries. This could help in identifying bottlenecks and areas for improvement. -- [ ] Automatic Query Caching: Implement a caching mechanism that automatically caches frequent queries or query results for faster retrieval. -- [ ] Support for Transactions: Add basic transaction support with features like commit, rollback, and transaction logs. This would be particularly challenging but also a unique feature for a CSV-based query engine. -- [ ] Advanced Analytics: Incorporate more complex statistical functions and operations, making the engine useful for data analysis tasks. -- [ ] Security Features: Implement security features like query validation, SQL injection prevention, and access control mechanisms. -- [ ] Optimized Storage Formats: Add support for optimized storage formats for CSV files, like columnar storage, to enhance read performance. - -### Process to a new step -This project is built to be understood by someone with basic knowledge of JavaScript and SQL and then following the steps. Ensure that the documentation is updated with each step and uses the same style, format, and structure as the previous steps. Best if it can use some of the older _gyan_ as well. - -Checklist -- Find a feature that you would want to implement. -- Break it down into steps such that each step can be completed in at most 20 mins. -- Create a new GitHub issue for the feature. -- Get the issue reviewed and approved by a maintainer and get it assigned. -- Create a new branch for the step. -- Implement the step. -- Create a PR for the step. -- Get the implementation, tests and documentation approved. -- Get the PR merged. - diff --git a/docs/tutorials/01.md b/docs/tutorials/01.md deleted file mode 100644 index 5faf47779..000000000 --- a/docs/tutorials/01.md +++ /dev/null @@ -1,54 +0,0 @@ -## Step 1: Project Setup and Basic Test - -In this step, we setup the basic structure and testing for our project. - -### 1.1 Create a New Node.js Project -- Open your terminal. -- Navigate to the directory where you want to create your project. -- Run the following command to create a `package.json` file with default values: -```bash -npm init -y -``` - -### 1.2 Install Necessary Packages -- We will use `csv-parser` for CSV parsing and `jest` for testing. Install both packages using: -```bash -npm install csv-parser jest --save-dev -``` - -### 1.3 Create a Basic Project Structure -- Create a directory named `src`. -- Inside `src`, create a file named `index.js`. This will be your main file. - -### 1.4 Configure Jest -- Add a Jest configuration to your `package.json` file. Add the following under the `scripts` section: -```json -"scripts": { - "test": "jest" -} -``` - -- Create a directory named `tests` at the root of your project. -- Inside `tests`, create a file named `index.test.js` for your Jest tests. - -### 1.5 Write a Basic Jest Test -- In `index.test.js`, write a simple test to check your Jest setup. Here's a basic test example: - -```javascript -// tests/index.test.js - -test('Basic Jest Test', () => { - expect(1).toBe(1); -}); -``` - -### 1.6 Run Your Jest Test -- To run the test, navigate to your project root in the terminal. -- Execute the test using the following command: - -```bash -npm test -``` - -You should see the test passing in the console. -This completes the initial setup with Jest testing. Make sure everything works up to this point before moving to the next step. \ No newline at end of file diff --git a/docs/tutorials/02.md b/docs/tutorials/02.md deleted file mode 100644 index c4bc3c742..000000000 --- a/docs/tutorials/02.md +++ /dev/null @@ -1,67 +0,0 @@ -## Step 2: Reading a CSV File - -In this step we add the functionality to read from CSV files, since our DB would read from and write to CSV files, considering them as tables. - -### 2.1 Create a Sample CSV File -Create a new CSV file in your project directory. Let's call it `sample.csv` and add some sample data to it: - -```csv -id,name,age -1,John,30 -2,Jane,25 -3,Bob,22 -``` - -### 2.2 Create a CSV Reading Function -- In the `src` directory, create a new file named `csvReader.js`. -- Implement a function to read the CSV file using `csv-parser`. - -```javascript -// src/csvReader.js - -const fs = require('fs'); -const csv = require('csv-parser'); - -function readCSV(filePath) { - const results = []; - - return new Promise((resolve, reject) => { - fs.createReadStream(filePath) - .pipe(csv()) - .on('data', (data) => results.push(data)) - .on('end', () => { - resolve(results); - }) - .on('error', (error) => { - reject(error); - }); - }); -} - -module.exports = readCSV; -``` - -### 2.3 Update Test to Check CSV Reading -Modify the test in `tests/index.test.js` to include a test for reading the CSV file. - -Example test: - -```javascript -// tests/index.test.js - -const readCSV = require('../src/csvReader'); - -test('Read CSV File', async () => { - const data = await readCSV('./sample.csv'); - expect(data.length).toBeGreaterThan(0); - expect(data.length).toBe(3); - expect(data[0].name).toBe('John'); - expect(data[0].age).toBe('30'); //ignore the string type here, we will fix this later -}); -``` - -### 2.4 Run the Updated Test -Run the test using `npm test`. Ensure the test passes and correctly reads the CSV file. - - - diff --git a/docs/tutorials/03.md b/docs/tutorials/03.md deleted file mode 100644 index c6e1dc18b..000000000 --- a/docs/tutorials/03.md +++ /dev/null @@ -1,54 +0,0 @@ -## Step 3: Building a Basic SQL Query Parser - -In this section we will implement the basic functionality for our DB to make our DB understand SQL so that we can query data from it using SQL. - -### 3.1 Create the Parser Function -In the `src` directory, create a new file named `queryParser.js` - -Write a function that takes a SQL query string and parses it using regex to identify the `SELECT` fields and the `FROM` table name. - -Example implementation: -```javascript -// src/queryParser.js - -function parseQuery(query) { - const selectRegex = /SELECT (.+) FROM (.+)/i; - const match = query.match(selectRegex); - - if (match) { - const [, fields, table] = match; - return { - fields: fields.split(',').map(field => field.trim()), - table: table.trim() - }; - } else { - throw new Error('Invalid query format'); - } -} - -module.exports = parseQuery; -``` - -### 3.2 Update Test to Check Query Parsing -Modify the test in `tests/index.test.js` to include a test for the SQL query parsing. -Example test: - -```javascript -// tests/index.test.js - -const parseQuery = require('../src/queryParser'); - -test('Parse SQL Query', () => { - const query = 'SELECT id, name FROM sample'; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['id', 'name'], - table: 'sample' - }); -}); -``` -> **💡Do it yourself:** Try writing a test for the case the above function would throw an error. - - -### 3.3 Run the Updated Test -Run the test using `npm test`. Ensure the test passes and correctly parses the SQL query. diff --git a/docs/tutorials/04.md b/docs/tutorials/04.md deleted file mode 100644 index 0339f1812..000000000 --- a/docs/tutorials/04.md +++ /dev/null @@ -1,57 +0,0 @@ -## Step 4: Integrating CSV Reading with Query Parsing - -In this step we integrate the functionalities implemented in the previous two steps together to get a basic `SELECT` query working in our DB. - -### 4.1 Create the Query Execution Function -- In `src/index.js`, rename the function to `executeSELECTQuery` to indicate its specific purpose. -- Ensure the function handles only SELECT queries. -Here's a basic implementation: - -```javascript -// src/index.js - -const parseQuery = require('./queryParser'); -const readCSV = require('./csvReader'); - -async function executeSELECTQuery(query) { - const { fields, table } = parseQuery(query); - const data = await readCSV(`${table}.csv`); - - // Filter the fields based on the query - return data.map(row => { - const filteredRow = {}; - fields.forEach(field => { - filteredRow[field] = row[field]; - }); - return filteredRow; - }); -} - -module.exports = executeSELECTQuery; -``` - -> **💡Do it yourself:** Find and list out conditions where this function can error out and handle them. - -### 4.2 Update Test to Check Query Execution -Modify the test in `tests/index.test.js` to include a test for executing the SQL query. -Example test: - -```javascript -// tests/index.test.js - -const executeSELECTQuery = require('../src/index'); - -test('Execute SQL Query', async () => { - const query = 'SELECT id, name FROM sample'; - const result = await executeSELECTQuery(query); - expect(result.length).toBeGreaterThan(0); - expect(result[0]).toHaveProperty('id'); - expect(result[0]).toHaveProperty('name'); - expect(result[0]).not.toHaveProperty('age'); - expect(result[0]).toEqual({ id: '1', name: 'John' }); -}); -``` - -### 4.3 Run the Updated Test -Run the test using `npm test`. -Ensure the test passes and correctly executes the SQL query, returning the filtered data. diff --git a/docs/tutorials/05.md b/docs/tutorials/05.md deleted file mode 100644 index 291552cba..000000000 --- a/docs/tutorials/05.md +++ /dev/null @@ -1,100 +0,0 @@ -## Step 5: Adding WHERE Clause Handling to the Parser - -In this step we add filtering capabilities by adding `WHERE` clause parsing to our DB. - -### 5.1 Update the Parser to Handle WHERE Clauses -- In the `src` directory, update `queryParser.js` to parse the `WHERE` clause from the SQL query. -The parser should be able to extract the condition in a simple format, like `field = value`. - -Here's an updated implementation: - -```javascript -// src/queryParser.js - -function parseQuery(query) { - const selectRegex = /SELECT (.+?) FROM (.+?)(?: WHERE (.*))?$/i; - const match = query.match(selectRegex); - - if (match) { - const [, fields, table, whereClause] = match; - return { - fields: fields.split(',').map(field => field.trim()), - table: table.trim(), - whereClause: whereClause ? whereClause.trim() : null - }; - } else { - throw new Error('Invalid query format'); - } -} - -module.exports = parseQuery; -``` - -### 5.2 Run the Updated Tests -- Run the tests using `npm test`. Ensure all tests pass, particularly the new test for WHERE clause parsing. -- If you see your older test fail, update that to include `"whereClause": null` in the expected output. - -### 5.3 Update the Execute Function to apply `WHERE` clauses -Modify `executeSELECTQuery` in `src/index.js` to filter results based on the `WHERE` clause. - -Example implementation: - -```javascript -// src/index.js - -const parseQuery = require('./queryParser'); -const readCSV = require('./csvReader'); - -async function executeSELECTQuery(query) { - const { fields, table, whereClause } = parseQuery(query); - const data = await readCSV(`${table}.csv`); - - // Filtering based on WHERE clause - const filteredData = whereClause - ? data.filter(row => { - const [field, value] = whereClause.split('=').map(s => s.trim()); - return row[field] === value; - }) - : data; - - // Selecting the specified fields - return filteredData.map(row => { - const selectedRow = {}; - fields.forEach(field => { - selectedRow[field] = row[field]; - }); - return selectedRow; - }); -} - -module.exports = executeSELECTQuery; -``` - -> **💡Ask yourself:** Is the above implementation case-insensitive? - -### 5.3 Update Tests for `WHERE` Clause -Modify the tests in `tests/index.test.js` to include tests for queries with `WHERE` clauses. - -Example test: -```javascript -// tests/index.test.js - -const executeSELECTQuery = require('../src/index'); - -test('Execute SQL Query with WHERE Clause', async () => { - const query = 'SELECT id, name FROM sample WHERE age = 25'; - const result = await executeSELECTQuery(query); - expect(result.length).toBe(1); - expect(result[0]).toHaveProperty('id'); - expect(result[0]).toHaveProperty('name'); - expect(result[0].id).toBe('2'); -}); -``` - -> **💡Do it yourself:** Add tests where the above -implementation would fail and to check for its case sensitivity/insensitivity. - -### 5.4 Run the Updated Tests -- Run the tests using `npm test` - - diff --git a/docs/tutorials/06.md b/docs/tutorials/06.md deleted file mode 100644 index 69405e509..000000000 --- a/docs/tutorials/06.md +++ /dev/null @@ -1,114 +0,0 @@ -## Step 6: Handling multiple conditions in WHERE clause - -In this step we add the functionality to add multiple filters in our SQL query. - -### 6.1 Update the Parser for Complex WHERE Clauses -- Modify `queryParser.js` to handle multiple conditions in the `WHERE` clause. -- The parser should split the conditions and identify the logical operators. - -Here's an example implementation: - -```javascript -// src/queryParser.js - -function parseQuery(query) { - const selectRegex = /SELECT (.+?) FROM (.+?)(?: WHERE (.*))?$/i; - const match = query.match(selectRegex); - - if (match) { - const [, fields, table, whereString] = match; - const whereClauses = whereString ? parseWhereClause(whereString) : []; - return { - fields: fields.split(',').map(field => field.trim()), - table: table.trim(), - whereClauses - }; - } else { - throw new Error('Invalid query format'); - } -} - -function parseWhereClause(whereString) { - const conditions = whereString.split(/ AND | OR /i); - return conditions.map(condition => { - const [field, operator, value] = condition.split(/\s+/); - return { field, operator, value }; - }); -} - -module.exports = parseQuery; -``` - -> **💡Do it yourself:** Add error handling to the newly added function. - -### 6.2 Update the Execute Function for Complex WHERE Clauses -- Modify `executeSELECTQuery` in `src/index.js` to evaluate multiple conditions in the `WHERE` clause. - -Here's an example implementation: - -```javascript -// src/index.js - -const parseQuery = require('./queryParser'); -const readCSV = require('./csvReader'); - -async function executeSELECTQuery(query) { - const { fields, table, whereClauses } = parseQuery(query); - const data = await readCSV(`${table}.csv`); - - // Apply WHERE clause filtering - const filteredData = whereClauses.length > 0 - ? data.filter(row => whereClauses.every(clause => { - // You can expand this to handle different operators - return row[clause.field] === clause.value; - })) - : data; - - // Select the specified fields - return filteredData.map(row => { - const selectedRow = {}; - fields.forEach(field => { - selectedRow[field] = row[field]; - }); - return selectedRow; - }); -} - -module.exports = executeSELECTQuery; -``` - -### 6.3 Update Tests for Complex WHERE Clauses -- Modify the tests in `tests/index.test.js` to include queries with complex WHERE clauses. - -Example test: - -```javascript -test('Parse SQL Query with Multiple WHERE Clauses', () => { - const query = 'SELECT id, name FROM sample WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['id', 'name'], - table: 'sample', - whereClauses: [{ - "field": "age", - "operator": "=", - "value": "30", - }, { - "field": "name", - "operator": "=", - "value": "John", - }] - }); -}); - -test('Execute SQL Query with Multiple WHERE Clause', async () => { - const query = 'SELECT id, name FROM sample WHERE age = 30 AND name = John'; - const result = await executeSELECTQuery(query); - expect(result.length).toBe(1); - expect(result[0]).toEqual({ id: '1', name: 'John' }); -}); -``` - -> **💡Do it yourself:** Add test cases where the implementation might throw errors and make sure they are handled. - -- Verify if all 7 of the tests are working fine by running `npm test`. If not update the older tests to work with the new parser. diff --git a/docs/tutorials/07.md b/docs/tutorials/07.md deleted file mode 100644 index 54fe8687d..000000000 --- a/docs/tutorials/07.md +++ /dev/null @@ -1,76 +0,0 @@ -## Step 7: Handling Different Comparison Operators - -In this step we move further ahead from the realm on equalities to the realm of inequalities by implementing support for different comparison operators in our query parser. - -### 7.1 Update the Parser to Recognize Comparison Operators -- Modify `parseWhereClause` function to handle a variety of comparison operators (`=`, `!=`, `>`, `<`, `>=`, `<=`). -- The parser should identify the operator and split the condition accordingly. - -Example implementation: - -```javascript -// src/queryParser.js -function parseWhereClause(whereString) { - const conditionRegex = /(.*?)(=|!=|>|<|>=|<=)(.*)/; - return whereString.split(/ AND | OR /i).map(conditionString => { - const match = conditionString.match(conditionRegex); - if (match) { - const [, field, operator, value] = match; - return { field: field.trim(), operator, value: value.trim() }; - } - throw new Error('Invalid WHERE clause format'); - }); -} -``` - -### 7.2 Update the Execute Function to Apply Different Operators -- Modify `executeSELECTQuery` in `src/index.js` to evaluate conditions using the identified operators. - -Example implementation: - -```javascript -// src/index.js -function evaluateCondition(row, clause) { - const { field, operator, value } = clause; - switch (operator) { - case '=': return row[field] === value; - case '!=': return row[field] !== value; - case '>': return row[field] > value; - case '<': return row[field] < value; - case '>=': return row[field] >= value; - case '<=': return row[field] <= value; - default: throw new Error(`Unsupported operator: ${operator}`); - } -} -``` -- Update the filteredData assignment to use the new `evaluateCondition` function. -```javascript - const filteredData = whereClauses.length > 0 - ? data.filter(row => whereClauses.every(clause => evaluateCondition(row, clause))) - : data; -``` - - -### 7.3 Update Tests for Different Comparison Operators -Modify the tests in `tests/index.test.js` to include queries with different comparison operators and run the tests using `npm test`. - -Example test: - -```javascript -// tests/index.test.js -test('Execute SQL Query with Greater Than', async () => { - const queryWithGT = 'SELECT id FROM sample WHERE age > 22'; - const result = await executeSELECTQuery(queryWithGT); - expect(result.length).toEqual(2); - expect(result[0]).toHaveProperty('id'); -}); - -test('Execute SQL Query with Not Equal to', async () => { - const queryWithGT = 'SELECT name FROM sample WHERE age != 25'; - const result = await executeSELECTQuery(queryWithGT); - expect(result.length).toEqual(2); - expect(result[0]).toHaveProperty('name'); -}); -``` - -> **💡Do it yourself:** Add negative test cases for the above implementation. \ No newline at end of file diff --git a/docs/tutorials/08.md b/docs/tutorials/08.md deleted file mode 100644 index 123aac444..000000000 --- a/docs/tutorials/08.md +++ /dev/null @@ -1,210 +0,0 @@ -## Step 8: Inner Join - -In this step we implement the `JOIN` functionality from SQL. - -### 8.1 Preparing CSV Files for INNER JOIN -- Rename `sample.csv` to `student.csv` -- Update the tests in `tests/index.test.js` to use `student.csv` instead of `sample.csv` and update the expected results accordingly. - -### 8.2 Create a new Enrollment CSV File -This setup gives us two CSV files: one for student information and another for their course enrollments. We can now use these files to demonstrate the INNER JOIN functionality in the next step. -Create a new file named `enrollment.csv` in the root of your project and add the following contents to it. - -```csv -student_id,course -1,Mathematics -1,Physics -2,Chemistry -3,Mathematics -``` - -### 8.3 Update the Parser to Handle JOIN Clauses -- Define what the parser should output for a `JOIN` query. -Assuming the following `JOIN` query: -```sql -SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id -``` -```json -{ - "fields": ["student.name", "enrollment.course"], - "table": "student", - "whereClauses": [], - "joinTable": "enrollment", - "joinCondition": { - "left": "student.id", - "right": "enrollment.student_id" - } -} -``` - -- When there is a `WHERE` clause it would be like this: -```sql -SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id WHERE student.name = John -``` - -```json -{ - "fields": ["student.name", "enrollment.course"], - "table": "student", - "whereClauses": [{ - "field": "student.name", - "operator": "=", - "value": "John" - }], - "joinTable": "enrollment", - "joinCondition": { - "left": "student.id", - "right": "enrollment.student_id" - } -} -``` - -Now, modify `queryParser.js` to parse `JOIN` clauses in the SQL query. -The parser should identify the tables and the join condition. - -Example implementation: - -```javascript -// src/queryParser.js - -function parseQuery(query) { - // First, let's trim the query to remove any leading/trailing whitespaces - query = query.trim(); - - // Initialize variables for different parts of the query - let selectPart, fromPart; - - // Split the query at the WHERE clause if it exists - const whereSplit = query.split(/\sWHERE\s/i); - query = whereSplit[0]; // Everything before WHERE clause - - // WHERE clause is the second part after splitting, if it exists - const whereClause = whereSplit.length > 1 ? whereSplit[1].trim() : null; - - // Split the remaining query at the JOIN clause if it exists - const joinSplit = query.split(/\sINNER JOIN\s/i); - selectPart = joinSplit[0].trim(); // Everything before JOIN clause - - // JOIN clause is the second part after splitting, if it exists - const joinPart = joinSplit.length > 1 ? joinSplit[1].trim() : null; - - // Parse the SELECT part - const selectRegex = /^SELECT\s(.+?)\sFROM\s(.+)/i; - const selectMatch = selectPart.match(selectRegex); - if (!selectMatch) { - throw new Error('Invalid SELECT format'); - } - - const [, fields, table] = selectMatch; - - // Parse the JOIN part if it exists - let joinTable = null, joinCondition = null; - if (joinPart) { - const joinRegex = /^(.+?)\sON\s([\w.]+)\s*=\s*([\w.]+)/i; - const joinMatch = joinPart.match(joinRegex); - if (!joinMatch) { - throw new Error('Invalid JOIN format'); - } - - joinTable = joinMatch[1].trim(); - joinCondition = { - left: joinMatch[2].trim(), - right: joinMatch[3].trim() - }; - } - - // Parse the WHERE part if it exists - let whereClauses = []; - if (whereClause) { - whereClauses = parseWhereClause(whereClause); - } - - return { - fields: fields.split(',').map(field => field.trim()), - table: table.trim(), - whereClauses, - joinTable, - joinCondition - }; -} -``` - -### 8.4 Update the Execute Function for INNER JOIN -Modify `executeSELECTQuery` in `src/index.js` to perform an `INNER JOIN` operation. Given that JOIN are generally performed before the where clause, we can apply the WHERE clause filtering after the JOIN operation. - -Example implementation: -```javascript -// src/index.js at executeSELECTQuery - -// Now we will have joinTable, joinCondition in the parsed query -const { fields, table, whereClauses, joinTable, joinCondition } = parseQuery(query); -let data = await readCSV(`${table}.csv`); - -// Perform INNER JOIN if specified -if (joinTable && joinCondition) { - const joinData = await readCSV(`${joinTable}.csv`); - data = data.flatMap(mainRow => { - return joinData - .filter(joinRow => { - const mainValue = mainRow[joinCondition.left.split('.')[1]]; - const joinValue = joinRow[joinCondition.right.split('.')[1]]; - return mainValue === joinValue; - }) - .map(joinRow => { - return fields.reduce((acc, field) => { - const [tableName, fieldName] = field.split('.'); - acc[field] = tableName === table ? mainRow[fieldName] : joinRow[fieldName]; - return acc; - }, {}); - }); - }); -} - -// Apply WHERE clause filtering after JOIN (or on the original data if no join) -const filteredData = whereClauses.length > 0 - ? data.filter(row => whereClauses.every(clause => evaluateCondition(row, clause))) - : data; -``` - -This would also require us to update the `SELECT` logic to handle the new `fields` format which should now work for both `SELECT` (`id`) and `SELECT JOIN` (`student.id`) queries. - -Sample implementation: -```javascript -// src/index.js at executeSELECTQuery - -filteredData.map(row => { - const selectedRow = {}; - fields.forEach(field => { - // Assuming 'field' is just the column name without table prefix - selectedRow[field] = row[field]; - }); - return selectedRow; -``` - -Verify if the results are as expected manually. - -```json -/* - result = [ - { 'student.name': 'John', 'enrollment.course': 'Mathematics' }, - { 'student.name': 'John', 'enrollment.course': 'Physics' }, - { 'student.name': 'Jane', 'enrollment.course': 'Chemistry' }, - { 'student.name': 'Bob', 'enrollment.course': 'Mathematics' } - ] -*/ -``` - -### 8.5 Update Tests for INNER JOIN - -Modify the tests in `tests/index.test.js` to include tests for `INNER JOIN` queries. Add these 4 tests: - -```javascript -test('Parse SQL Query with INNER JOIN', async () => {/*implement*/}); -test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => {/*implement*/}); -test('Execute SQL Query with INNER JOIN', async () => {/*implement*/}); -test('Execute SQL Query with INNER JOIN and a WHERE Clause', async () => {/*implement*/}); -``` - -Make sure the tests pass. If you are unsure take a peek at the tests in the `08` commit. - -> **💡Do it yourself:** Implement the tests mentioned in the code block above and also figure out negative test cases. diff --git a/docs/tutorials/09.md b/docs/tutorials/09.md deleted file mode 100644 index 86ca209ed..000000000 --- a/docs/tutorials/09.md +++ /dev/null @@ -1,131 +0,0 @@ -## Step 9: Implementing LEFT and RIGHT JOINs - -In this step we extend the `JOIN` functionality of our database to add support for `LEFT` and `RIGHT` joins as well. - -### 9.0 Update the CSV Files -- Update the CSV Files to include the following data: - -> `enrollment.csv` would become -```csv -student_id,course -1,Mathematics -1,Physics -2,Chemistry -3,Mathematics -5,Biology -``` - -and -> `student.csv` would become -```csv -id,name,age -1,John,30 -2,Jane,25 -3,Bob,22 -4,Alice,24 -``` - -This will ensure that that the `JOIN` queries can now have data that is not present in both tables. - -> 💡**Ask yourself**: Why was this update necessary? - - -### 9.1 Update the Parser to Recognize Different JOIN Types -- Modify `queryParser.js` to differentiate between `INNER JOIN`, `LEFT JOIN`, and `RIGHT JOIN`. Adjust the regex and logic to capture the type of JOIN. - -Updated `queryParser.js`: - -```javascript -// src/queryParser.js - -// ...existing code... - -function parseJoinClause(query) { - const joinRegex = /\s(INNER|LEFT|RIGHT) JOIN\s(.+?)\sON\s([\w.]+)\s*=\s*([\w.]+)/i; - const joinMatch = query.match(joinRegex); - - if (joinMatch) { - return { - joinType: joinMatch[1].trim(), - joinTable: joinMatch[2].trim(), - joinCondition: { - left: joinMatch[3].trim(), - right: joinMatch[4].trim() - } - }; - } - - return { - joinType: null, - joinTable: null, - joinCondition: null - }; -} - -// Update the parseQuery function to use parseJoinClause -// ...existing code... - -module.exports = { parseQuery, parseJoinClause }; -``` - -> 💡**Do it yourself**: Remember to update the `parseQuery` function to use the `parseJoinClause` function implemented above - -### 9.2 Updating the Execute Function for Different JOIN Types -- Modify `executeSELECTQuery` in `src/index.js` to handle `LEFT JOIN` and `RIGHT JOIN` alongside `INNER JOIN`. Implement `performLeftJoin` and `performRightJoin` functions to encapsulate the specific logic for these JOIN types. - -```javascript -// src/index.js - -// ...existing imports... - -// Helper functions for different JOIN types -function performInnerJoin(/* parameters */) { - // Logic for INNER JOIN - // ... -} - -function performLeftJoin(/* parameters */) { - // Logic for LEFT JOIN - // ... -} - -function performRightJoin(/* parameters */) { - // Logic for RIGHT JOIN - // ... -} - -async function executeSELECTQuery(query) { - const { fields, table, whereClauses, joinType, joinTable, joinCondition } = parseQuery(query); - let data = await readCSV(`${table}.csv`); - - // Logic for applying JOINs - if (joinTable && joinCondition) { - const joinData = await readCSV(`${joinTable}.csv`); - switch (joinType.toUpperCase()) { - case 'INNER': - data = performInnerJoin(data, joinData, joinCondition, fields, table); - break; - case 'LEFT': - data = performLeftJoin(data, joinData, joinCondition, fields, table); - break; - case 'RIGHT': - data = performRightJoin(data, joinData, joinCondition, fields, table); - break; - // Handle default case or unsupported JOIN types - } - } - - // ...existing code for WHERE clause and field selection... -} - -module.exports = executeSELECTQuery; -``` - -> 💡**Do it yourself**: Implement the `performInnerJoin`, `performLeftJoin`, `performRightJoin` and update the `executeSELECTQuery` function. - -### 9.3 Adding Tests for LEFT and RIGHT JOINs -- Given the number of tests that we will have, would be good to refactor them in separate files. Consider creating `csvReader.test.js`, `queryExecutor.test.js`, and `queryParser.test.js` files in the `tests` folder and segrate the tests accordingly. - -> 💡**Do it yourself**: Refactor tests - -The following tests should pass now - [Link to the Commit](https://github.com/ChakshuGautam/stylusdb-sql/commit/7d4877d09055da7ef63ee6f2321db2e3fa87ad24) \ No newline at end of file diff --git a/docs/tutorials/10.md b/docs/tutorials/10.md deleted file mode 100644 index 75f093b0a..000000000 --- a/docs/tutorials/10.md +++ /dev/null @@ -1,76 +0,0 @@ -## Step 10: Implementing GROUP BY and Aggregate Functions - -In this step we provide the much needed aggregation capabilities to our SQL engine by implementing support for `GROUP BY` and other aggregation functions. - -### 10.1 Update the Parser to Handle GROUP BY Clauses -- Modify `queryParser.js` to parse `GROUP BY` clauses in the SQL query. The parser should identify the fields to group by. - -Example snippet for updating `queryParser.js`: - -```javascript -// src/queryParser.js - -// ...existing code... - -function parseQuery(query) { - // ...existing parsing logic... - - // Updated regex to capture GROUP BY clause - const groupByRegex = /\sGROUP BY\s(.+)/i; - const groupByMatch = query.match(groupByRegex); - - let groupByFields = null; - if (groupByMatch) { - groupByFields = groupByMatch[1].split(',').map(field => field.trim()); - } - - return { - // ...existing parsed parts, - groupByFields - }; -} - -// ...remaining code... - -module.exports = { parseQuery, parseJoinClause }; -``` - -> 💡**Do it yourself**: Update the `parseQuery` function based on the hints in the above code snippet. - -### 10.2 Update the Execute Function to Apply GROUP BY and Aggregate Functions -- Modify `executeSELECTQuery` in `src/index.js` to perform grouping based on the `GROUP BY` clause and apply aggregate functions like `COUNT`, `SUM`, `AVG`, `MIN`, and `MAX`. - -Example snippet for updating `executeSELECTQuery`: -```javascript -// src/index.js - -// ...existing imports... - -// Helper function to apply GROUP BY and aggregate functions -function applyGroupBy(data, groupByFields, aggregateFunctions) { - // Implement logic to group data and calculate aggregates - // ... -} - -async function executeSELECTQuery(query) { - const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields } = parseQuery(query); - let data = await readCSV(`${table}.csv`); - - // ...existing logic for JOINs and WHERE clause... - - if (groupByFields) { - data = applyGroupBy(data, groupByFields, fields); - } - - // ...existing logic for field selection... -} - -module.exports = executeSELECTQuery; -``` - -> 💡**Do it yourself**: Implement the `applyGroupBy` function and update the `executeSELECTQuery` function based on the hints in the above code snippet. - -### 10.3 Add Tests for GROUP BY and Aggregate Functions -Update the test suite to include tests for queries using the `GROUP BY` clause and aggregate functions. [Commit for reference](https://github.com/ChakshuGautam/stylusdb-sql/commit/2df5a81650ce1f3846ec8e0b605aa2e7371dcf79) - -> 💡 **Do it yourself**: Think of both negative and positive scenarios and make sure to cover all cases. \ No newline at end of file diff --git a/docs/tutorials/11.md b/docs/tutorials/11.md deleted file mode 100644 index 02938ab60..000000000 --- a/docs/tutorials/11.md +++ /dev/null @@ -1,81 +0,0 @@ -## Step 11: Implementing ORDER BY Clause - -In this step we implement the functionality of ordering our records in a particular sequence by adding support for the `ORDER BY` SQL clause. - -### 11.1 Update the Parser to Handle ORDER BY Clauses -- Modify `queryParser.js` to parse `ORDER BY` clauses in the SQL query. The parser should identify the columns to order by and the sort direction (`ASC` or `DESC`). - -Example snippet for updating `queryParser.js`: -```javascript -// src/queryParser.js - -// ...existing code... - -function parseQuery(query) { - // ...existing parsing logic... - - // Updated regex to capture ORDER BY clause - const orderByRegex = /\sORDER BY\s(.+)/i; - const orderByMatch = query.match(orderByRegex); - - let orderByFields = null; - if (orderByMatch) { - orderByFields = orderByMatch[1].split(',').map(field => { - const [fieldName, order] = field.trim().split(/\s+/); - return { fieldName, order: order ? order.toUpperCase() : 'ASC' }; - }); - } - - return { - // ...existing parsed parts, - orderByFields - }; -} - -// ...remaining code... - -module.exports = { parseQuery, parseJoinClause }; -``` - -> 💡 **Do it yourself**: Update the `parseQuery` function to add support for `ORDER BY` clause with the help of hints in the above code snippet. - -> 💡 **Ask yourself**: Is there some scope of refactoring in the above code snippet? - - -### 11.2 Update the Execute Function to Apply ORDER BY Clause -- Modify `executeSELECTQuery` in `src/index.js` to sort the results based on the `ORDER BY` clause. - -```javascript -// src/index.js - -// ...existing imports and functions... - -async function executeSELECTQuery(query) { - const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields, orderByFields } = parseQuery(query); - let data = await readCSV(`${table}.csv`); - - // ...existing logic for JOINs, WHERE clause, and GROUP BY... - - if (orderByFields) { - data.sort((a, b) => { - for (let { fieldName, order } of orderByFields) { - if (a[fieldName] < b[fieldName]) return order === 'ASC' ? -1 : 1; - if (a[fieldName] > b[fieldName]) return order === 'ASC' ? 1 : -1; - } - return 0; - }); - } - - // ...existing logic for field selection... -} - -module.exports = executeSELECTQuery; -``` - -> 💡 **Do it yourself**: Update the `executeSELECTQuery` function to add support for `ORDER BY` clause with the help of hints in the above code snippet. - -### 11.3 Add Tests for ORDER BY Clause - -- Add tests for the `ORDER BY` clause. You can see the existing tests [here](https://github.com/ChakshuGautam/stylusdb-sql/commit/39efbc7d7a81296c58a31e5fe84224938f64bcf7) to do TDD. - -> 💡 **Ask yourself**: What is Test Drive Development? \ No newline at end of file diff --git a/docs/tutorials/12.md b/docs/tutorials/12.md deleted file mode 100644 index a3263ee6c..000000000 --- a/docs/tutorials/12.md +++ /dev/null @@ -1,71 +0,0 @@ -## Step 12: Implementing LIMIT Clause - -In this step we add support to fetch only a specific number of records as a result of the SQL Query by implementing the `LIMIT` clause. - -### 12.1 Update the Parser to Handle LIMIT Clauses -- Modify `queryParser.js` to parse `LIMIT` clauses in the SQL query. -The parser should identify the limit value. - -Example snippet for updating `queryParser.js`: - -```javascript -// src/queryParser.js - -// ...existing code... - -function parseQuery(query) { - // ...existing parsing logic... - - // Updated regex to capture LIMIT clause - const limitRegex = /\sLIMIT\s(\d+)/i; - const limitMatch = query.match(limitRegex); - - let limit = null; - if (limitMatch) { - limit = parseInt(limitMatch[1]); - } - - return { - // ...existing parsed parts, - limit - }; -} - -// ...remaining code... - -module.exports = { parseQuery, parseJoinClause }; -``` - -> 💡 **Do it yourself**: Update the `parseQuery` function to add support for `LIMIT` clause with the help of hints in the above code snippet. - -> 💡 **Ask yourself**: Is there some scope of refactoring in the above code snippet? - -### 12.2 Update the Execute Function to Apply LIMIT -Modify `executeSELECTQuery` in `src/index.js` to apply the `LIMIT` clause on the result set. -Example snippet for updating `executeSELECTQuery`: - -```javascript -// src/index.js - -// ...existing imports and functions... - -async function executeSELECTQuery(query) { - const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields, orderByFields, limit } = parseQuery(query); - let data = await readCSV(`${table}.csv`); - - // ...existing logic for JOINs, WHERE clause, GROUP BY, and ORDER BY... - - if (limit !== null) { - data = data.slice(0, limit); - } - - // ...existing logic for field selection... -} - -module.exports = executeSELECTQuery; -``` - -> 💡 **Do it yourself**: Update the `executeSELECTQuery` function to add support for `LIMIT` clause with the help of hints in the above code snippet. - -### 2.3 Add Tests for LIMIT Clause -- Update the test suite to include tests for queries using the `LIMIT` clause. [Commit for reference](https://github.com/ChakshuGautam/stylusdb-sql/commit/fd5ce77fd91e80655072fc6348d19d426fd12673) diff --git a/docs/tutorials/13.md b/docs/tutorials/13.md deleted file mode 100644 index 39ec2c803..000000000 --- a/docs/tutorials/13.md +++ /dev/null @@ -1,74 +0,0 @@ -## Step 13: Error Handling and Validation - -Every piece of software can error out and hence making it extremely crucial to handle errors and validate the data. We'll add support or error handling and validation in this step. - -### 3.1 Enhance Error Handling in Parser -- Update `parseQuery` function in `queryParser.js` to include more comprehensive error messages and checks. Ensure that it validates the structure of the SQL query and catches common syntax errors. - -Example updates for `queryParser.js`: - -```javascript -// src/queryParser.js - -// ...existing code... - -function parseQuery(query) { - try { - // ...existing parsing logic... - - // Include validation checks and throw errors with descriptive messages - if (!selectMatch) { - throw new Error("Invalid SELECT clause. Ensure it follows 'SELECT field1, field2 FROM table' format."); - } - // Add more checks as needed for JOIN, WHERE, GROUP BY, ORDER BY, and LIMIT - - return { - // ...existing parsed parts - }; - } catch (error) { - // Customize error message or log details if needed - throw new Error(`Query parsing error: ${error.message}`); - } -} - -// ...remaining code... - -module.exports = { parseQuery, parseJoinClause }; -``` - -> 💡 **Do it yourself**: Update the `parseQuery` function to add some level of error handling. - -### 13.2 Enhance Error Handling in Execute Function -- Update `executeSELECTQuery` function in `src/index.js` to handle errors gracefully. Catch errors during the execution process and provide informative messages. - -Example updates for `executeSELECTQuery`: - -```javascript -// src/index.js - -// ...existing imports and functions... - -async function executeSELECTQuery(query) { - try { - const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields, orderByFields, limit } = parseQuery(query); - - // ...existing logic for reading data and applying JOINs, WHERE, GROUP BY, ORDER BY, and LIMIT... - - // Return the final data or handle specific error cases - return data; - } catch (error) { - // Log error and provide user-friendly message - console.error("Error executing query:", error); - throw new Error(`Failed to execute query: ${error.message}`); - } -} - -module.exports = executeSELECTQuery; -``` - -> 💡 **Do it yourself**: Update the `executeSELECTQuery` to add some level of error handling. - -### 13.3 Update Tests to Cover Error Scenarios -- Add tests to cover error scenarios in the parser and execute function. Ensure that the error messages are as expected. [Commit for reference](https://github.com/ChakshuGautam/stylusdb-sql/commit/c2214a1a75de155786a54b353964235d8d17864a) - -Note that above is just an introduction and more detailed exceptions will be added later. \ No newline at end of file diff --git a/docs/tutorials/14.md b/docs/tutorials/14.md deleted file mode 100644 index d1422661e..000000000 --- a/docs/tutorials/14.md +++ /dev/null @@ -1,189 +0,0 @@ -## Step 14: Implementing DISTINCT Keyword -### 14.1 Update the Parser to Handle DISTINCT -- Modify parseQuery in `queryParser.js` to recognize the `DISTINCT` keyword in the SQL query. The parser should identify when `DISTINCT` is used and modify the parsed query accordingly. - -Example updates for queryParser.js: -```javascript -// src/queryParser.js - -// Find were to add this code -if (query.toUpperCase().includes('SELECT DISTINCT')) { - isDistinct = true; - query = query.replace('SELECT DISTINCT', 'SELECT'); -} -``` - -### 14.2 Update the Execute Function to Apply DISTINCT -Modify `executeSELECTQuery` in `src/index.js` to apply the DISTINCT keyword, removing duplicates from the result set. -Example updates for `executeSELECTQuery`: - -```javascript -// src/index.js - -// Find were to add this code -if (isDistinct) { - data = [...new Map(data.map(item => [fields.map(field => item[field]).join('|'), item])).values()]; -} - -module.exports = executeSELECTQuery; -``` - -### 14.3 Add Tests for DISTINCT Keyword -Update the `enrollment.csv` to include `5,Physics,` and `student.csv` to include `5,Jane,22`. This will ensure the following tests pass now. - -```javascript -test('Parse SQL Query with Basic DISTINCT', () => { - const query = 'SELECT DISTINCT age FROM student'; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['age'], - table: 'student', - isDistinct: true, - whereClauses: [], - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - orderByFields: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Parse SQL Query with DISTINCT and Multiple Columns', () => { - const query = 'SELECT DISTINCT student_id, course FROM enrollment'; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['student_id', 'course'], - table: 'enrollment', - isDistinct: true, - whereClauses: [], - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - orderByFields: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Parse SQL Query with DISTINCT and WHERE Clause', () => { - const query = 'SELECT DISTINCT course FROM enrollment WHERE student_id = "1"'; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['course'], - table: 'enrollment', - isDistinct: true, - whereClauses: [{ field: 'student_id', operator: '=', value: '"1"' }], - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - orderByFields: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Parse SQL Query with DISTINCT and JOIN Operations', () => { - const query = 'SELECT DISTINCT student.name FROM student INNER JOIN enrollment ON student.id = enrollment.student_id'; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['student.name'], - table: 'student', - isDistinct: true, - whereClauses: [], - groupByFields: null, - joinType: 'INNER', - joinTable: 'enrollment', - joinCondition: { - left: 'student.id', - right: 'enrollment.student_id' - }, - orderByFields: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Parse SQL Query with DISTINCT, ORDER BY, and LIMIT', () => { - const query = 'SELECT DISTINCT age FROM student ORDER BY age DESC LIMIT 2'; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['age'], - table: 'student', - isDistinct: true, - whereClauses: [], - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - orderByFields: [{ fieldName: 'age', order: 'DESC' }], - limit: 2, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Parse SQL Query with DISTINCT on All Columns', () => { - const query = 'SELECT DISTINCT * FROM student'; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['*'], - table: 'student', - isDistinct: true, - whereClauses: [], - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - orderByFields: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Basic DISTINCT Usage', async () => { - const query = 'SELECT DISTINCT age FROM student'; - const result = await executeSELECTQuery(query); - expect(result).toEqual([{ age: '30' }, { age: '25' }, { age: '22' }, { age: '24' }]); -}); - -test('DISTINCT with Multiple Columns', async () => { - const query = 'SELECT DISTINCT student_id, course FROM enrollment'; - const result = await executeSELECTQuery(query); - // Expecting unique combinations of student_id and course - expect(result).toEqual([ - { student_id: '1', course: 'Mathematics' }, - { student_id: '1', course: 'Physics' }, - { student_id: '2', course: 'Chemistry' }, - { student_id: '3', course: 'Mathematics' }, - { student_id: '5', course: 'Biology' }, - { student_id: '5', course: 'Physics' } - ]); -}); - -// Not a good test right now -test('DISTINCT with WHERE Clause', async () => { - const query = 'SELECT DISTINCT course FROM enrollment WHERE student_id = "1"'; - const result = await executeSELECTQuery(query); - // Expecting courses taken by student with ID 1 - expect(result).toEqual([{ course: 'Mathematics' }, { course: 'Physics' }]); -}); - -test('DISTINCT with JOIN Operations', async () => { - const query = 'SELECT DISTINCT student.name FROM student INNER JOIN enrollment ON student.id = enrollment.student_id'; - const result = await executeSELECTQuery(query); - // Expecting names of students who are enrolled in any course - expect(result).toEqual([{ "student.name": 'John' }, { "student.name": 'Jane' }, { "student.name": 'Bob' }]); -}); - -test('DISTINCT with ORDER BY and LIMIT', async () => { - const query = 'SELECT DISTINCT age FROM student ORDER BY age DESC LIMIT 2'; - const result = await executeSELECTQuery(query); - // Expecting the two highest unique ages - expect(result).toEqual([{ age: '30' }, { age: '25' }]); -}); -``` - -Update all the others to ensure they pass as well. \ No newline at end of file diff --git a/docs/tutorials/15.md b/docs/tutorials/15.md deleted file mode 100644 index 8a5831cc1..000000000 --- a/docs/tutorials/15.md +++ /dev/null @@ -1,141 +0,0 @@ -## Step 15: Adding Support for LIKE Operator -### 15.1 Update the Parser to Handle LIKE Clauses -Modify `parseQuery` in `queryParser.js` to recognize `LIKE`` conditions in the `WHERE`` clause. - -The parser should be capable of identifying LIKE patterns within the query. -Example updates for queryParser.js: - -```javascript -if (conditionString.includes(' LIKE ')) { - const [field, , pattern] = conditionString.split(/\sLIKE\s/i); - return { field: field.trim(), operator: 'LIKE', value: pattern.trim() }; -} -``` - -### 15.2 Update the Execute Function to Apply LIKE Conditions -Modify `executeSELECTQuery` in `src/index.js` to filter data using the LIKE operator. -Example updates for `executeSELECTQuery`: - -```javascript -// Inside operator matching logic -if (clause.operator === 'LIKE') { - // Transform SQL LIKE pattern to JavaScript RegExp pattern - const regexPattern = '^' + clause.value.replace(/%/g, '.*') + '$'; - return new RegExp(regexPattern, 'i').test(row[clause.field]); -} -``` - -### 15.3 Add Tests for LIKE Operator -Make sure the following test passes now. - -```javascript -test('Parse SQL Query with LIKE Clause', () => { - const query = "SELECT name FROM student WHERE name LIKE '%Jane%'"; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['name'], - table: 'student', - whereClauses: [{ field: 'name', operator: 'LIKE', value: '%Jane%' }], - isDistinct: false, - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - orderByFields: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Parse SQL Query with LIKE Clause and Wildcards', () => { - const query = "SELECT name FROM student WHERE name LIKE 'J%'"; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['name'], - table: 'student', - whereClauses: [{ field: 'name', operator: 'LIKE', value: 'J%' }], - isDistinct: false, - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - orderByFields: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Parse SQL Query with Multiple LIKE Clauses', () => { - const query = "SELECT name FROM student WHERE name LIKE 'J%' AND age LIKE '2%'"; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['name'], - table: 'student', - whereClauses: [ - { field: 'name', operator: 'LIKE', value: 'J%' }, - { field: 'age', operator: 'LIKE', value: '2%' } - ], - isDistinct: false, - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - orderByFields: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Parse SQL Query with LIKE and ORDER BY Clauses', () => { - const query = "SELECT name FROM student WHERE name LIKE '%e%' ORDER BY age DESC"; - const parsed = parseQuery(query); - expect(parsed).toEqual({ - fields: ['name'], - table: 'student', - whereClauses: [{ field: 'name', operator: 'LIKE', value: '%e%' }], - orderByFields: [{ fieldName: 'age', order: 'DESC' }], - isDistinct: false, - groupByFields: null, - joinType: null, - joinTable: null, - joinCondition: null, - limit: null, - hasAggregateWithoutGroupBy: false - }); -}); - -test('Execute SQL Query with LIKE Operator for Name', async () => { - const query = "SELECT name FROM student WHERE name LIKE '%Jane%'"; - const result = await executeSELECTQuery(query); - // Expecting names containing 'Jane' - expect(result).toEqual([{ name: 'Jane' }, { name: 'Jane' }]); -}); - -test('Execute SQL Query with LIKE Operator and Wildcards', async () => { - const query = "SELECT name FROM student WHERE name LIKE 'J%'"; - const result = await executeSELECTQuery(query); - // Expecting names starting with 'J' - expect(result).toEqual([{ name: 'John' }, { name: 'Jane' }, { name: 'Jane' }]); -}); - -test('Execute SQL Query with LIKE Operator Case Insensitive', async () => { - const query = "SELECT name FROM student WHERE name LIKE '%bob%'"; - const result = await executeSELECTQuery(query); - // Expecting names 'Bob' (case insensitive) - expect(result).toEqual([{ name: 'Bob' }]); -}); - -test('Execute SQL Query with LIKE Operator and DISTINCT', async () => { - const query = "SELECT DISTINCT name FROM student WHERE name LIKE '%e%'"; - const result = await executeSELECTQuery(query); - // Expecting unique names containing 'e' - expect(result).toEqual([{ name: 'Jane' }, { name: 'Alice' }]); -}); - -test('LIKE with ORDER BY and LIMIT', async () => { - const query = "SELECT name FROM student WHERE name LIKE '%a%' ORDER BY name ASC LIMIT 2"; - const result = await executeSELECTQuery(query); - // Expecting the first two names alphabetically that contain 'a' - expect(result).toEqual([{ name: 'Alice' }, { name: 'Jane' }]); -}); -``` diff --git a/docs/tutorials/16.md b/docs/tutorials/16.md deleted file mode 100644 index bca0cf6d2..000000000 --- a/docs/tutorials/16.md +++ /dev/null @@ -1,52 +0,0 @@ -## Step 16: Adding CI Support - -Let's add continuous integration (CI) support in our application using `GitHub actions` so that it auto runs the test on each commit to github and let's us know if something is off or is failing. -> **💡Do it yourself**: Read more about CI/CD. - -### 16.1 Setup directory -The code for Github Actions CI/CD resides inside the `.github/workflows` folder at the root of your project (github repository). - -Create the folder either using the GUI of your editor/file manager or you can follow the steps below to create it using the command line. - -```bash -cd /path/to/your/project/root -mkdir .github/workflows -``` - -### 16.2 Create a `tests.yml` file - -Create a file named `tests.yml` in the `.github/workflows` directory and paste the below configuration. - -```yaml -name: Run Tests - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [14.x, 16.x, 18.x] - - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - run: npm i - - run: npm test -``` - -### 16.3 Commit to github and see the tests passing - -Commit this folder to your github repository which you must have created to submit your assignment and you will see the tests passing. - -> **💡Do it yourself**: Look into toolings like `husky`. \ No newline at end of file diff --git a/docs/tutorials/17.md b/docs/tutorials/17.md deleted file mode 100644 index f88a18871..000000000 --- a/docs/tutorials/17.md +++ /dev/null @@ -1,60 +0,0 @@ -## Basic INSERT Statement Support -In this exercise we will just support staments of the following format `INSERT INTO grades (student_id, course, grade) VALUES ('4', 'Physics', 'A')`. - -### 17.1 Update the Query Parser -Add a function called `parseINSERTQuery` to the `queryParser.js` file. This function should take a query string as input and return an object with the following structure: - -```javascript -{ - type: 'INSERT', - table: 'grades', - columns: [ 'student_id', 'course', 'grade' ], - values: [ "'4'", "'Physics'", "'A'" ] -} -``` - -### 17.2 Update the Query Executer -Add a function called `executeINSERTQuery` to the `queryExecutor.js` file. This function should take a query object as input and insert the data into the CSV file. The function should return a promise that resolves to the number of rows inserted. - - -### 17.3. Make sure the following test passes - -Create this test in a file called `tests/insertExecuter.test.js` - -```javascript -const { executeINSERTQuery } = require('../src/queryExecutor'); -const { readCSV, writeCSV } = require('../src/csvReader'); -const fs = require('fs'); - -// Helper function to create grades.csv with initial data -async function createGradesCSV() { - const initialData = [ - { student_id: '1', course: 'Mathematics', grade: 'A' }, - { student_id: '2', course: 'Chemistry', grade: 'B' }, - { student_id: '3', course: 'Mathematics', grade: 'C' } - ]; - await writeCSV('grades.csv', initialData); -} - -// Test to INSERT a new grade and verify -test('Execute INSERT INTO Query for grades.csv', async () => { - // Create grades.csv with initial data - await createGradesCSV(); - - // Execute INSERT statement - const insertQuery = "INSERT INTO grades (student_id, course, grade) VALUES ('4', 'Physics', 'A')"; - await executeINSERTQuery(insertQuery); - - // Verify the new entry - const updatedData = await readCSV('grades.csv'); - const newEntry = updatedData.find(row => row.student_id === '4' && row.course === 'Physics'); - console.log(updatedData) - expect(newEntry).toBeDefined(); - expect(newEntry.grade).toEqual('A'); - - // Cleanup: Delete grades.csv - fs.unlinkSync('grades.csv'); -}); -``` - -Note that there is some refactoring also done in this step which is reflected in the above test case. Make sure you make relevant changes to your code as well. \ No newline at end of file diff --git a/docs/tutorials/18.md b/docs/tutorials/18.md deleted file mode 100644 index 75151ca75..000000000 --- a/docs/tutorials/18.md +++ /dev/null @@ -1,81 +0,0 @@ -## Step 18: Implementing DELETE Statement Support - -### 18.1 Update the Parser to Handle DELETE Statements -Modify `queryParser.js` to recognize `DELETE FROM` statements. -The parser should identify the table name and conditions for deletion. - -It should return an object with the following structure: -```javascript -{ - type: 'DELETE', - table: 'grades', - whereClauses: [ - { column: 'student_id', operator: '=', value: "'4'" }, - { column: 'course', operator: '=', value: "'Physics'" } - ] -} -``` - -### 18.2 Update the Executer to Handle DELETE Statements - -```javascript -// src/queryExecutor.js - -async function executeDELETEQuery(query) { - const { table, whereClauses } = parseDeleteQuery(query); - let data = await readCSV(`${table}.csv`); - - if (whereClauses.length > 0) { - // Filter out the rows that meet the where clause conditions - // Implement this. - } else { - // If no where clause, clear the entire table - data = []; - } - - // Save the updated data back to the CSV file - await writeCSV(`${table}.csv`, data); - - return { message: "Rows deleted successfully." }; -} - -module.exports = { executeSELECTQuery, executeINSERTQuery, executeDELETEQuery }; -``` - -### 18.3 Make sure the following test passes - -Create this test in a file called `tests/deleteExecuter.test.js` - -```javascript -const { executeDELETEQuery } = require('../src/queryExecutor'); -const { readCSV, writeCSV } = require('../src/csvReader'); -const fs = require('fs'); - -// Helper function to create courses.csv with initial data -async function createCoursesCSV() { - const initialData = [ - { course_id: '1', course_name: 'Mathematics', instructor: 'Dr. Smith' }, - { course_id: '2', course_name: 'Chemistry', instructor: 'Dr. Jones' }, - { course_id: '3', course_name: 'Physics', instructor: 'Dr. Taylor' } - ]; - await writeCSV('courses.csv', initialData); -} - -// Test to DELETE a course and verify -test('Execute DELETE FROM Query for courses.csv', async () => { - // Create courses.csv with initial data - await createCoursesCSV(); - - // Execute DELETE statement - const deleteQuery = "DELETE FROM courses WHERE course_id = '2'"; - await executeDELETEQuery(deleteQuery); - - // Verify the course was removed - const updatedData = await readCSV('courses.csv'); - const deletedCourse = updatedData.find(course => course.course_id === '2'); - expect(deletedCourse).toBeUndefined(); - - // Cleanup: Delete courses.csv - fs.unlinkSync('courses.csv'); -}); -``` diff --git a/docs/tutorials/19.md b/docs/tutorials/19.md deleted file mode 100644 index 12aee546e..000000000 --- a/docs/tutorials/19.md +++ /dev/null @@ -1,105 +0,0 @@ -## Step 19: CLI Integration -### 19.1 Create a Basic CLI for the SQL Engine -We'll use Node.js to create a simple CLI that takes SQL commands as input and displays the results. Node's `readline` module can be used for input, and console logging can display the results. - -- *Setup a New CLI File*: Create a new file `cli.js` in your project. -- *Implement Basic Command Line Interface*: - -Example implementation in cli.js: - -```javascript -const readline = require('readline'); -const { executeSELECTQuery, executeINSERTQuery, executeDELETEQuery } = require('./queryExecutor'); - -const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout -}); - -rl.setPrompt('SQL> '); -console.log('SQL Query Engine CLI. Enter your SQL commands, or type "exit" to quit.'); - -rl.prompt(); - -rl.on('line', async (line) => { - if (line.toLowerCase() === 'exit') { - rl.close(); - return; - } - - try { - // Execute the query - do your own implementation - }catch (error) { - console.error('Error:', error.message); - } - - rl.prompt(); -}).on('close', () => { - console.log('Exiting SQL CLI'); - process.exit(0); -}); -``` - -- *Running the CLI*: Users can run this CLI by executing node cli.js in their command line `node src/cli.js`. -It should look something like this: - - - -- *Interacting with the CLI*: Users can type SQL commands directly into the CLI. The CLI will then use the appropriate function (`SELECT`, `INSERT`, or `DELETE`) to execute the command and display the results. - -### 19.2 Make sure the following test passes - -Create this test in a file called `tests/cli.test.js` - -```javascript -const child_process = require('child_process'); -const path = require('path'); - -test('DISTINCT with Multiple Columns via CLI', (done) => { - const cliPath = path.join(__dirname, '..', 'src', 'cli.js'); - const cliProcess = child_process.spawn('node', [cliPath]); - - let outputData = ""; - cliProcess.stdout.on('data', (data) => { - outputData += data.toString(); - }); - - cliProcess.on('exit', () => { - // Define a regex pattern to extract the JSON result - const cleanedOutput = outputData.replace(/\s+/g, ' '); - - const resultRegex = /Result: (\[.+\])/s; - const match = cleanedOutput.match(resultRegex); - // Fix JSON outputput - match[1] = match[1].replace(/'/g, '"').replace(/(\w+):/g, '"$1":'); - - if (match && match[1]) { - // Parse the captured JSON string - const results = JSON.parse(match[1]); - - // Validation logic - expect(results).toEqual([ - { student_id: '1', course: 'Mathematics' }, - { student_id: '1', course: 'Physics' }, - { student_id: '2', course: 'Chemistry' }, - { student_id: '3', course: 'Mathematics' }, - { student_id: '5', course: 'Biology' }, - { student_id: '5', course: 'Physics' } - ]); - console.log("Test passed successfully"); - } else { - throw new Error('Failed to parse CLI output'); - } - - done(); - }); - - // Introduce a delay before sending the query - setTimeout(() => { - cliProcess.stdin.write("SELECT DISTINCT student_id, course FROM enrollment\n"); - setTimeout(() => { - cliProcess.stdin.write("exit\n"); - }, 1000); // 1 second delay - }, 1000); // 1 second delay -}); -``` diff --git a/docs/tutorials/20.md b/docs/tutorials/20.md deleted file mode 100644 index db85de5bd..000000000 --- a/docs/tutorials/20.md +++ /dev/null @@ -1,13 +0,0 @@ -## Step 20: Publish Your Package to NPM -- Add a Shebang Line: Ensure the first line of your cli.js file starts with a shebang line that specifies the path to the Node.js interpreter. This line is crucial for letting the system know that the script should be run with Node.js. -- Make sure everything is perfect on the package.json. See the additional commands added for CLI to work. Test this locally with `npm link` and running the command. -- *Update the Version*: Update the version in package.json. -- *Create an npm Account*: If you don't have an npm account, create one at npmjs.com. -- *Update the Readme*: Update the Readme to include the commands that you would want to show up on npmjs.com. -- *Login to npm*: In your terminal, log in to npm using npm login and enter your credentials. -- *Publish the Package*: - - Run `npm publish` in your project directory. - - Ensure there are no errors in the output, and your package should now be live on npm. -- *Verify Publication*: - - Check your package on the npm website - https://www.npmjs.com/package/stylusdb-sql. - - Try installing it in a separate project using `npm install stylusdb-sql` \ No newline at end of file diff --git a/docs/tutorials/assets/tutorial-19.gif b/docs/tutorials/assets/tutorial-19.gif deleted file mode 100644 index 67405f195..000000000 Binary files a/docs/tutorials/assets/tutorial-19.gif and /dev/null differ diff --git a/enrollment.csv b/enrollment.csv new file mode 100644 index 000000000..e80af8d93 --- /dev/null +++ b/enrollment.csv @@ -0,0 +1,6 @@ +student_id,course +1,Mathematics +1,Physics +2,Chemistry +3,Mathematics +5,Biology \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3afaec37f..a6ba782df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "0.1.6", "license": "ISC", "dependencies": { - "csv-parser": "^3.0.0", "json2csv": "^6.0.0-alpha.2", "xterm": "^5.3.0" }, @@ -17,6 +16,7 @@ "stylusdb-cli": "node ./src/cli.js" }, "devDependencies": { + "csv-parser": "^3.0.0", "jest": "^29.7.0" } }, @@ -1573,6 +1573,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", + "dev": true, "dependencies": { "minimist": "^1.2.0" }, @@ -2943,6 +2944,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } diff --git a/package.json b/package.json index f52103d5c..d2b9c2349 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "A minimal SQL based DB based on CSV files. For educational purposes only.", "main": "./src/index.js", "directories": { - "doc": "docs" + "doc": "docs", + "test": "tests" }, "scripts": { "test": "jest", @@ -38,11 +39,11 @@ "author": "Chakshu Gautam", "license": "ISC", "devDependencies": { + "csv-parser": "^3.0.0", "jest": "^29.7.0" }, "dependencies": { - "csv-parser": "^3.0.0", "json2csv": "^6.0.0-alpha.2", "xterm": "^5.3.0" } -} \ No newline at end of file +} diff --git a/sample.csv b/sample.csv new file mode 100644 index 000000000..9e7a9fa25 --- /dev/null +++ b/sample.csv @@ -0,0 +1,4 @@ +id,name,age +1,John,30 +2,Jane,25 +3,Bob,22 \ No newline at end of file diff --git a/src/cli.js b/src/cli.js new file mode 100644 index 000000000..6d281e6ea --- /dev/null +++ b/src/cli.js @@ -0,0 +1,89 @@ +#!/usr/bin/env node + +const readline = require('readline'); + +const { executeSELECTQuery, executeINSERTQuery, executeDELETEQuery } = require('./index'); + + + + +const rl = readline.createInterface({ + + input: process.stdin, + + output: process.stdout + +}); + + + + +rl.setPrompt('SQL> '); + +console.log('SQL Query Engine CLI. Enter your SQL commands, or type "exit" to quit.'); + + + + +rl.prompt(); + + + + +rl.on('line', async (line) => { + + if (line.toLowerCase() === 'exit') { + + rl.close(); + + return; + + } + + + + + try { + + if (line.toLowerCase().startsWith('select')) { + + const result = await executeSELECTQuery(line); + + console.log('Result:', result); + + } else if (line.toLowerCase().startsWith('insert into')) { + + const result = await executeINSERTQuery(line); + + console.log(result.message); + + } else if (line.toLowerCase().startsWith('delete from')) { + + const result = await executeDELETEQuery(line); + + console.log(result.message); + + } else { + + console.log('Unsupported command'); + + } + + } catch (error) { + + console.error('Error:', error.message); + + } + + + + + rl.prompt(); + +}).on('close', () => { + + console.log('Exiting SQL CLI'); + + process.exit(0); + +}); \ No newline at end of file diff --git a/src/csvReader.js b/src/csvReader.js index e69de29bb..1296d8a25 100644 --- a/src/csvReader.js +++ b/src/csvReader.js @@ -0,0 +1,42 @@ +const fs = require('fs'); +const csv = require('csv-parser'); +const { parse } = require('json2csv'); + +// function readCSV(filePath) { +// const results = []; + +// return new Promise((resolve, reject) => { +// fs.createReadStream(filePath) +// .pipe(csv()) +// .on('data', (data) => results.push(data)) +// .on('end', () => { +// resolve(results); +// }) +// .on('error', (error) => { +// reject(error); +// }); +// }); +// } +function readCSV(filePath) { + const results = []; + return new Promise((resolve, reject) => { + fs.createReadStream(filePath) + .pipe(csv()) + .on('data', (data) => { + results.push(data) + }) + .on('end', () => { + resolve(results); + }) + .on('error', (error) => { + reject(error); + }); + }); +} + +async function writeCSV(filename, data) { + const csv = parse(data); + fs.writeFileSync(filename, csv); +} + +module.exports = { readCSV, writeCSV }; \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000..37946433b --- /dev/null +++ b/src/index.js @@ -0,0 +1,14 @@ +const { readCSV, writeCSV } = require('./csvReader'); +const { parseSelectQuery, parseInsertQuery, parseDeleteQuery } = require('./queryParser'); +const { executeSELECTQuery, executeINSERTQuery, executeDELETEQuery } = require('./queryExecuter'); + +module.exports = { + readCSV, + writeCSV, + executeSELECTQuery, + executeINSERTQuery, + executeDELETEQuery, + parseSelectQuery, + parseInsertQuery, + parseDeleteQuery +} \ No newline at end of file diff --git a/src/queryExecutor.js b/src/queryExecutor.js new file mode 100644 index 000000000..85681f54b --- /dev/null +++ b/src/queryExecutor.js @@ -0,0 +1,370 @@ +const { parseSelectQuery, parseInsertQuery, parseDeleteQuery } = require('./queryParser'); +const { readCSV, writeCSV } = require('./csvReader'); + +function performInnerJoin(data, joinData, joinCondition, fields, table) { + return data.flatMap(mainRow => { + return joinData + .filter(joinRow => { + const mainValue = mainRow[joinCondition.left.split('.')[1]]; + const joinValue = joinRow[joinCondition.right.split('.')[1]]; + return mainValue === joinValue; + }) + .map(joinRow => { + return fields.reduce((acc, field) => { + const [tableName, fieldName] = field.split('.'); + acc[field] = tableName === table ? mainRow[fieldName] : joinRow[fieldName]; + return acc; + }, {}); + }); + }); +} + +function performLeftJoin(data, joinData, joinCondition, fields, table) { + return data.flatMap(mainRow => { + const matchingJoinRows = joinData.filter(joinRow => { + const mainValue = getValueFromRow(mainRow, joinCondition.left); + const joinValue = getValueFromRow(joinRow, joinCondition.right); + return mainValue === joinValue; + }); + + if (matchingJoinRows.length === 0) { + return [createResultRow(mainRow, null, fields, table, true)]; + } + + return matchingJoinRows.map(joinRow => createResultRow(mainRow, joinRow, fields, table, true)); + }); +} + +function getValueFromRow(row, compoundFieldName) { + const [tableName, fieldName] = compoundFieldName.split('.'); + return row[`${tableName}.${fieldName}`] || row[fieldName]; +} + +function performRightJoin(data, joinData, joinCondition, fields, table) { + // Cache the structure of a main table row (keys only) + const mainTableRowStructure = data.length > 0 ? Object.keys(data[0]).reduce((acc, key) => { + acc[key] = null; // Set all values to null initially + return acc; + }, {}) : {}; + + return joinData.map(joinRow => { + const mainRowMatch = data.find(mainRow => { + const mainValue = getValueFromRow(mainRow, joinCondition.left); + const joinValue = getValueFromRow(joinRow, joinCondition.right); + return mainValue === joinValue; + }); + + // Use the cached structure if no match is found + const mainRowToUse = mainRowMatch || mainTableRowStructure; + + // Include all necessary fields from the 'student' table + return createResultRow(mainRowToUse, joinRow, fields, table, true); + }); +} + +function createResultRow(mainRow, joinRow, fields, table, includeAllMainFields) { + const resultRow = {}; + + if (includeAllMainFields) { + // Include all fields from the main table + Object.keys(mainRow || {}).forEach(key => { + const prefixedKey = `${table}.${key}`; + resultRow[prefixedKey] = mainRow ? mainRow[key] : null; + }); + } + + // Now, add or overwrite with the fields specified in the query + fields.forEach(field => { + const [tableName, fieldName] = field.includes('.') ? field.split('.') : [table, field]; + resultRow[field] = tableName === table && mainRow ? mainRow[fieldName] : joinRow ? joinRow[fieldName] : null; + }); + + return resultRow; +} + +function evaluateCondition(row, clause) { + let { field, operator, value } = clause; + + // Check if the field exists in the row + if (row[field] === undefined) { + throw new Error(`Invalid field: ${field}`); + } + + // Parse row value and condition value based on their actual types + const rowValue = parseValue(row[field]); + let conditionValue = parseValue(value); + + if (operator === 'LIKE') { + // Transform SQL LIKE pattern to JavaScript RegExp pattern + const regexPattern = '^' + value.replace(/%/g, '.*').replace(/_/g, '.') + '$'; + const regex = new RegExp(regexPattern, 'i'); // 'i' for case-insensitive matching + return regex.test(row[field]); + } + + switch (operator) { + case '=': return rowValue === conditionValue; + case '!=': return rowValue !== conditionValue; + case '>': return rowValue > conditionValue; + case '<': return rowValue < conditionValue; + case '>=': return rowValue >= conditionValue; + case '<=': return rowValue <= conditionValue; + default: throw new Error(`Unsupported operator: ${operator}`); + } +} + +// Helper function to parse value based on its apparent type +function parseValue(value) { + + // Return null or undefined as is + if (value === null || value === undefined) { + return value; + } + + // If the value is a string enclosed in single or double quotes, remove them + if (typeof value === 'string' && ((value.startsWith("'") && value.endsWith("'")) || (value.startsWith('"') && value.endsWith('"')))) { + value = value.substring(1, value.length - 1); + } + + // Check if value is a number + if (!isNaN(value) && value.trim() !== '') { + return Number(value); + } + // Assume value is a string if not a number + return value; +} + +function applyGroupBy(data, groupByFields, aggregateFunctions) { + const groupResults = {}; + + data.forEach(row => { + // Generate a key for the group + const groupKey = groupByFields.map(field => row[field]).join('-'); + + // Initialize group in results if it doesn't exist + if (!groupResults[groupKey]) { + groupResults[groupKey] = { count: 0, sums: {}, mins: {}, maxes: {} }; + groupByFields.forEach(field => groupResults[groupKey][field] = row[field]); + } + + // Aggregate calculations + groupResults[groupKey].count += 1; + aggregateFunctions.forEach(func => { + const match = /(\w+)\((\w+)\)/.exec(func); + if (match) { + const [, aggFunc, aggField] = match; + const value = parseFloat(row[aggField]); + + switch (aggFunc.toUpperCase()) { + case 'SUM': + groupResults[groupKey].sums[aggField] = (groupResults[groupKey].sums[aggField] || 0) + value; + break; + case 'MIN': + groupResults[groupKey].mins[aggField] = Math.min(groupResults[groupKey].mins[aggField] || value, value); + break; + case 'MAX': + groupResults[groupKey].maxes[aggField] = Math.max(groupResults[groupKey].maxes[aggField] || value, value); + break; + // Additional aggregate functions can be added here + } + } + }); + }); + + // Convert grouped results into an array format + return Object.values(groupResults).map(group => { + // Construct the final grouped object based on required fields + const finalGroup = {}; + groupByFields.forEach(field => finalGroup[field] = group[field]); + aggregateFunctions.forEach(func => { + const match = /(\w+)\((\*|\w+)\)/.exec(func); + if (match) { + const [, aggFunc, aggField] = match; + switch (aggFunc.toUpperCase()) { + case 'SUM': + finalGroup[func] = group.sums[aggField]; + break; + case 'MIN': + finalGroup[func] = group.mins[aggField]; + break; + case 'MAX': + finalGroup[func] = group.maxes[aggField]; + break; + case 'COUNT': + finalGroup[func] = group.count; + break; + // Additional aggregate functions can be handled here + } + } + }); + + return finalGroup; + }); +} + +async function executeSELECTQuery(query) { + try { + + const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields, hasAggregateWithoutGroupBy, orderByFields, limit, isDistinct } = parseSelectQuery(query); + let data = await readCSV(`${table}.csv`); + + // Perform INNER JOIN if specified + if (joinTable && joinCondition) { + const joinData = await readCSV(`${joinTable}.csv`); + switch (joinType.toUpperCase()) { + case 'INNER': + data = performInnerJoin(data, joinData, joinCondition, fields, table); + break; + case 'LEFT': + data = performLeftJoin(data, joinData, joinCondition, fields, table); + break; + case 'RIGHT': + data = performRightJoin(data, joinData, joinCondition, fields, table); + break; + default: + throw new Error(`Unsupported JOIN type: ${joinType}`); + } + } + // Apply WHERE clause filtering after JOIN (or on the original data if no join) + let filteredData = whereClauses.length > 0 + ? data.filter(row => whereClauses.every(clause => evaluateCondition(row, clause))) + : data; + + let groupResults = filteredData; + if (hasAggregateWithoutGroupBy) { + // Special handling for queries like 'SELECT COUNT(*) FROM table' + const result = {}; + + fields.forEach(field => { + const match = /(\w+)\((\*|\w+)\)/.exec(field); + if (match) { + const [, aggFunc, aggField] = match; + switch (aggFunc.toUpperCase()) { + case 'COUNT': + result[field] = filteredData.length; + break; + case 'SUM': + result[field] = filteredData.reduce((acc, row) => acc + parseFloat(row[aggField]), 0); + break; + case 'AVG': + result[field] = filteredData.reduce((acc, row) => acc + parseFloat(row[aggField]), 0) / filteredData.length; + break; + case 'MIN': + result[field] = Math.min(...filteredData.map(row => parseFloat(row[aggField]))); + break; + case 'MAX': + result[field] = Math.max(...filteredData.map(row => parseFloat(row[aggField]))); + break; + // Additional aggregate functions can be handled here + } + } + }); + + return [result]; + // Add more cases here if needed for other aggregates + } else if (groupByFields) { + groupResults = applyGroupBy(filteredData, groupByFields, fields); + + // Order them by the specified fields + let orderedResults = groupResults; + if (orderByFields) { + orderedResults = groupResults.sort((a, b) => { + for (let { fieldName, order } of orderByFields) { + if (a[fieldName] < b[fieldName]) return order === 'ASC' ? -1 : 1; + if (a[fieldName] > b[fieldName]) return order === 'ASC' ? 1 : -1; + } + return 0; + }); + } + if (limit !== null) { + groupResults = groupResults.slice(0, limit); + } + return groupResults; + } else { + + // Order them by the specified fields + let orderedResults = groupResults; + if (orderByFields) { + orderedResults = groupResults.sort((a, b) => { + for (let { fieldName, order } of orderByFields) { + if (a[fieldName] < b[fieldName]) return order === 'ASC' ? -1 : 1; + if (a[fieldName] > b[fieldName]) return order === 'ASC' ? 1 : -1; + } + return 0; + }); + } + + // Select the specified fields + let finalResults = orderedResults.map(row => { + const selectedRow = {}; + fields.forEach(field => { + // Assuming 'field' is just the column name without table prefix + selectedRow[field] = row[field]; + }); + return selectedRow; + }); + + // Remove duplicates if specified + let distinctResults = finalResults; + if (isDistinct) { + distinctResults = [...new Map(finalResults.map(item => [fields.map(field => item[field]).join('|'), item])).values()]; + } + + let limitResults = distinctResults; + if (limit !== null) { + limitResults = distinctResults.slice(0, limit); + } + + return limitResults; + + + } + } catch (error) { + throw new Error(`Error executing query: ${error.message}`); + } +} + +async function executeINSERTQuery(query) { + console.log(parseInsertQuery(query)); + const { table, columns, values } = parseInsertQuery(query); + const data = await readCSV(`${table}.csv`); + + // Create a new row object + const newRow = {}; + columns.forEach((column, index) => { + // Remove single quotes from the values + let value = values[index]; + if (value.startsWith("'") && value.endsWith("'")) { + value = value.substring(1, value.length - 1); + } + newRow[column] = value; + }); + + // Add the new row to the data + data.push(newRow); + + // Save the updated data back to the CSV file + await writeCSV(`${table}.csv`, data); // Implement writeCSV function + + return { message: "Row inserted successfully." }; +} + +async function executeDELETEQuery(query) { + const { table, whereClauses } = parseDeleteQuery(query); + let data = await readCSV(`${table}.csv`); + + if (whereClauses.length > 0) { + // Filter out the rows that meet the where clause conditions + data = data.filter(row => !whereClauses.every(clause => evaluateCondition(row, clause))); + } else { + // If no where clause, clear the entire table + data = []; + } + + // Save the updated data back to the CSV file + await writeCSV(`${table}.csv`, data); + + return { message: "Rows deleted successfully." }; +} + + +module.exports = { executeSELECTQuery, executeINSERTQuery, executeDELETEQuery }; \ No newline at end of file diff --git a/src/queryParser.js b/src/queryParser.js new file mode 100644 index 000000000..67b94f5d0 --- /dev/null +++ b/src/queryParser.js @@ -0,0 +1,177 @@ +function parseSelectQuery(query) { + try { + + // Trim the query to remove any leading/trailing whitespaces + query = query.trim(); + + // Initialize distinct flag + let isDistinct = false; + + // Check for DISTINCT keyword and update the query + if (query.toUpperCase().includes('SELECT DISTINCT')) { + isDistinct = true; + query = query.replace('SELECT DISTINCT', 'SELECT'); + } + + // Updated regex to capture LIMIT clause and remove it for further processing + const limitRegex = /\sLIMIT\s(\d+)/i; + const limitMatch = query.match(limitRegex); + + let limit = null; + if (limitMatch) { + limit = parseInt(limitMatch[1], 10); + query = query.replace(limitRegex, ''); // Remove LIMIT clause + } + + // Process ORDER BY clause and remove it for further processing + const orderByRegex = /\sORDER BY\s(.+)/i; + const orderByMatch = query.match(orderByRegex); + let orderByFields = null; + if (orderByMatch) { + orderByFields = orderByMatch[1].split(',').map(field => { + const [fieldName, order] = field.trim().split(/\s+/); + return { fieldName, order: order ? order.toUpperCase() : 'ASC' }; + }); + query = query.replace(orderByRegex, ''); + } + + // Process GROUP BY clause and remove it for further processing + const groupByRegex = /\sGROUP BY\s(.+)/i; + const groupByMatch = query.match(groupByRegex); + let groupByFields = null; + if (groupByMatch) { + groupByFields = groupByMatch[1].split(',').map(field => field.trim()); + query = query.replace(groupByRegex, ''); + } + + // Process WHERE clause + const whereSplit = query.split(/\sWHERE\s/i); + const queryWithoutWhere = whereSplit[0]; // Everything before WHERE clause + const whereClause = whereSplit.length > 1 ? whereSplit[1].trim() : null; + + // Process JOIN clause + const joinSplit = queryWithoutWhere.split(/\s(INNER|LEFT|RIGHT) JOIN\s/i); + const selectPart = joinSplit[0].trim(); // Everything before JOIN clause + + // Extract JOIN information + const { joinType, joinTable, joinCondition } = parseJoinClause(queryWithoutWhere); + + // Parse SELECT part + const selectRegex = /^SELECT\s(.+?)\sFROM\s(.+)/i; + const selectMatch = selectPart.match(selectRegex); + if (!selectMatch) { + throw new Error('Invalid SELECT format'); + } + const [, fields, table] = selectMatch; + + // Parse WHERE part if it exists + let whereClauses = []; + if (whereClause) { + whereClauses = parseWhereClause(whereClause); + } + + // Check for aggregate functions without GROUP BY + const hasAggregateWithoutGroupBy = checkAggregateWithoutGroupBy(query, groupByFields); + + return { + fields: fields.split(',').map(field => field.trim()), + table: table.trim(), + whereClauses, + joinType, + joinTable, + joinCondition, + groupByFields, + orderByFields, + hasAggregateWithoutGroupBy, + limit, + isDistinct + }; + } catch (error) { + throw new Error(`Query parsing error: ${error.message}`); + } +} + +function checkAggregateWithoutGroupBy(query, groupByFields) { + const aggregateFunctionRegex = /(\bCOUNT\b|\bAVG\b|\bSUM\b|\bMIN\b|\bMAX\b)\s*\(\s*(\*|\w+)\s*\)/i; + return aggregateFunctionRegex.test(query) && !groupByFields; +} + +function parseWhereClause(whereString) { + const conditionRegex = /(.*?)(=|!=|>=|<=|>|<)(.*)/; + return whereString.split(/ AND | OR /i).map(conditionString => { + if (conditionString.includes(' LIKE ')) { + const [field, pattern] = conditionString.split(/\sLIKE\s/i); + return { field: field.trim(), operator: 'LIKE', value: pattern.trim().replace(/^'(.*)'$/, '$1') }; + } else { + const match = conditionString.match(conditionRegex); + if (match) { + const [, field, operator, value] = match; + return { field: field.trim(), operator, value: value.trim() }; + } + throw new Error('Invalid WHERE clause format'); + } + }); +} + +function parseJoinClause(query) { + const joinRegex = /\s(INNER|LEFT|RIGHT) JOIN\s(.+?)\sON\s([\w.]+)\s*=\s*([\w.]+)/i; + const joinMatch = query.match(joinRegex); + + if (joinMatch) { + return { + joinType: joinMatch[1].trim(), + joinTable: joinMatch[2].trim(), + joinCondition: { + left: joinMatch[3].trim(), + right: joinMatch[4].trim() + } + }; + } + + return { + joinType: null, + joinTable: null, + joinCondition: null + }; +} + +function parseInsertQuery(query) { + const insertRegex = /INSERT INTO (\w+)\s\((.+)\)\sVALUES\s\((.+)\)/i; + const match = query.match(insertRegex); + + if (!match) { + throw new Error("Invalid INSERT INTO syntax."); + } + + const [, table, columns, values] = match; + return { + type: 'INSERT', + table: table.trim(), + columns: columns.split(',').map(column => column.trim()), + values: values.split(',').map(value => value.trim()) + }; +} + +function parseDeleteQuery(query) { + const deleteRegex = /DELETE FROM (\w+)( WHERE (.*))?/i; + const match = query.match(deleteRegex); + + if (!match) { + throw new Error("Invalid DELETE syntax."); + } + + const [, table, , whereString] = match; + let whereClauses = []; + if (whereString) { + whereClauses = parseWhereClause(whereString); + } + + return { + type: 'DELETE', + table: table.trim(), + whereClauses + }; +} + + +module.exports = { parseSelectQuery, parseJoinClause, parseInsertQuery, parseDeleteQuery }; \ No newline at end of file diff --git a/student.csv b/student.csv new file mode 100644 index 000000000..e9c960121 --- /dev/null +++ b/student.csv @@ -0,0 +1,5 @@ +id,name,age +1,John,30 +2,Jane,25 +3,Bob,22 +4,Alice,24 \ No newline at end of file diff --git a/tests/step-02/index.test.js b/tests/step-02/index.test.js index a5467ee48..59a3322e8 100644 --- a/tests/step-02/index.test.js +++ b/tests/step-02/index.test.js @@ -1,9 +1,9 @@ -const readCSV = require('../../src/csvReader'); +const {readCSV} = require('../../src/csvReader'); test('Read CSV File', async () => { - const data = await readCSV('./sample.csv'); + const data = await readCSV('./student.csv'); expect(data.length).toBeGreaterThan(0); - expect(data.length).toBe(3); + expect(data.length).toBe(4); expect(data[0].name).toBe('John'); expect(data[0].age).toBe('30'); //ignore the string type here, we will fix this later }); \ No newline at end of file diff --git a/tests/step-03/index.test.js b/tests/step-03/index.test.js index 9145ad3e4..924e2e3c1 100644 --- a/tests/step-03/index.test.js +++ b/tests/step-03/index.test.js @@ -1,19 +1,29 @@ -const readCSV = require('../../src/csvReader'); -const parseQuery = require('../../src/queryParser'); + +const {readCSV} = require('../../src/csvReader'); +const {parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { - const data = await readCSV('./sample.csv'); + const data = await readCSV('./student.csv'); expect(data.length).toBeGreaterThan(0); - expect(data.length).toBe(3); + expect(data.length).toBe(4); expect(data[0].name).toBe('John'); expect(data[0].age).toBe('30'); //ignore the string type here, we will fix this later }); test('Parse SQL Query', () => { - const query = 'SELECT id, name FROM sample'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample' + table: 'student', + whereClauses: [], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); \ No newline at end of file diff --git a/tests/step-04/index.test.js b/tests/step-04/index.test.js index bc353dd3d..51e8cfd7d 100644 --- a/tests/step-04/index.test.js +++ b/tests/step-04/index.test.js @@ -1,26 +1,35 @@ -const readCSV = require('../../src/csvReader'); -const parseQuery = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { - const data = await readCSV('./sample.csv'); + const data = await readCSV('./student.csv'); expect(data.length).toBeGreaterThan(0); - expect(data.length).toBe(3); + expect(data.length).toBe(4); expect(data[0].name).toBe('John'); expect(data[0].age).toBe('30'); //ignore the string type here, we will fix this later }); test('Parse SQL Query', () => { - const query = 'SELECT id, name FROM sample'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample' + table: 'student', + whereClauses: [], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Execute SQL Query', async () => { - const query = 'SELECT id, name FROM sample'; + const query = 'SELECT id, name FROM student'; const result = await executeSELECTQuery(query); expect(result.length).toBeGreaterThan(0); expect(result[0]).toHaveProperty('id'); diff --git a/tests/step-05/index.test.js b/tests/step-05/index.test.js index 66a77c061..8522a0db7 100644 --- a/tests/step-05/index.test.js +++ b/tests/step-05/index.test.js @@ -1,27 +1,35 @@ -const readCSV = require('../../src/csvReader'); -const parseQuery = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { - const data = await readCSV('./sample.csv'); + const data = await readCSV('./student.csv'); expect(data.length).toBeGreaterThan(0); - expect(data.length).toBe(3); + expect(data.length).toBe(4); expect(data[0].name).toBe('John'); expect(data[0].age).toBe('30'); //ignore the string type here, we will fix this later }); test('Parse SQL Query', () => { - const query = 'SELECT id, name FROM sample'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample', - whereClause: null + table: 'student', + whereClauses: [], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Execute SQL Query', async () => { - const query = 'SELECT id, name FROM sample'; + const query = 'SELECT id, name FROM student'; const result = await executeSELECTQuery(query); expect(result.length).toBeGreaterThan(0); expect(result[0]).toHaveProperty('id'); @@ -29,19 +37,29 @@ test('Execute SQL Query', async () => { expect(result[0]).not.toHaveProperty('age'); expect(result[0]).toEqual({ id: '1', name: 'John' }); }); - test('Parse SQL Query with WHERE Clause', () => { - const query = 'SELECT id, name FROM sample WHERE age = 25'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student WHERE age = 25'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample', - whereClause: 'age = 25' + table: 'student', + whereClauses: [{ + "field": "age", + "operator": "=", + "value": "25", + }], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); - test('Execute SQL Query with WHERE Clause', async () => { - const query = 'SELECT id, name FROM sample WHERE age = 25'; + const query = 'SELECT id, name FROM student WHERE age = 25'; const result = await executeSELECTQuery(query); expect(result.length).toBe(1); expect(result[0]).toHaveProperty('id'); diff --git a/tests/step-06/index.test.js b/tests/step-06/index.test.js index 2e2ef6416..fe79fabba 100644 --- a/tests/step-06/index.test.js +++ b/tests/step-06/index.test.js @@ -1,27 +1,35 @@ -const readCSV = require('../../src/csvReader'); -const parseQuery = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { - const data = await readCSV('./sample.csv'); + const data = await readCSV('./student.csv'); expect(data.length).toBeGreaterThan(0); - expect(data.length).toBe(3); + expect(data.length).toBe(4); expect(data[0].name).toBe('John'); expect(data[0].age).toBe('30'); //ignore the string type here, we will fix this later }); test('Parse SQL Query', () => { - const query = 'SELECT id, name FROM sample'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample', - whereClauses: [] + table: 'student', + whereClauses: [], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Execute SQL Query', async () => { - const query = 'SELECT id, name FROM sample'; + const query = 'SELECT id, name FROM student'; const result = await executeSELECTQuery(query); expect(result.length).toBeGreaterThan(0); expect(result[0]).toHaveProperty('id'); @@ -29,23 +37,29 @@ test('Execute SQL Query', async () => { expect(result[0]).not.toHaveProperty('age'); expect(result[0]).toEqual({ id: '1', name: 'John' }); }); - test('Parse SQL Query with WHERE Clause', () => { - const query = 'SELECT id, name FROM sample WHERE age = 25'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student WHERE age = 25'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample', + table: 'student', whereClauses: [{ - field: "age", - operator: "=", - value: "25", + "field": "age", + "operator": "=", + "value": "25", }], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); - test('Execute SQL Query with WHERE Clause', async () => { - const query = 'SELECT id, name FROM sample WHERE age = 25'; + const query = 'SELECT id, name FROM student WHERE age = 25'; const result = await executeSELECTQuery(query); expect(result.length).toBe(1); expect(result[0]).toHaveProperty('id'); @@ -54,11 +68,11 @@ test('Execute SQL Query with WHERE Clause', async () => { }); test('Parse SQL Query with Multiple WHERE Clauses', () => { - const query = 'SELECT id, name FROM sample WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample', + table: 'student', whereClauses: [{ "field": "age", "operator": "=", @@ -67,12 +81,20 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { "field": "name", "operator": "=", "value": "John", - }] + }], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); -test('Execute SQL Query with Multiple WHERE Clause', async () => { - const query = 'SELECT id, name FROM sample WHERE age = 30 AND name = John'; +test('Execute SQL Query with Complex WHERE Clause', async () => { + const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; const result = await executeSELECTQuery(query); expect(result.length).toBe(1); expect(result[0]).toEqual({ id: '1', name: 'John' }); diff --git a/tests/step-07/index.test.js b/tests/step-07/index.test.js index ee0ebed5e..5faa49133 100644 --- a/tests/step-07/index.test.js +++ b/tests/step-07/index.test.js @@ -1,27 +1,35 @@ -const readCSV = require('../../src/csvReader'); -const parseQuery = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { - const data = await readCSV('./sample.csv'); + const data = await readCSV('./student.csv'); expect(data.length).toBeGreaterThan(0); - expect(data.length).toBe(3); + expect(data.length).toBe(4); expect(data[0].name).toBe('John'); expect(data[0].age).toBe('30'); //ignore the string type here, we will fix this later }); test('Parse SQL Query', () => { - const query = 'SELECT id, name FROM sample'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample', - whereClauses: [] + table: 'student', + whereClauses: [], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Execute SQL Query', async () => { - const query = 'SELECT id, name FROM sample'; + const query = 'SELECT id, name FROM student'; const result = await executeSELECTQuery(query); expect(result.length).toBeGreaterThan(0); expect(result[0]).toHaveProperty('id'); @@ -29,23 +37,29 @@ test('Execute SQL Query', async () => { expect(result[0]).not.toHaveProperty('age'); expect(result[0]).toEqual({ id: '1', name: 'John' }); }); - test('Parse SQL Query with WHERE Clause', () => { - const query = 'SELECT id, name FROM sample WHERE age = 25'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student WHERE age = 25'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample', + table: 'student', whereClauses: [{ - field: "age", - operator: "=", - value: "25", + "field": "age", + "operator": "=", + "value": "25", }], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); - test('Execute SQL Query with WHERE Clause', async () => { - const query = 'SELECT id, name FROM sample WHERE age = 25'; + const query = 'SELECT id, name FROM student WHERE age = 25'; const result = await executeSELECTQuery(query); expect(result.length).toBe(1); expect(result[0]).toHaveProperty('id'); @@ -54,11 +68,11 @@ test('Execute SQL Query with WHERE Clause', async () => { }); test('Parse SQL Query with Multiple WHERE Clauses', () => { - const query = 'SELECT id, name FROM sample WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], - table: 'sample', + table: 'student', whereClauses: [{ "field": "age", "operator": "=", @@ -67,27 +81,34 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { "field": "name", "operator": "=", "value": "John", - }] + }], + joinCondition: null, + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); -test('Execute SQL Query with Multiple WHERE Clause', async () => { - const query = 'SELECT id, name FROM sample WHERE age = 30 AND name = John'; +test('Execute SQL Query with Complex WHERE Clause', async () => { + const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; const result = await executeSELECTQuery(query); expect(result.length).toBe(1); expect(result[0]).toEqual({ id: '1', name: 'John' }); }); - test('Execute SQL Query with Greater Than', async () => { - const queryWithGT = 'SELECT id FROM sample WHERE age > 22'; + const queryWithGT = 'SELECT id FROM student WHERE age > 22'; const result = await executeSELECTQuery(queryWithGT); - expect(result.length).toEqual(2); + expect(result.length).toEqual(3); expect(result[0]).toHaveProperty('id'); }); test('Execute SQL Query with Not Equal to', async () => { - const queryWithGT = 'SELECT name FROM sample WHERE age != 25'; + const queryWithGT = 'SELECT name FROM student WHERE age != 25'; const result = await executeSELECTQuery(queryWithGT); - expect(result.length).toEqual(2); + expect(result.length).toEqual(3); expect(result[0]).toHaveProperty('name'); }); \ No newline at end of file diff --git a/tests/step-08/index.test.js b/tests/step-08/index.test.js index aab1467e6..93e1799b0 100644 --- a/tests/step-08/index.test.js +++ b/tests/step-08/index.test.js @@ -1,24 +1,30 @@ -const readCSV = require('../../src/csvReader'); -const parseQuery = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); expect(data.length).toBeGreaterThan(0); - expect(data.length).toBe(3); + expect(data.length).toBe(4); expect(data[0].name).toBe('John'); expect(data[0].age).toBe('30'); //ignore the string type here, we will fix this later }); test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', whereClauses: [], joinCondition: null, - joinTable: null + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); @@ -31,10 +37,9 @@ test('Execute SQL Query', async () => { expect(result[0]).not.toHaveProperty('age'); expect(result[0]).toEqual({ id: '1', name: 'John' }); }); - test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -44,10 +49,15 @@ test('Parse SQL Query with WHERE Clause', () => { "value": "25", }], joinCondition: null, - joinTable: null + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); - test('Execute SQL Query with WHERE Clause', async () => { const query = 'SELECT id, name FROM student WHERE age = 25'; const result = await executeSELECTQuery(query); @@ -59,7 +69,7 @@ test('Execute SQL Query with WHERE Clause', async () => { test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -73,7 +83,13 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { "value": "John", }], joinCondition: null, - joinTable: null + joinTable: null, + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); @@ -83,42 +99,53 @@ test('Execute SQL Query with Complex WHERE Clause', async () => { expect(result.length).toBe(1); expect(result[0]).toEqual({ id: '1', name: 'John' }); }); - test('Execute SQL Query with Greater Than', async () => { const queryWithGT = 'SELECT id FROM student WHERE age > 22'; const result = await executeSELECTQuery(queryWithGT); - expect(result.length).toEqual(2); + expect(result.length).toEqual(3); expect(result[0]).toHaveProperty('id'); }); test('Execute SQL Query with Not Equal to', async () => { const queryWithGT = 'SELECT name FROM student WHERE age != 25'; const result = await executeSELECTQuery(queryWithGT); - expect(result.length).toEqual(2); + expect(result.length).toEqual(3); expect(result[0]).toHaveProperty('name'); }); test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', whereClauses: [], joinTable: 'enrollment', - joinCondition: { left: 'student.id', right: 'enrollment.student_id' } + joinType: "INNER", + joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }) }); test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', whereClauses: [{ field: 'student.age', operator: '>', value: '20' }], joinTable: 'enrollment', - joinCondition: { left: 'student.id', right: 'enrollment.student_id' } + joinType: "INNER", + joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }) }); diff --git a/tests/step-09/index.test.js b/tests/step-09/index.test.js index aaf711f5a..02e6cdb86 100644 --- a/tests/step-09/index.test.js +++ b/tests/step-09/index.test.js @@ -1,6 +1,6 @@ -const readCSV = require('../../src/csvReader'); -const {parseQuery} = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); @@ -12,14 +12,19 @@ test('Read CSV File', async () => { test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', whereClauses: [], joinCondition: null, joinTable: null, - joinType: null + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); @@ -32,10 +37,9 @@ test('Execute SQL Query', async () => { expect(result[0]).not.toHaveProperty('age'); expect(result[0]).toEqual({ id: '1', name: 'John' }); }); - test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -46,10 +50,14 @@ test('Parse SQL Query with WHERE Clause', () => { }], joinCondition: null, joinTable: null, - joinType: null + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); - test('Execute SQL Query with WHERE Clause', async () => { const query = 'SELECT id, name FROM student WHERE age = 25'; const result = await executeSELECTQuery(query); @@ -61,7 +69,7 @@ test('Execute SQL Query with WHERE Clause', async () => { test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -76,7 +84,12 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { }], joinCondition: null, joinTable: null, - joinType: null + joinType: null, + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); @@ -86,7 +99,6 @@ test('Execute SQL Query with Complex WHERE Clause', async () => { expect(result.length).toBe(1); expect(result[0]).toEqual({ id: '1', name: 'John' }); }); - test('Execute SQL Query with Greater Than', async () => { const queryWithGT = 'SELECT id FROM student WHERE age > 22'; const result = await executeSELECTQuery(queryWithGT); @@ -103,27 +115,37 @@ test('Execute SQL Query with Not Equal to', async () => { test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', whereClauses: [], joinTable: 'enrollment', + joinType: "INNER", joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, - joinType: 'INNER' + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }) }); test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', whereClauses: [{ field: 'student.age', operator: '>', value: '20' }], joinTable: 'enrollment', + joinType: "INNER", joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, - joinType: 'INNER' + groupByFields: null, + hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }) }); diff --git a/tests/step-10/index.test.js b/tests/step-10/index.test.js index 5e118eda5..2b9ef2e73 100644 --- a/tests/step-10/index.test.js +++ b/tests/step-10/index.test.js @@ -1,6 +1,6 @@ -const readCSV = require('../../src/csvReader'); -const {parseQuery, parseJoinClause} = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); @@ -258,7 +258,7 @@ test('Average age of students above a certain age', async () => { test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -268,12 +268,15 @@ test('Parse SQL Query', () => { joinType: null, groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -287,12 +290,15 @@ test('Parse SQL Query with WHERE Clause', () => { joinType: null, groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -310,12 +316,15 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { joinType: null, groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -325,12 +334,15 @@ test('Parse SQL Query with INNER JOIN', async () => { joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }) }); test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -340,6 +352,9 @@ test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }) }); @@ -387,7 +402,7 @@ test('Returns null for queries without JOIN', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -397,12 +412,15 @@ test('Parse LEFT Join Query Completely', () => { joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }) }) test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -412,12 +430,15 @@ test('Parse LEFT Join Query Completely', () => { joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }) }) test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age > 22'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -427,12 +448,15 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main tabl "whereClauses": [{ "field": "student.age", "operator": ">", "value": "22" }], groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Physics'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -442,12 +466,15 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join tabl "whereClauses": [{ "field": "enrollment.course", "operator": "=", "value": "'Physics'" }], groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age < 25'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -457,12 +484,15 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main tab "whereClauses": [{ "field": "student.age", "operator": "<", "value": "25" }], groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Chemistry'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -472,13 +502,16 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join tab "whereClauses": [{ "field": "enrollment.course", "operator": "=", "value": "'Chemistry'" }], groupByFields: null, hasAggregateWithoutGroupBy: false, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse COUNT Aggregate Query', () => { const query = 'SELECT COUNT(*) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['COUNT(*)'], table: 'student', @@ -488,13 +521,16 @@ test('Parse COUNT Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SUM Aggregate Query', () => { const query = 'SELECT SUM(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['SUM(age)'], table: 'student', @@ -504,12 +540,15 @@ test('Parse SUM Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse AVG Aggregate Query', () => { const query = 'SELECT AVG(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['AVG(age)'], table: 'student', @@ -519,12 +558,15 @@ test('Parse AVG Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse MIN Aggregate Query', () => { const query = 'SELECT MIN(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MIN(age)'], table: 'student', @@ -534,12 +576,15 @@ test('Parse MIN Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse MAX Aggregate Query', () => { const query = 'SELECT MAX(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MAX(age)'], table: 'student', @@ -549,12 +594,15 @@ test('Parse MAX Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse basic GROUP BY query', () => { const query = 'SELECT age, COUNT(*) FROM student GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -563,13 +611,16 @@ test('Parse basic GROUP BY query', () => { joinType: null, joinTable: null, joinCondition: null, - hasAggregateWithoutGroupBy: false + hasAggregateWithoutGroupBy: false, + orderByFields: null, + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with WHERE clause', () => { const query = 'SELECT age, COUNT(*) FROM student WHERE age > 22 GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -578,13 +629,16 @@ test('Parse GROUP BY query with WHERE clause', () => { joinType: null, joinTable: null, joinCondition: null, - hasAggregateWithoutGroupBy: false + hasAggregateWithoutGroupBy: false, + orderByFields: null, + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with multiple fields', () => { const query = 'SELECT student_id, course, COUNT(*) FROM enrollment GROUP BY student_id, course'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student_id', 'course', 'COUNT(*)'], table: 'enrollment', @@ -593,13 +647,16 @@ test('Parse GROUP BY query with multiple fields', () => { joinType: null, joinTable: null, joinCondition: null, - hasAggregateWithoutGroupBy: false + hasAggregateWithoutGroupBy: false, + orderByFields: null, + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with JOIN and WHERE clauses', () => { const query = 'SELECT student.name, COUNT(*) FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE enrollment.course = "Mathematics" GROUP BY student.name'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student.name', 'COUNT(*)'], table: 'student', @@ -611,6 +668,9 @@ test('Parse GROUP BY query with JOIN and WHERE clauses', () => { left: 'student.id', right: 'enrollment.student_id' }, - hasAggregateWithoutGroupBy: false + hasAggregateWithoutGroupBy: false, + orderByFields: null, + "limit": null, + isDistinct: false, }); }); \ No newline at end of file diff --git a/tests/step-11/index.test.js b/tests/step-11/index.test.js index 1cf5f2def..68f672e72 100644 --- a/tests/step-11/index.test.js +++ b/tests/step-11/index.test.js @@ -1,6 +1,6 @@ -const readCSV = require('../../src/csvReader'); -const {parseQuery, parseJoinClause} = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); @@ -258,7 +258,7 @@ test('Average age of students above a certain age', async () => { test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -268,13 +268,15 @@ test('Parse SQL Query', () => { joinType: null, groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -288,13 +290,15 @@ test('Parse SQL Query with WHERE Clause', () => { joinType: null, groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -312,13 +316,15 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { joinType: null, groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -328,13 +334,15 @@ test('Parse SQL Query with INNER JOIN', async () => { joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }) }); test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -344,7 +352,9 @@ test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }) }); @@ -392,7 +402,7 @@ test('Returns null for queries without JOIN', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -402,13 +412,15 @@ test('Parse LEFT Join Query Completely', () => { joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }) }) test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -418,13 +430,15 @@ test('Parse LEFT Join Query Completely', () => { joinCondition: { left: 'student.id', right: 'enrollment.student_id' }, groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }) }) test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age > 22'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -434,13 +448,15 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main tabl "whereClauses": [{ "field": "student.age", "operator": ">", "value": "22" }], groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Physics'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -450,13 +466,15 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join tabl "whereClauses": [{ "field": "enrollment.course", "operator": "=", "value": "'Physics'" }], groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age < 25'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -466,13 +484,15 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main tab "whereClauses": [{ "field": "student.age", "operator": "<", "value": "25" }], groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Chemistry'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -482,14 +502,16 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join tab "whereClauses": [{ "field": "enrollment.course", "operator": "=", "value": "'Chemistry'" }], groupByFields: null, hasAggregateWithoutGroupBy: false, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse COUNT Aggregate Query', () => { const query = 'SELECT COUNT(*) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['COUNT(*)'], table: 'student', @@ -499,14 +521,16 @@ test('Parse COUNT Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse SUM Aggregate Query', () => { const query = 'SELECT SUM(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['SUM(age)'], table: 'student', @@ -516,13 +540,15 @@ test('Parse SUM Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse AVG Aggregate Query', () => { const query = 'SELECT AVG(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['AVG(age)'], table: 'student', @@ -532,13 +558,15 @@ test('Parse AVG Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse MIN Aggregate Query', () => { const query = 'SELECT MIN(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MIN(age)'], table: 'student', @@ -548,13 +576,15 @@ test('Parse MIN Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse MAX Aggregate Query', () => { const query = 'SELECT MAX(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MAX(age)'], table: 'student', @@ -564,13 +594,15 @@ test('Parse MAX Aggregate Query', () => { "joinCondition": null, "joinTable": null, "joinType": null, - "orderByFields": null + "orderByFields": null, + "limit": null, + isDistinct: false }); }); test('Parse basic GROUP BY query', () => { const query = 'SELECT age, COUNT(*) FROM student GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -580,13 +612,15 @@ test('Parse basic GROUP BY query', () => { joinTable: null, joinCondition: null, hasAggregateWithoutGroupBy: false, - orderByFields: null + orderByFields: null, + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with WHERE clause', () => { const query = 'SELECT age, COUNT(*) FROM student WHERE age > 22 GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -596,13 +630,15 @@ test('Parse GROUP BY query with WHERE clause', () => { joinTable: null, joinCondition: null, hasAggregateWithoutGroupBy: false, - orderByFields: null + orderByFields: null, + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with multiple fields', () => { const query = 'SELECT student_id, course, COUNT(*) FROM enrollment GROUP BY student_id, course'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student_id', 'course', 'COUNT(*)'], table: 'enrollment', @@ -612,13 +648,15 @@ test('Parse GROUP BY query with multiple fields', () => { joinTable: null, joinCondition: null, hasAggregateWithoutGroupBy: false, - orderByFields: null + orderByFields: null, + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with JOIN and WHERE clauses', () => { const query = 'SELECT student.name, COUNT(*) FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE enrollment.course = "Mathematics" GROUP BY student.name'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student.name', 'COUNT(*)'], table: 'student', @@ -631,7 +669,9 @@ test('Parse GROUP BY query with JOIN and WHERE clauses', () => { right: 'enrollment.student_id' }, hasAggregateWithoutGroupBy: false, - orderByFields: null + orderByFields: null, + "limit": null, + isDistinct: false, }); }); diff --git a/tests/step-12/index.test.js b/tests/step-12/index.test.js index d15c77ef5..d6a7a6913 100644 --- a/tests/step-12/index.test.js +++ b/tests/step-12/index.test.js @@ -1,6 +1,6 @@ -const readCSV = require('../../src/csvReader'); -const {parseQuery, parseJoinClause} = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); @@ -258,7 +258,7 @@ test('Average age of students above a certain age', async () => { test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -269,13 +269,14 @@ test('Parse SQL Query', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -290,13 +291,14 @@ test('Parse SQL Query with WHERE Clause', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -315,13 +317,14 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -332,13 +335,14 @@ test('Parse SQL Query with INNER JOIN', async () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }) }); test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -349,7 +353,8 @@ test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }) }); @@ -397,7 +402,7 @@ test('Returns null for queries without JOIN', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -408,13 +413,14 @@ test('Parse LEFT Join Query Completely', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }) }) test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -425,13 +431,14 @@ test('Parse LEFT Join Query Completely', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }) }) test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age > 22'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -442,13 +449,14 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main tabl groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Physics'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -459,13 +467,14 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join tabl groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age < 25'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -476,13 +485,14 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main tab groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Chemistry'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -493,14 +503,15 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join tab groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse COUNT Aggregate Query', () => { const query = 'SELECT COUNT(*) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['COUNT(*)'], table: 'student', @@ -511,14 +522,15 @@ test('Parse COUNT Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SUM Aggregate Query', () => { const query = 'SELECT SUM(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['SUM(age)'], table: 'student', @@ -529,13 +541,14 @@ test('Parse SUM Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse AVG Aggregate Query', () => { const query = 'SELECT AVG(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['AVG(age)'], table: 'student', @@ -546,13 +559,14 @@ test('Parse AVG Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse MIN Aggregate Query', () => { const query = 'SELECT MIN(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MIN(age)'], table: 'student', @@ -563,13 +577,14 @@ test('Parse MIN Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse MAX Aggregate Query', () => { const query = 'SELECT MAX(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MAX(age)'], table: 'student', @@ -580,13 +595,14 @@ test('Parse MAX Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse basic GROUP BY query', () => { const query = 'SELECT age, COUNT(*) FROM student GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -597,13 +613,14 @@ test('Parse basic GROUP BY query', () => { joinCondition: null, hasAggregateWithoutGroupBy: false, orderByFields: null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with WHERE clause', () => { const query = 'SELECT age, COUNT(*) FROM student WHERE age > 22 GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -614,13 +631,14 @@ test('Parse GROUP BY query with WHERE clause', () => { joinCondition: null, hasAggregateWithoutGroupBy: false, orderByFields: null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with multiple fields', () => { const query = 'SELECT student_id, course, COUNT(*) FROM enrollment GROUP BY student_id, course'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student_id', 'course', 'COUNT(*)'], table: 'enrollment', @@ -631,13 +649,14 @@ test('Parse GROUP BY query with multiple fields', () => { joinCondition: null, hasAggregateWithoutGroupBy: false, orderByFields: null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with JOIN and WHERE clauses', () => { const query = 'SELECT student.name, COUNT(*) FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE enrollment.course = "Mathematics" GROUP BY student.name'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student.name', 'COUNT(*)'], table: 'student', @@ -652,6 +671,7 @@ test('Parse GROUP BY query with JOIN and WHERE clauses', () => { hasAggregateWithoutGroupBy: false, orderByFields: null, "limit": null, + isDistinct: false, }); }); diff --git a/tests/step-13/index.test.js b/tests/step-13/index.test.js index 0797faaba..5fe667f4c 100644 --- a/tests/step-13/index.test.js +++ b/tests/step-13/index.test.js @@ -1,6 +1,6 @@ -const readCSV = require('../../src/csvReader'); -const {parseQuery, parseJoinClause} = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); @@ -258,7 +258,7 @@ test('Average age of students above a certain age', async () => { test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -269,13 +269,14 @@ test('Parse SQL Query', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -290,13 +291,14 @@ test('Parse SQL Query with WHERE Clause', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -315,13 +317,14 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -332,13 +335,14 @@ test('Parse SQL Query with INNER JOIN', async () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }) }); test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -349,7 +353,8 @@ test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }) }); @@ -397,7 +402,7 @@ test('Returns null for queries without JOIN', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -408,13 +413,14 @@ test('Parse LEFT Join Query Completely', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }) }) test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -425,13 +431,14 @@ test('Parse LEFT Join Query Completely', () => { groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }) }) test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age > 22'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -442,13 +449,14 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main tabl groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Physics'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -459,13 +467,14 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join tabl groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age < 25'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -476,13 +485,14 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main tab groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Chemistry'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -493,14 +503,15 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join tab groupByFields: null, hasAggregateWithoutGroupBy: false, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse COUNT Aggregate Query', () => { const query = 'SELECT COUNT(*) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['COUNT(*)'], table: 'student', @@ -511,14 +522,15 @@ test('Parse COUNT Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse SUM Aggregate Query', () => { const query = 'SELECT SUM(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['SUM(age)'], table: 'student', @@ -529,13 +541,14 @@ test('Parse SUM Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse AVG Aggregate Query', () => { const query = 'SELECT AVG(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['AVG(age)'], table: 'student', @@ -546,13 +559,14 @@ test('Parse AVG Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse MIN Aggregate Query', () => { const query = 'SELECT MIN(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MIN(age)'], table: 'student', @@ -563,13 +577,14 @@ test('Parse MIN Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse MAX Aggregate Query', () => { const query = 'SELECT MAX(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MAX(age)'], table: 'student', @@ -580,13 +595,14 @@ test('Parse MAX Aggregate Query', () => { "joinTable": null, "joinType": null, "orderByFields": null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse basic GROUP BY query', () => { const query = 'SELECT age, COUNT(*) FROM student GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -597,13 +613,14 @@ test('Parse basic GROUP BY query', () => { joinCondition: null, hasAggregateWithoutGroupBy: false, orderByFields: null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with WHERE clause', () => { const query = 'SELECT age, COUNT(*) FROM student WHERE age > 22 GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -614,13 +631,14 @@ test('Parse GROUP BY query with WHERE clause', () => { joinCondition: null, hasAggregateWithoutGroupBy: false, orderByFields: null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with multiple fields', () => { const query = 'SELECT student_id, course, COUNT(*) FROM enrollment GROUP BY student_id, course'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student_id', 'course', 'COUNT(*)'], table: 'enrollment', @@ -631,13 +649,14 @@ test('Parse GROUP BY query with multiple fields', () => { joinCondition: null, hasAggregateWithoutGroupBy: false, orderByFields: null, - "limit": null + "limit": null, + isDistinct: false }); }); test('Parse GROUP BY query with JOIN and WHERE clauses', () => { const query = 'SELECT student.name, COUNT(*) FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE enrollment.course = "Mathematics" GROUP BY student.name'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student.name', 'COUNT(*)'], table: 'student', @@ -652,6 +671,7 @@ test('Parse GROUP BY query with JOIN and WHERE clauses', () => { hasAggregateWithoutGroupBy: false, orderByFields: null, "limit": null, + isDistinct: false, }); }); diff --git a/tests/step-14/index.test.js b/tests/step-14/index.test.js index 502411fa7..0bc0e0607 100644 --- a/tests/step-14/index.test.js +++ b/tests/step-14/index.test.js @@ -1,6 +1,6 @@ -const readCSV = require('../../src/csvReader'); -const {parseQuery, parseJoinClause} = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); @@ -258,7 +258,7 @@ test('Average age of students above a certain age', async () => { test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -276,7 +276,7 @@ test('Parse SQL Query', () => { test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -298,7 +298,7 @@ test('Parse SQL Query with WHERE Clause', () => { test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -324,7 +324,7 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -342,7 +342,7 @@ test('Parse SQL Query with INNER JOIN', async () => { test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -402,7 +402,7 @@ test('Returns null for queries without JOIN', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -420,7 +420,7 @@ test('Parse LEFT Join Query Completely', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -438,7 +438,7 @@ test('Parse LEFT Join Query Completely', () => { test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age > 22'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -456,7 +456,7 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main tabl test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Physics'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -474,7 +474,7 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join tabl test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age < 25'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -492,7 +492,7 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main tab test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Chemistry'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -511,7 +511,7 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join tab test('Parse COUNT Aggregate Query', () => { const query = 'SELECT COUNT(*) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['COUNT(*)'], table: 'student', @@ -530,7 +530,7 @@ test('Parse COUNT Aggregate Query', () => { test('Parse SUM Aggregate Query', () => { const query = 'SELECT SUM(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['SUM(age)'], table: 'student', @@ -548,7 +548,7 @@ test('Parse SUM Aggregate Query', () => { test('Parse AVG Aggregate Query', () => { const query = 'SELECT AVG(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['AVG(age)'], table: 'student', @@ -566,7 +566,7 @@ test('Parse AVG Aggregate Query', () => { test('Parse MIN Aggregate Query', () => { const query = 'SELECT MIN(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MIN(age)'], table: 'student', @@ -584,7 +584,7 @@ test('Parse MIN Aggregate Query', () => { test('Parse MAX Aggregate Query', () => { const query = 'SELECT MAX(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MAX(age)'], table: 'student', @@ -602,7 +602,7 @@ test('Parse MAX Aggregate Query', () => { test('Parse basic GROUP BY query', () => { const query = 'SELECT age, COUNT(*) FROM student GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -620,7 +620,7 @@ test('Parse basic GROUP BY query', () => { test('Parse GROUP BY query with WHERE clause', () => { const query = 'SELECT age, COUNT(*) FROM student WHERE age > 22 GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -638,7 +638,7 @@ test('Parse GROUP BY query with WHERE clause', () => { test('Parse GROUP BY query with multiple fields', () => { const query = 'SELECT student_id, course, COUNT(*) FROM enrollment GROUP BY student_id, course'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student_id', 'course', 'COUNT(*)'], table: 'enrollment', @@ -656,7 +656,7 @@ test('Parse GROUP BY query with multiple fields', () => { test('Parse GROUP BY query with JOIN and WHERE clauses', () => { const query = 'SELECT student.name, COUNT(*) FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE enrollment.course = "Mathematics" GROUP BY student.name'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student.name', 'COUNT(*)'], table: 'student', diff --git a/tests/step-15/index.test.js b/tests/step-15/index.test.js index a2aa4daee..dc1fa19ae 100644 --- a/tests/step-15/index.test.js +++ b/tests/step-15/index.test.js @@ -1,6 +1,6 @@ -const readCSV = require('../../src/csvReader'); -const {parseQuery, parseJoinClause} = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); @@ -258,7 +258,7 @@ test('Average age of students above a certain age', async () => { test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -276,7 +276,7 @@ test('Parse SQL Query', () => { test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -298,7 +298,7 @@ test('Parse SQL Query with WHERE Clause', () => { test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -324,7 +324,7 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -342,7 +342,7 @@ test('Parse SQL Query with INNER JOIN', async () => { test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -402,7 +402,7 @@ test('Returns null for queries without JOIN', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -420,7 +420,7 @@ test('Parse LEFT Join Query Completely', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -438,7 +438,7 @@ test('Parse LEFT Join Query Completely', () => { test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age > 22'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -456,7 +456,7 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main tabl test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Physics'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -474,7 +474,7 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join tabl test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age < 25'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -492,7 +492,7 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main tab test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Chemistry'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -511,7 +511,7 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join tab test('Parse COUNT Aggregate Query', () => { const query = 'SELECT COUNT(*) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['COUNT(*)'], table: 'student', @@ -530,7 +530,7 @@ test('Parse COUNT Aggregate Query', () => { test('Parse SUM Aggregate Query', () => { const query = 'SELECT SUM(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['SUM(age)'], table: 'student', @@ -548,7 +548,7 @@ test('Parse SUM Aggregate Query', () => { test('Parse AVG Aggregate Query', () => { const query = 'SELECT AVG(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['AVG(age)'], table: 'student', @@ -566,7 +566,7 @@ test('Parse AVG Aggregate Query', () => { test('Parse MIN Aggregate Query', () => { const query = 'SELECT MIN(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MIN(age)'], table: 'student', @@ -584,7 +584,7 @@ test('Parse MIN Aggregate Query', () => { test('Parse MAX Aggregate Query', () => { const query = 'SELECT MAX(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MAX(age)'], table: 'student', @@ -602,7 +602,7 @@ test('Parse MAX Aggregate Query', () => { test('Parse basic GROUP BY query', () => { const query = 'SELECT age, COUNT(*) FROM student GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -620,7 +620,7 @@ test('Parse basic GROUP BY query', () => { test('Parse GROUP BY query with WHERE clause', () => { const query = 'SELECT age, COUNT(*) FROM student WHERE age > 22 GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -638,7 +638,7 @@ test('Parse GROUP BY query with WHERE clause', () => { test('Parse GROUP BY query with multiple fields', () => { const query = 'SELECT student_id, course, COUNT(*) FROM enrollment GROUP BY student_id, course'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student_id', 'course', 'COUNT(*)'], table: 'enrollment', @@ -656,7 +656,7 @@ test('Parse GROUP BY query with multiple fields', () => { test('Parse GROUP BY query with JOIN and WHERE clauses', () => { const query = 'SELECT student.name, COUNT(*) FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE enrollment.course = "Mathematics" GROUP BY student.name'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student.name', 'COUNT(*)'], table: 'student', diff --git a/tests/step-16/index.test.js b/tests/step-16/index.test.js index a2aa4daee..dc1fa19ae 100644 --- a/tests/step-16/index.test.js +++ b/tests/step-16/index.test.js @@ -1,6 +1,6 @@ -const readCSV = require('../../src/csvReader'); -const {parseQuery, parseJoinClause} = require('../../src/queryParser'); -const executeSELECTQuery = require('../../src/index'); +const {readCSV} = require('../../src/csvReader'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); +const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { const data = await readCSV('./student.csv'); @@ -258,7 +258,7 @@ test('Average age of students above a certain age', async () => { test('Parse SQL Query', () => { const query = 'SELECT id, name FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -276,7 +276,7 @@ test('Parse SQL Query', () => { test('Parse SQL Query with WHERE Clause', () => { const query = 'SELECT id, name FROM student WHERE age = 25'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -298,7 +298,7 @@ test('Parse SQL Query with WHERE Clause', () => { test('Parse SQL Query with Multiple WHERE Clauses', () => { const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['id', 'name'], table: 'student', @@ -324,7 +324,7 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => { test('Parse SQL Query with INNER JOIN', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id=enrollment.student_id'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -342,7 +342,7 @@ test('Parse SQL Query with INNER JOIN', async () => { test('Parse SQL Query with INNER JOIN and WHERE Clause', async () => { const query = 'SELECT student.name, enrollment.course FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE student.age > 20'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -402,7 +402,7 @@ test('Returns null for queries without JOIN', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -420,7 +420,7 @@ test('Parse LEFT Join Query Completely', () => { test('Parse LEFT Join Query Completely', () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id'; - const result = parseQuery(query); + const result = parseSelectQuery(query); expect(result).toEqual({ fields: ['student.name', 'enrollment.course'], table: 'student', @@ -438,7 +438,7 @@ test('Parse LEFT Join Query Completely', () => { test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age > 22'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -456,7 +456,7 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the main tabl test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student LEFT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Physics'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -474,7 +474,7 @@ test('Parse SQL Query with LEFT JOIN with a WHERE clause filtering the join tabl test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main table', async () => { const query = 'SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE student.age < 25'; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -492,7 +492,7 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the main tab test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join table', async () => { const query = `SELECT student.name, enrollment.course FROM student RIGHT JOIN enrollment ON student.id=enrollment.student_id WHERE enrollment.course = 'Chemistry'`; - const result = await parseQuery(query); + const result = await parseSelectQuery(query); expect(result).toEqual({ "fields": ["student.name", "enrollment.course"], "joinCondition": { "left": "student.id", "right": "enrollment.student_id" }, @@ -511,7 +511,7 @@ test('Parse SQL Query with RIGHT JOIN with a WHERE clause filtering the join tab test('Parse COUNT Aggregate Query', () => { const query = 'SELECT COUNT(*) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['COUNT(*)'], table: 'student', @@ -530,7 +530,7 @@ test('Parse COUNT Aggregate Query', () => { test('Parse SUM Aggregate Query', () => { const query = 'SELECT SUM(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['SUM(age)'], table: 'student', @@ -548,7 +548,7 @@ test('Parse SUM Aggregate Query', () => { test('Parse AVG Aggregate Query', () => { const query = 'SELECT AVG(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['AVG(age)'], table: 'student', @@ -566,7 +566,7 @@ test('Parse AVG Aggregate Query', () => { test('Parse MIN Aggregate Query', () => { const query = 'SELECT MIN(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MIN(age)'], table: 'student', @@ -584,7 +584,7 @@ test('Parse MIN Aggregate Query', () => { test('Parse MAX Aggregate Query', () => { const query = 'SELECT MAX(age) FROM student'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['MAX(age)'], table: 'student', @@ -602,7 +602,7 @@ test('Parse MAX Aggregate Query', () => { test('Parse basic GROUP BY query', () => { const query = 'SELECT age, COUNT(*) FROM student GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -620,7 +620,7 @@ test('Parse basic GROUP BY query', () => { test('Parse GROUP BY query with WHERE clause', () => { const query = 'SELECT age, COUNT(*) FROM student WHERE age > 22 GROUP BY age'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['age', 'COUNT(*)'], table: 'student', @@ -638,7 +638,7 @@ test('Parse GROUP BY query with WHERE clause', () => { test('Parse GROUP BY query with multiple fields', () => { const query = 'SELECT student_id, course, COUNT(*) FROM enrollment GROUP BY student_id, course'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student_id', 'course', 'COUNT(*)'], table: 'enrollment', @@ -656,7 +656,7 @@ test('Parse GROUP BY query with multiple fields', () => { test('Parse GROUP BY query with JOIN and WHERE clauses', () => { const query = 'SELECT student.name, COUNT(*) FROM student INNER JOIN enrollment ON student.id = enrollment.student_id WHERE enrollment.course = "Mathematics" GROUP BY student.name'; - const parsed = parseQuery(query); + const parsed = parseSelectQuery(query); expect(parsed).toEqual({ fields: ['student.name', 'COUNT(*)'], table: 'student', diff --git a/tests/step-17/index.test.js b/tests/step-17/index.test.js index c99d01fbb..dc1fa19ae 100644 --- a/tests/step-17/index.test.js +++ b/tests/step-17/index.test.js @@ -1,5 +1,5 @@ const {readCSV} = require('../../src/csvReader'); -const {executeSELECTQuery } = require('../../src/index'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { diff --git a/tests/step-17/insertExecuter.test.js b/tests/step-17/insertExecuter.test.js index 8c405f727..9154b0e2e 100644 --- a/tests/step-17/insertExecuter.test.js +++ b/tests/step-17/insertExecuter.test.js @@ -1,4 +1,4 @@ -const { executeINSERTQuery } = require('../../src/index'); +const { executeINSERTQuery } = require('../../src/queryExecutor'); const { readCSV, writeCSV } = require('../../src/csvReader'); const fs = require('fs'); @@ -9,6 +9,7 @@ async function createGradesCSV() { { student_id: '2', course: 'Chemistry', grade: 'B' }, { student_id: '3', course: 'Mathematics', grade: 'C' } ]; + fs.writeFileSync('grades.csv', ''); await writeCSV('grades.csv', initialData); } diff --git a/tests/step-18/deleteExecutor.test.js b/tests/step-18/deleteExecutor.test.js index 11ae617b7..636403858 100644 --- a/tests/step-18/deleteExecutor.test.js +++ b/tests/step-18/deleteExecutor.test.js @@ -1,4 +1,4 @@ -const { executeDELETEQuery } = require('../../src/index'); +const { executeDELETEQuery } = require('../../src/queryExecutor'); const { readCSV, writeCSV } = require('../../src/csvReader'); const fs = require('fs'); diff --git a/tests/step-18/index.test.js b/tests/step-18/index.test.js index c99d01fbb..dc1fa19ae 100644 --- a/tests/step-18/index.test.js +++ b/tests/step-18/index.test.js @@ -1,5 +1,5 @@ const {readCSV} = require('../../src/csvReader'); -const {executeSELECTQuery } = require('../../src/index'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { diff --git a/tests/step-18/insertExecuter.test.js b/tests/step-18/insertExecuter.test.js index 8c405f727..f9ff4fdf9 100644 --- a/tests/step-18/insertExecuter.test.js +++ b/tests/step-18/insertExecuter.test.js @@ -1,4 +1,4 @@ -const { executeINSERTQuery } = require('../../src/index'); +const { executeINSERTQuery } = require('../../src/queryExecutor'); const { readCSV, writeCSV } = require('../../src/csvReader'); const fs = require('fs'); @@ -12,22 +12,28 @@ async function createGradesCSV() { await writeCSV('grades.csv', initialData); } -// Test to INSERT a new grade and verify -test('Execute INSERT INTO Query for grades.csv', async () => { - // Create grades.csv with initial data - await createGradesCSV(); +// Define the test suite +describe('Insert Executer Tests', () => { + // This is the beforeEach hook + beforeEach(async () => { + // Create grades.csv with initial data before each test + await createGradesCSV(); + }); - // Execute INSERT statement - const insertQuery = "INSERT INTO grades (student_id, course, grade) VALUES ('4', 'Physics', 'A')"; - await executeINSERTQuery(insertQuery); + // Test to INSERT a new grade and verify + test('Execute INSERT INTO Query for grades.csv', async () => { + // Execute INSERT statement + const insertQuery = "INSERT INTO grades (student_id, course, grade) VALUES ('4', 'Physics', 'A')"; + await executeINSERTQuery(insertQuery); - // Verify the new entry - const updatedData = await readCSV('grades.csv'); - const newEntry = updatedData.find(row => row.student_id === '4' && row.course === 'Physics'); - console.log(updatedData) - expect(newEntry).toBeDefined(); - expect(newEntry.grade).toEqual('A'); + // Verify the new entry + const updatedData = await readCSV('grades.csv'); + const newEntry = updatedData.find(row => row.student_id === '4' && row.course === 'Physics'); + console.log(updatedData) + expect(newEntry).toBeDefined(); + expect(newEntry.grade).toEqual('A'); - // Cleanup: Delete grades.csv - fs.unlinkSync('grades.csv'); -}); \ No newline at end of file + // Cleanup: Delete grades.csv + fs.unlinkSync('grades.csv'); + }); +}); diff --git a/tests/step-19/deleteExecutor.test.js b/tests/step-19/deleteExecutor.test.js index 11ae617b7..636403858 100644 --- a/tests/step-19/deleteExecutor.test.js +++ b/tests/step-19/deleteExecutor.test.js @@ -1,4 +1,4 @@ -const { executeDELETEQuery } = require('../../src/index'); +const { executeDELETEQuery } = require('../../src/queryExecutor'); const { readCSV, writeCSV } = require('../../src/csvReader'); const fs = require('fs'); diff --git a/tests/step-19/index.test.js b/tests/step-19/index.test.js index c99d01fbb..dc1fa19ae 100644 --- a/tests/step-19/index.test.js +++ b/tests/step-19/index.test.js @@ -1,5 +1,5 @@ const {readCSV} = require('../../src/csvReader'); -const {executeSELECTQuery } = require('../../src/index'); +const {executeSELECTQuery } = require('../../src/queryExecutor'); const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser'); test('Read CSV File', async () => { diff --git a/tests/step-19/insertExecuter.test.js b/tests/step-19/insertExecuter.test.js index 8c405f727..95453731b 100644 --- a/tests/step-19/insertExecuter.test.js +++ b/tests/step-19/insertExecuter.test.js @@ -1,33 +1,72 @@ -const { executeINSERTQuery } = require('../../src/index'); +const { executeINSERTQuery } = require('../../src/queryExecutor'); const { readCSV, writeCSV } = require('../../src/csvReader'); const fs = require('fs'); -// Helper function to create grades.csv with initial data + async function createGradesCSV() { - const initialData = [ - { student_id: '1', course: 'Mathematics', grade: 'A' }, - { student_id: '2', course: 'Chemistry', grade: 'B' }, - { student_id: '3', course: 'Mathematics', grade: 'C' } - ]; - await writeCSV('grades.csv', initialData); + + const initialData = [ + + { student_id: "1", course: "Mathematics", grade: "A" }, + + { student_id: "2", course: "Chemistry", grade: "B" }, + + { student_id: "3", course: "Mathematics", grade: "C" }, + + ]; + + await writeCSV("grades.csv", initialData); + } + + + // Test to INSERT a new grade and verify -test('Execute INSERT INTO Query for grades.csv', async () => { - // Create grades.csv with initial data - await createGradesCSV(); - - // Execute INSERT statement - const insertQuery = "INSERT INTO grades (student_id, course, grade) VALUES ('4', 'Physics', 'A')"; - await executeINSERTQuery(insertQuery); - - // Verify the new entry - const updatedData = await readCSV('grades.csv'); - const newEntry = updatedData.find(row => row.student_id === '4' && row.course === 'Physics'); - console.log(updatedData) - expect(newEntry).toBeDefined(); - expect(newEntry.grade).toEqual('A'); - - // Cleanup: Delete grades.csv - fs.unlinkSync('grades.csv'); + +test("Execute INSERT INTO Query for grades.csv", async () => { + + // Create grades.csv with initial data + + await createGradesCSV(); + + + + + // Execute INSERT statement + + const insertQuery = + + "INSERT INTO grades (student_id, course, grade) VALUES ('4', 'Physics', 'A')"; + + await executeINSERTQuery(insertQuery); + + + + + // Verify the new entry + + const updatedData = await readCSV("grades.csv"); + + const newEntry = updatedData.find( + + (row) => row.student_id === "4" && row.course === "Physics" + + ); + + + + + + expect(newEntry).toBeDefined(); + + expect(newEntry.grade).toEqual("A"); + + + + + // Cleanup: Delete grades.csv + + fs.unlinkSync("grades.csv"); + }); \ No newline at end of file