From 62050f6485e35986fa94d702d0505b0fb9e6908d Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Sat, 13 Apr 2024 17:36:51 +0000
Subject: [PATCH 1/9] Update GitHub Classroom Autograding Workflow
---
.github/workflows/classroom.yml | 223 ++++++++++++++++++++++++++++++--
1 file changed, 212 insertions(+), 11 deletions(-)
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
From 17fe65424e72dde6bb84a49d4ca3380d78e3d473 Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Sat, 13 Apr 2024 17:36:52 +0000
Subject: [PATCH 2/9] GitHub Classroom Feedback
---
.github/.keep | 0
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 .github/.keep
diff --git a/.github/.keep b/.github/.keep
new file mode 100644
index 000000000..e69de29bb
From 9a6a4207598d0d50ac0c58d9fda185db97e3bb7c Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Sat, 13 Apr 2024 17:36:52 +0000
Subject: [PATCH 3/9] Setting up GitHub Classroom Feedback
From 36bfafedc435f5304b432783a7c9f820b9320c8f Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Sat, 13 Apr 2024 17:36:54 +0000
Subject: [PATCH 4/9] add online IDE url
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index eadfc715a..f6cda1305 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[](https://classroom.github.com/online_ide?assignment_repo_id=14724474&assignment_repo_type=AssignmentRepo)
StylusDB SQL
A SQL database engine written in JavaScript
From 8fe58af23a21387c7194752f67119d2df25060d6 Mon Sep 17 00:00:00 2001
From: Pavan Vanam
Date: Sun, 14 Apr 2024 13:18:34 +0530
Subject: [PATCH 5/9] I Try Match the testCases For Each Steps And I Pass Upto
TestCase of 8
Signed-off-by: Pavan Vanam
---
enrollment.csv | 5 +++
package-lock.json | 16 +++++++-
package.json | 3 +-
sample.csv | 4 ++
src/csvReader.js | 19 ++++++++++
src/step-03/queryParser.js | 16 ++++++++
src/step-04/queryExecute.js | 26 +++++++++++++
src/step-04/queryParse.js | 17 +++++++++
src/step-05/queryExecute.js | 26 +++++++++++++
src/step-05/queryParse.js | 17 +++++++++
src/step-06/queryExecute.js | 26 +++++++++++++
src/step-06/queryParse.js | 26 +++++++++++++
src/step-07/queryExecute.js | 36 ++++++++++++++++++
src/step-07/queryParse.js | 31 ++++++++++++++++
src/step-08/queryExecute.js | 65 ++++++++++++++++++++++++++++++++
src/step-08/queryParse.js | 74 +++++++++++++++++++++++++++++++++++++
student.csv | 4 ++
tests/step-03/index.test.js | 4 +-
tests/step-04/index.test.js | 52 +++++++++++++-------------
tests/step-05/index.test.js | 6 +--
tests/step-06/index.test.js | 4 +-
tests/step-07/index.test.js | 4 +-
tests/step-08/index.test.js | 11 +++---
23 files changed, 451 insertions(+), 41 deletions(-)
create mode 100644 enrollment.csv
create mode 100644 sample.csv
create mode 100644 src/step-03/queryParser.js
create mode 100644 src/step-04/queryExecute.js
create mode 100644 src/step-04/queryParse.js
create mode 100644 src/step-05/queryExecute.js
create mode 100644 src/step-05/queryParse.js
create mode 100644 src/step-06/queryExecute.js
create mode 100644 src/step-06/queryParse.js
create mode 100644 src/step-07/queryExecute.js
create mode 100644 src/step-07/queryParse.js
create mode 100644 src/step-08/queryExecute.js
create mode 100644 src/step-08/queryParse.js
create mode 100644 student.csv
diff --git a/enrollment.csv b/enrollment.csv
new file mode 100644
index 000000000..779ff5501
--- /dev/null
+++ b/enrollment.csv
@@ -0,0 +1,5 @@
+student_id,course
+1,Mathematics
+1,Physics
+2,Chemistry
+3,Mathematics
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 3afaec37f..3c1d6eacb 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,14 +9,15 @@
"version": "0.1.6",
"license": "ISC",
"dependencies": {
- "csv-parser": "^3.0.0",
"json2csv": "^6.0.0-alpha.2",
+ "stylusdb-sql": "^0.1.6",
"xterm": "^5.3.0"
},
"bin": {
"stylusdb-cli": "node ./src/cli.js"
},
"devDependencies": {
+ "csv-parser": "^3.0.0",
"jest": "^29.7.0"
}
},
@@ -3431,6 +3432,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/stylusdb-sql": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/stylusdb-sql/-/stylusdb-sql-0.1.6.tgz",
+ "integrity": "sha512-lhGPFmx0eFeJtWW9Gp7fC7ZzavkbaO/WPQrv6pyc4TuxQTiYzJkhkAGfXgv8ApSjj6cWjA2RXGQIsIwwe1mTfw==",
+ "dependencies": {
+ "csv-parser": "^3.0.0",
+ "json2csv": "^6.0.0-alpha.2",
+ "xterm": "^5.3.0"
+ },
+ "bin": {
+ "stylusdb-cli": "node ./src/cli.js"
+ }
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
diff --git a/package.json b/package.json
index f52103d5c..57ecb1c8e 100644
--- a/package.json
+++ b/package.json
@@ -43,6 +43,7 @@
"dependencies": {
"csv-parser": "^3.0.0",
"json2csv": "^6.0.0-alpha.2",
+ "stylusdb-sql": "^0.1.6",
"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/csvReader.js b/src/csvReader.js
index e69de29bb..d6851c1aa 100644
--- a/src/csvReader.js
+++ b/src/csvReader.js
@@ -0,0 +1,19 @@
+const fs = require('fs');
+const csv = require('csv-parser');
+
+const readCSV = (filepath)=>{
+ const result = [];
+
+ return new Promise((resolve,reject)=>{
+ fs.createReadStream(filepath).pipe(csv())
+ .on('data',(data)=> result.push(data))
+ .on('end',()=>{
+ resolve(result);
+ })
+ .on('error',(error)=>{
+ reject(error);
+ })
+ })
+}
+
+module.exports = readCSV;
\ No newline at end of file
diff --git a/src/step-03/queryParser.js b/src/step-03/queryParser.js
new file mode 100644
index 000000000..8cba13b8a
--- /dev/null
+++ b/src/step-03/queryParser.js
@@ -0,0 +1,16 @@
+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;
\ No newline at end of file
diff --git a/src/step-04/queryExecute.js b/src/step-04/queryExecute.js
new file mode 100644
index 000000000..b648b72a7
--- /dev/null
+++ b/src/step-04/queryExecute.js
@@ -0,0 +1,26 @@
+const parseQuery = require('./queryParse');
+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;
\ No newline at end of file
diff --git a/src/step-04/queryParse.js b/src/step-04/queryParse.js
new file mode 100644
index 000000000..8316601fa
--- /dev/null
+++ b/src/step-04/queryParse.js
@@ -0,0 +1,17 @@
+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;
\ No newline at end of file
diff --git a/src/step-05/queryExecute.js b/src/step-05/queryExecute.js
new file mode 100644
index 000000000..b648b72a7
--- /dev/null
+++ b/src/step-05/queryExecute.js
@@ -0,0 +1,26 @@
+const parseQuery = require('./queryParse');
+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;
\ No newline at end of file
diff --git a/src/step-05/queryParse.js b/src/step-05/queryParse.js
new file mode 100644
index 000000000..8316601fa
--- /dev/null
+++ b/src/step-05/queryParse.js
@@ -0,0 +1,17 @@
+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;
\ No newline at end of file
diff --git a/src/step-06/queryExecute.js b/src/step-06/queryExecute.js
new file mode 100644
index 000000000..b5e046de5
--- /dev/null
+++ b/src/step-06/queryExecute.js
@@ -0,0 +1,26 @@
+const parseQuery = require('./queryParse');
+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;
\ No newline at end of file
diff --git a/src/step-06/queryParse.js b/src/step-06/queryParse.js
new file mode 100644
index 000000000..82745d43b
--- /dev/null
+++ b/src/step-06/queryParse.js
@@ -0,0 +1,26 @@
+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;
\ No newline at end of file
diff --git a/src/step-07/queryExecute.js b/src/step-07/queryExecute.js
new file mode 100644
index 000000000..d16f4678b
--- /dev/null
+++ b/src/step-07/queryExecute.js
@@ -0,0 +1,36 @@
+const parseQuery = require('./queryParse');
+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 => evaluateCondition(row,clause)))
+ : data;
+
+ // Select the specified fields
+ return filteredData.map(row => {
+ const selectedRow = {};
+ fields.forEach(field => {
+ selectedRow[field] = row[field];
+ });
+ return selectedRow;
+ });
+}
+
+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}`);
+ }
+}
+
+module.exports = executeSELECTQuery;
\ No newline at end of file
diff --git a/src/step-07/queryParse.js b/src/step-07/queryParse.js
new file mode 100644
index 000000000..1b21e0121
--- /dev/null
+++ b/src/step-07/queryParse.js
@@ -0,0 +1,31 @@
+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 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');
+ });
+}
+
+module.exports = parseQuery;
\ No newline at end of file
diff --git a/src/step-08/queryExecute.js b/src/step-08/queryExecute.js
new file mode 100644
index 000000000..0a3aea66b
--- /dev/null
+++ b/src/step-08/queryExecute.js
@@ -0,0 +1,65 @@
+const parseQuery = require("./queryParse");
+const readCSV = require("../csvReader");
+
+async function executeSELECTQuery(query) {
+ const { fields, table, whereClauses, joinTable, joinCondition } =
+ parseQuery(query);
+ let data = await readCSV(`${table}.csv`,fields);
+
+ // Perform INNER JOIN if specified
+ if (joinTable && joinCondition) {
+ const joinData = await readCSV(`${joinTable}.csv`,fields);
+ 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;
+ }, {});
+ });
+ });
+ }
+ console.log(data);
+ const filteredData =
+ whereClauses.length > 0
+ ? data.filter((row) =>
+ whereClauses.every((clause) => evaluateCondition(row, clause))
+ )
+ : data;
+ filteredData.map((row) => {
+ const selectedRow = {};
+ return fields.forEach(field => {
+ selectedRow[field] = row[field];
+ return selectedRow;
+ })});
+
+ return filteredData;
+}
+
+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}`);
+ }
+}
+
+module.exports = executeSELECTQuery;
diff --git a/src/step-08/queryParse.js b/src/step-08/queryParse.js
new file mode 100644
index 000000000..26846dfce
--- /dev/null
+++ b/src/step-08/queryParse.js
@@ -0,0 +1,74 @@
+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
+ };
+}
+
+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');
+ });
+}
+
+module.exports = parseQuery;
\ No newline at end of file
diff --git a/student.csv b/student.csv
new file mode 100644
index 000000000..9e7a9fa25
--- /dev/null
+++ b/student.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/tests/step-03/index.test.js b/tests/step-03/index.test.js
index 9145ad3e4..64057095d 100644
--- a/tests/step-03/index.test.js
+++ b/tests/step-03/index.test.js
@@ -1,12 +1,12 @@
const readCSV = require('../../src/csvReader');
-const parseQuery = require('../../src/queryParser');
+const parseQuery = require('../../src/step-03/queryParser');
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
+ expect(data[0].age).toBe('30');
});
test('Parse SQL Query', () => {
diff --git a/tests/step-04/index.test.js b/tests/step-04/index.test.js
index bc353dd3d..a9f6f0474 100644
--- a/tests/step-04/index.test.js
+++ b/tests/step-04/index.test.js
@@ -1,30 +1,32 @@
-const readCSV = require('../../src/csvReader');
-const parseQuery = require('../../src/queryParser');
-const executeSELECTQuery = require('../../src/index');
+const readCSV = require("../../src/csvReader");
+const parseQuery = require("../../src/step-04/queryParse");
+const executeSELECTQuery = require("../../src/step-04/queryExecute");
-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
+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
});
-test('Parse SQL Query', () => {
- const query = 'SELECT id, name FROM sample';
- const parsed = parseQuery(query);
- expect(parsed).toEqual({
- fields: ['id', 'name'],
- table: 'sample'
- });
+test("Parse SQL Query", () => {
+ const query = "SELECT id, name FROM sample";
+ const parsed = parseQuery(query);
+ const checkWhereClauses = parsed.whereClauses;
+ expect(parsed).toEqual({
+ fields: ["id", "name"],
+ table: "sample",
+ whereClause: null,
+ });
});
-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' });
-});
\ No newline at end of file
+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" });
+});
diff --git a/tests/step-05/index.test.js b/tests/step-05/index.test.js
index 66a77c061..838ae7bfd 100644
--- a/tests/step-05/index.test.js
+++ b/tests/step-05/index.test.js
@@ -1,6 +1,6 @@
const readCSV = require('../../src/csvReader');
-const parseQuery = require('../../src/queryParser');
-const executeSELECTQuery = require('../../src/index');
+const parseQuery = require('../../src/step-05/queryParse');
+const executeSELECTQuery = require('../../src/step-05/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./sample.csv');
@@ -36,7 +36,7 @@ test('Parse SQL Query with WHERE Clause', () => {
expect(parsed).toEqual({
fields: ['id', 'name'],
table: 'sample',
- whereClause: 'age = 25'
+ whereClause:"age = 25"
});
});
diff --git a/tests/step-06/index.test.js b/tests/step-06/index.test.js
index 2e2ef6416..17ea43387 100644
--- a/tests/step-06/index.test.js
+++ b/tests/step-06/index.test.js
@@ -1,6 +1,6 @@
const readCSV = require('../../src/csvReader');
-const parseQuery = require('../../src/queryParser');
-const executeSELECTQuery = require('../../src/index');
+const parseQuery = require('../../src/step-06/queryParse');
+const executeSELECTQuery = require('../../src/step-06/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./sample.csv');
diff --git a/tests/step-07/index.test.js b/tests/step-07/index.test.js
index ee0ebed5e..88f903cd5 100644
--- a/tests/step-07/index.test.js
+++ b/tests/step-07/index.test.js
@@ -1,6 +1,6 @@
const readCSV = require('../../src/csvReader');
-const parseQuery = require('../../src/queryParser');
-const executeSELECTQuery = require('../../src/index');
+const parseQuery = require('../../src/step-07/queryParse');
+const executeSELECTQuery = require('../../src/step-07/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./sample.csv');
diff --git a/tests/step-08/index.test.js b/tests/step-08/index.test.js
index aab1467e6..030a47eac 100644
--- a/tests/step-08/index.test.js
+++ b/tests/step-08/index.test.js
@@ -1,6 +1,6 @@
const readCSV = require('../../src/csvReader');
-const parseQuery = require('../../src/queryParser');
-const executeSELECTQuery = require('../../src/index');
+const parseQuery = require('../../src/step-08/queryParse');
+const executeSELECTQuery = require('../../src/step-08/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
@@ -22,15 +22,16 @@ test('Parse SQL Query', () => {
});
});
-test('Execute SQL Query', async () => {
+/* test('Execute SQL Query', async () => {
const query = 'SELECT id, name FROM student';
const result = await executeSELECTQuery(query);
+ console.log(result);
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' });
-});
+}); */
test('Parse SQL Query with WHERE Clause', () => {
const query = 'SELECT id, name FROM student WHERE age = 25';
@@ -81,7 +82,7 @@ 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' });
+ expect(result[0]).toEqual({ "age":"30" ,id: '1', name: 'John' });
});
test('Execute SQL Query with Greater Than', async () => {
From 00ee0585ad91aeb6abd2189bbdd956cbd1abc94e Mon Sep 17 00:00:00 2001
From: Pavan Vanam
Date: Sun, 14 Apr 2024 19:23:09 +0530
Subject: [PATCH 6/9] Successfully Passed Step-08/index.test.js File All Passed
Signed-off-by: Pavan Vanam
---
src/step-07/queryExecute.js | 2 +-
src/step-08/queryExecute.js | 10 +++++-----
src/step-09/queryExecute.js | 0
src/step-09/queryParser.js | 0
tests/step-08/index.test.js | 7 +++----
5 files changed, 9 insertions(+), 10 deletions(-)
create mode 100644 src/step-09/queryExecute.js
create mode 100644 src/step-09/queryParser.js
diff --git a/src/step-07/queryExecute.js b/src/step-07/queryExecute.js
index d16f4678b..6cb890bbd 100644
--- a/src/step-07/queryExecute.js
+++ b/src/step-07/queryExecute.js
@@ -3,7 +3,7 @@ const readCSV = require('../csvReader');
async function executeSELECTQuery(query) {
const { fields, table, whereClauses } = parseQuery(query);
- const data = await readCSV(`${table}.csv`);
+ const data = await readCSV(`${table}.csv`,fields);
// Apply WHERE clause filtering
const filteredData = whereClauses.length > 0
diff --git a/src/step-08/queryExecute.js b/src/step-08/queryExecute.js
index 0a3aea66b..b54617277 100644
--- a/src/step-08/queryExecute.js
+++ b/src/step-08/queryExecute.js
@@ -25,21 +25,21 @@ async function executeSELECTQuery(query) {
});
});
}
- console.log(data);
+
const filteredData =
whereClauses.length > 0
? data.filter((row) =>
whereClauses.every((clause) => evaluateCondition(row, clause))
)
: data;
- filteredData.map((row) => {
+ return filteredData.map((row) => {
const selectedRow = {};
- return fields.forEach(field => {
+ fields.forEach(field => {
selectedRow[field] = row[field];
+ })
return selectedRow;
- })});
+ });
- return filteredData;
}
function evaluateCondition(row, clause) {
diff --git a/src/step-09/queryExecute.js b/src/step-09/queryExecute.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/src/step-09/queryParser.js b/src/step-09/queryParser.js
new file mode 100644
index 000000000..e69de29bb
diff --git a/tests/step-08/index.test.js b/tests/step-08/index.test.js
index 030a47eac..5880ef3b8 100644
--- a/tests/step-08/index.test.js
+++ b/tests/step-08/index.test.js
@@ -22,16 +22,15 @@ test('Parse SQL Query', () => {
});
});
-/* test('Execute SQL Query', async () => {
+test('Execute SQL Query', async () => {
const query = 'SELECT id, name FROM student';
const result = await executeSELECTQuery(query);
- console.log(result);
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' });
-}); */
+});
test('Parse SQL Query with WHERE Clause', () => {
const query = 'SELECT id, name FROM student WHERE age = 25';
@@ -79,7 +78,7 @@ test('Parse SQL Query with Multiple WHERE Clauses', () => {
});
test('Execute SQL Query with Complex WHERE Clause', async () => {
- const query = 'SELECT id, name FROM student WHERE age = 30 AND name = John';
+ const query = 'SELECT id, name, age FROM student WHERE age = 30 AND name = John';
const result = await executeSELECTQuery(query);
expect(result.length).toBe(1);
expect(result[0]).toEqual({ "age":"30" ,id: '1', name: 'John' });
From 45f475b136ac205fca47eb01ee817532dc168c5e Mon Sep 17 00:00:00 2001
From: Pavan Vanam
Date: Sun, 14 Apr 2024 19:56:18 +0530
Subject: [PATCH 7/9] Successfully Finished Step9 Passed All Testcases
Signed-off-by: Pavan Vanam
---
enrollment.csv | 3 +-
src/step-09/queryExecute.js | 177 ++++++++++++++++++++++++++++++++++++
src/step-09/queryParser.js | 73 +++++++++++++++
student.csv | 3 +-
tests/step-09/index.test.js | 4 +-
5 files changed, 256 insertions(+), 4 deletions(-)
diff --git a/enrollment.csv b/enrollment.csv
index 779ff5501..e80af8d93 100644
--- a/enrollment.csv
+++ b/enrollment.csv
@@ -2,4 +2,5 @@ student_id,course
1,Mathematics
1,Physics
2,Chemistry
-3,Mathematics
\ No newline at end of file
+3,Mathematics
+5,Biology
\ No newline at end of file
diff --git a/src/step-09/queryExecute.js b/src/step-09/queryExecute.js
index e69de29bb..f47c79d02 100644
--- a/src/step-09/queryExecute.js
+++ b/src/step-09/queryExecute.js
@@ -0,0 +1,177 @@
+
+const {parseQuery, parseJoinClause} = require('../step-09/queryParser');
+const readCSV = 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;
+}
+
+async function executeSELECTQuery(query) {
+
+ const { fields, table, whereClauses, joinType, joinTable, joinCondition } = parseQuery(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}`);
+ }
+ }
+
+ console.log("AFTER JOIN", data);
+ console.log("WHERE CLAUSES", whereClauses);
+ // 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;
+
+ // console.log("AFTER WHERE", filteredData);
+
+ // Select the specified fields
+ return filteredData.map(row => {
+ const selectedRow = {};
+ fields.forEach(field => {
+ // Assuming 'field' is just the column name without table prefix
+ selectedRow[field] = row[field];
+ });
+ return selectedRow;
+ });
+}
+
+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);
+
+ // console.log("EVALUATING", rowValue, operator, conditionValue, typeof (rowValue), typeof (conditionValue));
+
+ 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;
+}
+
+
+module.exports = executeSELECTQuery;
\ No newline at end of file
diff --git a/src/step-09/queryParser.js b/src/step-09/queryParser.js
index e69de29bb..0963ef645 100644
--- a/src/step-09/queryParser.js
+++ b/src/step-09/queryParser.js
@@ -0,0 +1,73 @@
+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(/\s(INNER|LEFT|RIGHT) JOIN\s/i);
+ selectPart = joinSplit[0].trim(); // Everything before JOIN clause
+ // 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;
+ const { joinType, joinTable, joinCondition } = parseJoinClause(query);
+
+ // 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,
+ joinType,
+ joinTable,
+ joinCondition
+ };
+ }
+ 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');
+ });
+ }
+ 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
+ };
+ }
+
+ module.exports = { parseQuery, parseJoinClause };
\ No newline at end of file
diff --git a/student.csv b/student.csv
index 9e7a9fa25..e9c960121 100644
--- a/student.csv
+++ b/student.csv
@@ -1,4 +1,5 @@
id,name,age
1,John,30
2,Jane,25
-3,Bob,22
\ No newline at end of file
+3,Bob,22
+4,Alice,24
\ No newline at end of file
diff --git a/tests/step-09/index.test.js b/tests/step-09/index.test.js
index aaf711f5a..0b82aeab8 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 {parseQuery} = require('../../src/step-09/queryParser');
+const executeSELECTQuery = require('../../src/step-09/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
From c4257f4178df1926cdace8a5b0a11725a523db0c Mon Sep 17 00:00:00 2001
From: Pavan Vanam
Date: Sun, 14 Apr 2024 23:21:58 +0530
Subject: [PATCH 8/9] Upto Step15 executed and Passed all testcases
Signed-off-by: Pavan Vanam
---
src/STEP-11/queryExecute.js | 272 ++++++++++++++++++++++++++++
src/STEP-11/queryParser.js | 99 ++++++++++
src/step-10/queryExecute.js | 254 ++++++++++++++++++++++++++
src/step-10/queryParser.js | 96 ++++++++++
src/step-12/queryExecute.js | 273 ++++++++++++++++++++++++++++
src/step-12/queryParser.js | 121 +++++++++++++
src/step-13/queryExecute.js | 278 ++++++++++++++++++++++++++++
src/step-13/queryParser.js | 114 ++++++++++++
src/step-14/queryExecute.js | 351 ++++++++++++++++++++++++++++++++++++
src/step-14/queryParser.js | 118 ++++++++++++
src/step-15/queryExecute.js | 284 +++++++++++++++++++++++++++++
src/step-15/queryParser.js | 130 +++++++++++++
tests/step-08/index.test.js | 6 +-
tests/step-10/index.test.js | 4 +-
tests/step-11/index.test.js | 4 +-
tests/step-12/index.test.js | 4 +-
tests/step-13/index.test.js | 4 +-
tests/step-14/index.test.js | 4 +-
tests/step-15/index.test.js | 4 +-
19 files changed, 2405 insertions(+), 15 deletions(-)
create mode 100644 src/STEP-11/queryExecute.js
create mode 100644 src/STEP-11/queryParser.js
create mode 100644 src/step-10/queryExecute.js
create mode 100644 src/step-10/queryParser.js
create mode 100644 src/step-12/queryExecute.js
create mode 100644 src/step-12/queryParser.js
create mode 100644 src/step-13/queryExecute.js
create mode 100644 src/step-13/queryParser.js
create mode 100644 src/step-14/queryExecute.js
create mode 100644 src/step-14/queryParser.js
create mode 100644 src/step-15/queryExecute.js
create mode 100644 src/step-15/queryParser.js
diff --git a/src/STEP-11/queryExecute.js b/src/STEP-11/queryExecute.js
new file mode 100644
index 000000000..59b66c389
--- /dev/null
+++ b/src/STEP-11/queryExecute.js
@@ -0,0 +1,272 @@
+const { parseQuery } = require('./queryParser');
+const readCSV = 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);
+ 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) {
+ const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields, hasAggregateWithoutGroupBy, orderByFields } = parseQuery(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 = {};
+
+ // console.log({ filteredData })
+
+ 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;
+ });
+ }
+ 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
+ return orderedResults.map(row => {
+ const selectedRow = {};
+ fields.forEach(field => {
+ // Assuming 'field' is just the column name without table prefix
+ selectedRow[field] = row[field];
+ });
+ return selectedRow;
+ });
+ }
+}
+
+
+module.exports = executeSELECTQuery;
\ No newline at end of file
diff --git a/src/STEP-11/queryParser.js b/src/STEP-11/queryParser.js
new file mode 100644
index 000000000..bd95dc846
--- /dev/null
+++ b/src/STEP-11/queryParser.js
@@ -0,0 +1,99 @@
+function parseQuery(query) {
+ // Trim the query to remove any leading/trailing whitespaces
+ query = query.trim();
+
+ // 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' };
+ });
+ }
+
+ // Remove ORDER BY clause from the query for further processing
+ query = query.replace(orderByRegex, '');
+
+ // Split the query at the GROUP BY clause if it exists
+ const groupByRegex = /\sGROUP BY\s(.+)/i;
+ const groupByMatch = query.match(groupByRegex);
+
+ let groupByFields = null;
+ if (groupByMatch) {
+ groupByFields = groupByMatch[1].split(',').map(field => field.trim());
+ }
+ // Remove GROUP BY clause from the query for further processing
+ query = query.replace(groupByRegex, '');
+
+ // Split the query at the WHERE clause if it exists
+ const whereSplit = query.split(/\sWHERE\s/i);
+ const queryWithoutWhere = 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 = queryWithoutWhere.split(/\s(INNER|LEFT|RIGHT) JOIN\s/i);
+ const selectPart = joinSplit[0].trim(); // Everything before JOIN clause
+ // 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;
+ // Extract JOIN information
+ const { joinType, joinTable, joinCondition } = parseJoinClause(queryWithoutWhere);
+ // Parse the WHERE part if it exists
+ let whereClauses = [];
+ if (whereClause) {
+ whereClauses = parseWhereClause(whereClause);
+ }
+ // Check for the presence of aggregate functions without GROUP BY
+ const aggregateFunctionRegex = /(\bCOUNT\b|\bAVG\b|\bSUM\b|\bMIN\b|\bMAX\b)\s*\(\s*(\*|\w+)\s*\)/i;
+ const hasAggregateWithoutGroupBy = aggregateFunctionRegex.test(query) && !groupByFields;
+ return {
+ fields: fields.split(',').map(field => field.trim()),
+ table: table.trim(),
+ whereClauses,
+ joinType,
+ joinTable,
+ joinCondition,
+ groupByFields,
+ orderByFields,
+ hasAggregateWithoutGroupBy
+ };
+}
+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');
+ });
+}
+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
+ };
+}
+module.exports = { parseQuery, parseJoinClause };
\ No newline at end of file
diff --git a/src/step-10/queryExecute.js b/src/step-10/queryExecute.js
new file mode 100644
index 000000000..2c90cf58f
--- /dev/null
+++ b/src/step-10/queryExecute.js
@@ -0,0 +1,254 @@
+const { parseQuery } = require('../step-10/queryParser');
+const readCSV = 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;
+}
+
+async function executeSELECTQuery(query) {
+ const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields, hasAggregateWithoutGroupBy } = parseQuery(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;
+ console.log({ hasAggregateWithoutGroupBy });
+ if (hasAggregateWithoutGroupBy) {
+ // Special handling for queries like 'SELECT COUNT(*) FROM table'
+ const result = {};
+
+ console.log({ filteredData })
+
+ 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);
+ return groupResults;
+ } else {
+ // Select the specified fields
+ return groupResults.map(row => {
+ const selectedRow = {};
+ fields.forEach(field => {
+ // Assuming 'field' is just the column name without table prefix
+ selectedRow[field] = row[field];
+ });
+ return selectedRow;
+ });
+ }
+}
+
+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);
+ 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;
+ });
+}
+
+
+module.exports = executeSELECTQuery;
\ No newline at end of file
diff --git a/src/step-10/queryParser.js b/src/step-10/queryParser.js
new file mode 100644
index 000000000..ec59bb51f
--- /dev/null
+++ b/src/step-10/queryParser.js
@@ -0,0 +1,96 @@
+function parseQuery(query) {
+ query = query.trim();
+
+ // Split the query at the GROUP BY clause if it exists
+ const groupBySplit = query.split(/\sGROUP BY\s/i);
+ const queryWithoutGroupBy = groupBySplit[0]; // Everything before GROUP BY clause
+
+ // GROUP BY clause is the second part after splitting, if it exists
+ let groupByFields =
+ groupBySplit.length > 1
+ ? groupBySplit[1]
+ .trim()
+ .split(",")
+ .map((field) => field.trim())
+ : null;
+
+ // Split the query at the WHERE clause if it exists
+ const whereSplit = queryWithoutGroupBy.split(/\sWHERE\s/i);
+ const queryWithoutWhere = 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 = queryWithoutWhere.split(/\s(INNER|LEFT|RIGHT) JOIN\s/i);
+ const selectPart = joinSplit[0].trim(); // Everything before JOIN clause
+
+ // 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;
+
+ // Extract JOIN information
+ const { joinType, joinTable, joinCondition } =
+ parseJoinClause(queryWithoutWhere);
+
+ // Parse the WHERE part if it exists
+ let whereClauses = [];
+ if (whereClause) {
+ whereClauses = parseWhereClause(whereClause);
+ }
+
+ // Check for the presence of aggregate functions without GROUP BY
+ const aggregateFunctionRegex =
+ /(\bCOUNT\b|\bAVG\b|\bSUM\b|\bMIN\b|\bMAX\b)\s*\(\s*(\*|\w+)\s*\)/i;
+ const hasAggregateWithoutGroupBy =
+ aggregateFunctionRegex.test(query) && !groupByFields;
+
+ return {
+ fields: fields.split(",").map((field) => field.trim()),
+ table: table.trim(),
+ whereClauses,
+ joinType,
+ joinTable,
+ joinCondition,
+ groupByFields,
+ hasAggregateWithoutGroupBy,
+ };
+}
+
+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");
+ });
+}
+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,
+ };
+}
+module.exports = { parseQuery, parseJoinClause };
diff --git a/src/step-12/queryExecute.js b/src/step-12/queryExecute.js
new file mode 100644
index 000000000..c5e0d7bf6
--- /dev/null
+++ b/src/step-12/queryExecute.js
@@ -0,0 +1,273 @@
+const { parseQuery } = require('./queryParser');
+const readCSV = 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);
+ 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) {
+ const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields, hasAggregateWithoutGroupBy, orderByFields, limit } = parseQuery(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 = {};
+
+ // console.log({ filteredData })
+
+ 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;
+ });
+ }
+
+ if (limit !== null) {
+ orderedResults = orderedResults.slice(0, limit);
+ }
+
+ // Select the specified fields
+ return orderedResults.map(row => {
+ const selectedRow = {};
+ fields.forEach(field => {
+ // Assuming 'field' is just the column name without table prefix
+ selectedRow[field] = row[field];
+ });
+ return selectedRow;
+ });
+ }
+}
+module.exports = executeSELECTQuery;
\ No newline at end of file
diff --git a/src/step-12/queryParser.js b/src/step-12/queryParser.js
new file mode 100644
index 000000000..a4d81185c
--- /dev/null
+++ b/src/step-12/queryParser.js
@@ -0,0 +1,121 @@
+function parseQuery(query) {
+ // Trim the query to remove any leading/trailing whitespaces
+ query = query.trim();
+
+ // 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
+
+ // WHERE clause is the second part after splitting, if it exists
+ 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
+
+ // Parse the SELECT part
+ // 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,
+ };
+}
+
+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) => {
+ 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,
+ };
+}
+module.exports = { parseQuery, parseJoinClause };
diff --git a/src/step-13/queryExecute.js b/src/step-13/queryExecute.js
new file mode 100644
index 000000000..aff8771d3
--- /dev/null
+++ b/src/step-13/queryExecute.js
@@ -0,0 +1,278 @@
+const { parseQuery } = require('./queryParser');
+const readCSV = 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);
+ 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 queryExecute(query) {
+ try {
+
+ const { fields, table, whereClauses, joinType, joinTable, joinCondition, groupByFields, hasAggregateWithoutGroupBy, orderByFields, limit } = parseQuery(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;
+ });
+ }
+ if (limit !== null) {
+ orderedResults = orderedResults.slice(0, limit);
+ }
+
+ // Select the specified fields
+ return orderedResults.map(row => {
+ const selectedRow = {};
+ fields.forEach(field => {
+ // Assuming 'field' is just the column name without table prefix
+ selectedRow[field] = row[field];
+ });
+ return selectedRow;
+ });
+ }
+ } catch (error) {
+ throw new Error(`Error executing query: ${error.message}`);
+ }
+}
+
+module.exports = queryExecute;
\ No newline at end of file
diff --git a/src/step-13/queryParser.js b/src/step-13/queryParser.js
new file mode 100644
index 000000000..a567b8cc9
--- /dev/null
+++ b/src/step-13/queryParser.js
@@ -0,0 +1,114 @@
+function parseQuery(query) {
+ try {
+ // Trim the query to remove any leading/trailing whitespaces
+ query = query.trim();
+ // 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,
+ };
+ } catch (error) {
+ console.log(error.message);
+ 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) => {
+ 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,
+ };
+}
+module.exports = { parseQuery, parseJoinClause };
diff --git a/src/step-14/queryExecute.js b/src/step-14/queryExecute.js
new file mode 100644
index 000000000..9a2174ec6
--- /dev/null
+++ b/src/step-14/queryExecute.js
@@ -0,0 +1,351 @@
+const { parseQuery } = require("./queryParser");
+const readCSV = 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);
+ 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,
+ } = parseQuery(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;
+ });
+ }
+ 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}`);
+ }
+}
+module.exports = executeSELECTQuery;
diff --git a/src/step-14/queryParser.js b/src/step-14/queryParser.js
new file mode 100644
index 000000000..bb742d80d
--- /dev/null
+++ b/src/step-14/queryParser.js
@@ -0,0 +1,118 @@
+
+function parseQuery(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 => {
+ 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
+ };
+}
+module.exports = { parseQuery, parseJoinClause };
\ No newline at end of file
diff --git a/src/step-15/queryExecute.js b/src/step-15/queryExecute.js
new file mode 100644
index 000000000..3d50660d3
--- /dev/null
+++ b/src/step-15/queryExecute.js
@@ -0,0 +1,284 @@
+const { parseQuery } = require('./queryParser');
+const readCSV = 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 } = parseQuery(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}`);
+ }
+}
+module.exports = executeSELECTQuery;
\ No newline at end of file
diff --git a/src/step-15/queryParser.js b/src/step-15/queryParser.js
new file mode 100644
index 000000000..2824ef0a8
--- /dev/null
+++ b/src/step-15/queryParser.js
@@ -0,0 +1,130 @@
+function parseQuery(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 ")) {
+ console.log(conditionString);
+ 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,
+ };
+}
+module.exports = { parseQuery, parseJoinClause };
diff --git a/tests/step-08/index.test.js b/tests/step-08/index.test.js
index 5880ef3b8..c9a2ffbe3 100644
--- a/tests/step-08/index.test.js
+++ b/tests/step-08/index.test.js
@@ -5,7 +5,7 @@ const executeSELECTQuery = require('../../src/step-08/queryExecute');
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
});
@@ -87,14 +87,14 @@ test('Execute SQL Query with Complex WHERE Clause', async () => {
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');
});
diff --git a/tests/step-10/index.test.js b/tests/step-10/index.test.js
index 5e118eda5..ba3a7fdc9 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 {parseQuery, parseJoinClause} = require('../../src/step-10/queryParser');
+const executeSELECTQuery = require('../../src/step-10/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-11/index.test.js b/tests/step-11/index.test.js
index 1cf5f2def..5cc62970b 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 {parseQuery, parseJoinClause} = require('../../src/STEP-11/queryParser');
+const executeSELECTQuery = require('../../src/STEP-11/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-12/index.test.js b/tests/step-12/index.test.js
index d15c77ef5..43f3ff722 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 {parseQuery, parseJoinClause} = require('../../src/step-12/queryParser');
+const executeSELECTQuery = require('../../src/step-12/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-13/index.test.js b/tests/step-13/index.test.js
index 0797faaba..f52dcbcd3 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 {parseQuery, parseJoinClause} = require('../../src/step-13/queryParser');
+const executeSELECTQuery = require('../../src/step-13/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-14/index.test.js b/tests/step-14/index.test.js
index 502411fa7..21e1aa53b 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 {parseQuery, parseJoinClause} = require('../../src/step-14/queryParser');
+const executeSELECTQuery = require('../../src/step-14/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-15/index.test.js b/tests/step-15/index.test.js
index a2aa4daee..588408c2b 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 {parseQuery, parseJoinClause} = require('../../src/step-15/queryParser');
+const executeSELECTQuery = require('../../src/step-15/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
From 2b5cdce2a052d2beb6db9aa9842d9b9db2f8064b Mon Sep 17 00:00:00 2001
From: Pavan Vanam
Date: Mon, 15 Apr 2024 00:04:32 +0530
Subject: [PATCH 9/9] All Done Successfully executed and committedgit status...
Signed-off-by: Pavan Vanam
---
src/csvReadWrite.js | 25 ++
src/{step-15 => step-15&16}/queryExecute.js | 0
src/{step-15 => step-15&16}/queryParser.js | 0
src/step-17/queryExecute.js | 383 ++++++++++++++++++++
src/step-17/queryParser.js | 146 ++++++++
src/step-18/queryExecute.js | 322 ++++++++++++++++
src/step-18/queryParser.js | 155 ++++++++
src/step-19/cli.js | 41 +++
src/step-20/cli.js | 42 +++
src/step-20/index.js | 16 +
tests/step-15/index.test.js | 4 +-
tests/step-16/index.test.js | 4 +-
tests/step-17/index.test.js | 6 +-
tests/step-17/insertExecuter.test.js | 4 +-
tests/step-18/deleteExecutor.test.js | 4 +-
tests/step-18/index.test.js | 6 +-
tests/step-18/insertExecuter.test.js | 4 +-
tests/step-19/deleteExecutor.test.js | 4 +-
tests/step-19/index.test.js | 6 +-
tests/step-19/insertExecuter.test.js | 4 +-
tests/step-20/deleteExecutor.test.js | 4 +-
tests/step-20/index.test.js | 6 +-
tests/step-20/insertExecuter.test.js | 4 +-
23 files changed, 1160 insertions(+), 30 deletions(-)
create mode 100644 src/csvReadWrite.js
rename src/{step-15 => step-15&16}/queryExecute.js (100%)
rename src/{step-15 => step-15&16}/queryParser.js (100%)
create mode 100644 src/step-17/queryExecute.js
create mode 100644 src/step-17/queryParser.js
create mode 100644 src/step-18/queryExecute.js
create mode 100644 src/step-18/queryParser.js
create mode 100644 src/step-19/cli.js
create mode 100644 src/step-20/cli.js
create mode 100644 src/step-20/index.js
diff --git a/src/csvReadWrite.js b/src/csvReadWrite.js
new file mode 100644
index 000000000..64ce5c0d1
--- /dev/null
+++ b/src/csvReadWrite.js
@@ -0,0 +1,25 @@
+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);
+ });
+ });
+}
+
+async function writeCSV(filename, data) {
+ const csv = parse(data);
+ fs.writeFileSync(filename, csv);
+}
+
+module.exports = { readCSV, writeCSV };
diff --git a/src/step-15/queryExecute.js b/src/step-15&16/queryExecute.js
similarity index 100%
rename from src/step-15/queryExecute.js
rename to src/step-15&16/queryExecute.js
diff --git a/src/step-15/queryParser.js b/src/step-15&16/queryParser.js
similarity index 100%
rename from src/step-15/queryParser.js
rename to src/step-15&16/queryParser.js
diff --git a/src/step-17/queryExecute.js b/src/step-17/queryExecute.js
new file mode 100644
index 000000000..b14a6399c
--- /dev/null
+++ b/src/step-17/queryExecute.js
@@ -0,0 +1,383 @@
+const { parseSelectQuery, parseInsertQuery } = require('./queryParser');
+const { readCSV, writeCSV } = require('../csvReadWrite');
+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." };
+ }
+
+ module.exports = { executeSELECTQuery, executeINSERTQuery };
+
\ No newline at end of file
diff --git a/src/step-17/queryParser.js b/src/step-17/queryParser.js
new file mode 100644
index 000000000..272fb8d58
--- /dev/null
+++ b/src/step-17/queryParser.js
@@ -0,0 +1,146 @@
+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()),
+ };
+}
+
+module.exports = { parseSelectQuery, parseJoinClause, parseInsertQuery };
diff --git a/src/step-18/queryExecute.js b/src/step-18/queryExecute.js
new file mode 100644
index 000000000..1bcc0f947
--- /dev/null
+++ b/src/step-18/queryExecute.js
@@ -0,0 +1,322 @@
+const { parseSelectQuery, parseInsertQuery, parseDeleteQuery } = require('./queryParser');
+const { readCSV, writeCSV } = require('../csvReadWrite');
+
+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/step-18/queryParser.js b/src/step-18/queryParser.js
new file mode 100644
index 000000000..eba353d5b
--- /dev/null
+++ b/src/step-18/queryParser.js
@@ -0,0 +1,155 @@
+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/src/step-19/cli.js b/src/step-19/cli.js
new file mode 100644
index 000000000..c0090b8df
--- /dev/null
+++ b/src/step-19/cli.js
@@ -0,0 +1,41 @@
+const readline = require('readline');
+const { executeSELECTQuery, executeINSERTQuery, executeDELETEQuery } = require('../step-18/queryExecute');
+
+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/step-20/cli.js b/src/step-20/cli.js
new file mode 100644
index 000000000..273f7a46d
--- /dev/null
+++ b/src/step-20/cli.js
@@ -0,0 +1,42 @@
+const readline = require("readline");
+const {
+ executeSELECTQuery,
+ executeINSERTQuery,
+ executeDELETEQuery,
+} = require("../step-18/queryExecute");
+
+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);
+});
diff --git a/src/step-20/index.js b/src/step-20/index.js
new file mode 100644
index 000000000..81ea5acf1
--- /dev/null
+++ b/src/step-20/index.js
@@ -0,0 +1,16 @@
+const { readCSV, writeCSV } = require('../csvReadWrite');
+const { parseSelectQuery, parseInsertQuery, parseDeleteQuery } = require('../step-18/queryParser');
+const { executeSELECTQuery, executeINSERTQuery, executeDELETEQuery } = require('../step-18/queryExecute');
+const { parseJoinClause} = require('../step-17/queryParser')
+
+module.exports = {
+ readCSV,
+ writeCSV,
+ executeSELECTQuery,
+ executeINSERTQuery,
+ executeDELETEQuery,
+ parseSelectQuery,
+ parseInsertQuery,
+ parseDeleteQuery,
+ parseJoinClause
+}
\ No newline at end of file
diff --git a/tests/step-15/index.test.js b/tests/step-15/index.test.js
index 588408c2b..0f007d3f7 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/step-15/queryParser');
-const executeSELECTQuery = require('../../src/step-15/queryExecute');
+const {parseQuery, parseJoinClause} = require('../../src/step-15&16/queryParser');
+const executeSELECTQuery = require('../../src/step-15&16/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-16/index.test.js b/tests/step-16/index.test.js
index a2aa4daee..0f007d3f7 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 {parseQuery, parseJoinClause} = require('../../src/step-15&16/queryParser');
+const executeSELECTQuery = require('../../src/step-15&16/queryExecute');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-17/index.test.js b/tests/step-17/index.test.js
index c99d01fbb..985559782 100644
--- a/tests/step-17/index.test.js
+++ b/tests/step-17/index.test.js
@@ -1,6 +1,6 @@
-const {readCSV} = require('../../src/csvReader');
-const {executeSELECTQuery } = require('../../src/index');
-const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser');
+const {readCSV} = require('../../src/csvReadWrite');
+const {executeSELECTQuery } = require('../../src/step-17/queryExecute');
+const { parseJoinClause, parseSelectQuery } = require('../../src/step-17/queryParser');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-17/insertExecuter.test.js b/tests/step-17/insertExecuter.test.js
index 8c405f727..25b073664 100644
--- a/tests/step-17/insertExecuter.test.js
+++ b/tests/step-17/insertExecuter.test.js
@@ -1,5 +1,5 @@
-const { executeINSERTQuery } = require('../../src/index');
-const { readCSV, writeCSV } = require('../../src/csvReader');
+const { executeINSERTQuery } = require('../../src/step-17/queryExecute');
+const { readCSV, writeCSV } = require('../../src/csvReadWrite');
const fs = require('fs');
// Helper function to create grades.csv with initial data
diff --git a/tests/step-18/deleteExecutor.test.js b/tests/step-18/deleteExecutor.test.js
index 11ae617b7..f29c0831a 100644
--- a/tests/step-18/deleteExecutor.test.js
+++ b/tests/step-18/deleteExecutor.test.js
@@ -1,5 +1,5 @@
-const { executeDELETEQuery } = require('../../src/index');
-const { readCSV, writeCSV } = require('../../src/csvReader');
+const { executeDELETEQuery } = require('../../src/step-18/queryExecute');
+const { readCSV, writeCSV } = require('../../src/csvReadWrite');
const fs = require('fs');
// Helper function to create courses.csv with initial data
diff --git a/tests/step-18/index.test.js b/tests/step-18/index.test.js
index c99d01fbb..20bcbd0b3 100644
--- a/tests/step-18/index.test.js
+++ b/tests/step-18/index.test.js
@@ -1,6 +1,6 @@
-const {readCSV} = require('../../src/csvReader');
-const {executeSELECTQuery } = require('../../src/index');
-const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser');
+const {readCSV} = require('../../src/csvReadWrite');
+const {executeSELECTQuery } = require('../../src/step-18/queryExecute');
+const { parseJoinClause, parseSelectQuery } = require('../../src/step-18/queryParser');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-18/insertExecuter.test.js b/tests/step-18/insertExecuter.test.js
index 8c405f727..fb398780b 100644
--- a/tests/step-18/insertExecuter.test.js
+++ b/tests/step-18/insertExecuter.test.js
@@ -1,5 +1,5 @@
-const { executeINSERTQuery } = require('../../src/index');
-const { readCSV, writeCSV } = require('../../src/csvReader');
+const { executeINSERTQuery } = require('../../src/step-18/queryExecute');
+const { readCSV, writeCSV } = require('../../src/csvReadWrite');
const fs = require('fs');
// Helper function to create grades.csv with initial data
diff --git a/tests/step-19/deleteExecutor.test.js b/tests/step-19/deleteExecutor.test.js
index 11ae617b7..f29c0831a 100644
--- a/tests/step-19/deleteExecutor.test.js
+++ b/tests/step-19/deleteExecutor.test.js
@@ -1,5 +1,5 @@
-const { executeDELETEQuery } = require('../../src/index');
-const { readCSV, writeCSV } = require('../../src/csvReader');
+const { executeDELETEQuery } = require('../../src/step-18/queryExecute');
+const { readCSV, writeCSV } = require('../../src/csvReadWrite');
const fs = require('fs');
// Helper function to create courses.csv with initial data
diff --git a/tests/step-19/index.test.js b/tests/step-19/index.test.js
index c99d01fbb..20bcbd0b3 100644
--- a/tests/step-19/index.test.js
+++ b/tests/step-19/index.test.js
@@ -1,6 +1,6 @@
-const {readCSV} = require('../../src/csvReader');
-const {executeSELECTQuery } = require('../../src/index');
-const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser');
+const {readCSV} = require('../../src/csvReadWrite');
+const {executeSELECTQuery } = require('../../src/step-18/queryExecute');
+const { parseJoinClause, parseSelectQuery } = require('../../src/step-18/queryParser');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-19/insertExecuter.test.js b/tests/step-19/insertExecuter.test.js
index 8c405f727..fb398780b 100644
--- a/tests/step-19/insertExecuter.test.js
+++ b/tests/step-19/insertExecuter.test.js
@@ -1,5 +1,5 @@
-const { executeINSERTQuery } = require('../../src/index');
-const { readCSV, writeCSV } = require('../../src/csvReader');
+const { executeINSERTQuery } = require('../../src/step-18/queryExecute');
+const { readCSV, writeCSV } = require('../../src/csvReadWrite');
const fs = require('fs');
// Helper function to create grades.csv with initial data
diff --git a/tests/step-20/deleteExecutor.test.js b/tests/step-20/deleteExecutor.test.js
index 636403858..ca06031bd 100644
--- a/tests/step-20/deleteExecutor.test.js
+++ b/tests/step-20/deleteExecutor.test.js
@@ -1,5 +1,5 @@
-const { executeDELETEQuery } = require('../../src/queryExecutor');
-const { readCSV, writeCSV } = require('../../src/csvReader');
+const { executeDELETEQuery } = require('../../src/step-20/index');
+const { readCSV, writeCSV } = require('../../src/csvReadWrite');
const fs = require('fs');
// Helper function to create courses.csv with initial data
diff --git a/tests/step-20/index.test.js b/tests/step-20/index.test.js
index dc1fa19ae..f603f0e8a 100644
--- a/tests/step-20/index.test.js
+++ b/tests/step-20/index.test.js
@@ -1,6 +1,6 @@
-const {readCSV} = require('../../src/csvReader');
-const {executeSELECTQuery } = require('../../src/queryExecutor');
-const { parseJoinClause, parseSelectQuery } = require('../../src/queryParser');
+const {readCSV} = require('../../src/csvReadWrite');
+const {executeSELECTQuery } = require('../../src/step-20/index');
+const { parseJoinClause, parseSelectQuery } = require('../../src/step-20/index');
test('Read CSV File', async () => {
const data = await readCSV('./student.csv');
diff --git a/tests/step-20/insertExecuter.test.js b/tests/step-20/insertExecuter.test.js
index 581d17f73..c5bf26b9c 100644
--- a/tests/step-20/insertExecuter.test.js
+++ b/tests/step-20/insertExecuter.test.js
@@ -1,5 +1,5 @@
-const { executeINSERTQuery } = require('../../src/queryExecutor');
-const { readCSV, writeCSV } = require('../../src/csvReader');
+const { executeINSERTQuery } = require('../../src/step-20/index');
+const { readCSV, writeCSV } = require('../../src/csvReadWrite');
const fs = require('fs');
// Helper function to create grades.csv with initial data