From 6143b4bc95bbc3f40360527bca76f69360d254b3 Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Mon, 15 Apr 2024 08:18:28 +0000
Subject: [PATCH 1/5] 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 6c0cd9265687854e5ffb06b0b352c5e608bb91f9 Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Mon, 15 Apr 2024 08:18:28 +0000
Subject: [PATCH 2/5] 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 4630aac71abe35e000b5eb6234ce7549b8a5b48f Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Mon, 15 Apr 2024 08:18:29 +0000
Subject: [PATCH 3/5] Setting up GitHub Classroom Feedback
From 31b83731d7a1016879399cb072ed432d1c4c36aa Mon Sep 17 00:00:00 2001
From: "github-classroom[bot]"
<66690702+github-classroom[bot]@users.noreply.github.com>
Date: Mon, 15 Apr 2024 08:18:32 +0000
Subject: [PATCH 4/5] add online IDE url
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index eadfc715a..c6e37473b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+[](https://classroom.github.com/online_ide?assignment_repo_id=14738511&assignment_repo_type=AssignmentRepo)
StylusDB SQL
A SQL database engine written in JavaScript
From d002cf911e7c17c7970b2518f3226d7b041f83b2 Mon Sep 17 00:00:00 2001
From: shubhamaltpi
Date: Mon, 15 Apr 2024 14:47:54 +0530
Subject: [PATCH 5/5] 12 comp
---
src/csvReader.js | 20 ++++
src/index.js | 276 +++++++++++++++++++++++++++++++++++++++++++++
src/queryParser.js | 116 +++++++++++++++++++
3 files changed, 412 insertions(+)
create mode 100644 src/index.js
create mode 100644 src/queryParser.js
diff --git a/src/csvReader.js b/src/csvReader.js
index e69de29bb..b1d92429a 100644
--- a/src/csvReader.js
+++ b/src/csvReader.js
@@ -0,0 +1,20 @@
+const fs = require('fs');
+const csv = require('csv-parser');
+
+function readCSV(filePath) {
+ const results = [];
+
+ return new Promise((resolve, reject) => {
+ fs.createReadStream(filePath)
+ .pipe(csv())
+ .on('data', (data) => results.push(data))
+ .on('end', () => {
+ resolve(results);
+ })
+ .on('error', (error) => {
+ reject(error);
+ });
+ });
+}
+
+module.exports = readCSV;
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 000000000..bbdf47bac
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,276 @@
+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 } = 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;
+ }
+ }
+ });
+ return [result];
+ // Add more cases here if needed for other aggregates
+} else if (groupByFields) {
+ groupResults = applyGroupBy(filteredData, groupByFields, fields);
+
+ // order
+ 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 {
+
+ 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=executeSELECTQuery;
\ No newline at end of file
diff --git a/src/queryParser.js b/src/queryParser.js
new file mode 100644
index 000000000..f48cb412c
--- /dev/null
+++ b/src/queryParser.js
@@ -0,0 +1,116 @@
+// /*****STEP : 13**************** */
+function parseQuery(query) {
+ // Trim the query to remove any leading/trailing whitespaces
+ try{
+ 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, '');
+ }
+
+ const whereSplit = query.split(/\sWHERE\s/i);
+ const queryWithoutWhere = whereSplit[0]; // Everything before WHERE clause
+ const whereClause = whereSplit.length > 1 ? whereSplit[1].trim() : null;
+
+ 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(err){
+ throw new Error(`Query parsing error: ${err.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 };