Skip to content

Commit

Permalink
complete till step 13
Browse files Browse the repository at this point in the history
  • Loading branch information
sourav-sm committed Apr 12, 2024
1 parent 35758a6 commit 27c49bb
Show file tree
Hide file tree
Showing 17 changed files with 2,100 additions and 986 deletions.
2 changes: 1 addition & 1 deletion enrollment.csv
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ student_id,course
1,Physics
2,Chemistry
3,Mathematics
5,Biology
5,Biology
3 changes: 2 additions & 1 deletion src/csvReader.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ function readCSV(filePath) {
});
}

module.exports = readCSV;
module.exports = readCSV;

191 changes: 107 additions & 84 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,82 +68,6 @@ function createResultRow(mainRow, joinRow, fields, table, includeAllMainFields)
});
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
Expand Down Expand Up @@ -180,28 +104,23 @@ function parseValue(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;
Expand All @@ -217,7 +136,6 @@ function applyGroupBy(data, groupByFields, aggregateFunctions) {
}
});
});

// Convert grouped results into an array format
return Object.values(groupResults).map(group => {
// Construct the final grouped object based on required fields
Expand All @@ -244,10 +162,115 @@ function applyGroupBy(data, groupByFields, aggregateFunctions) {
}
}
});

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);

module.exports = executeSELECTQuery;
// 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;
79 changes: 55 additions & 24 deletions src/queryParser.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,65 @@
// /*****STEP : 13**************** */
function parseQuery(query) {
// Trim the query to remove any leading/trailing whitespaces
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
// Trim the query to remove any leading/trailing whitespaces
try{
query = query.trim();

// 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;
//Updated regex to capture LIMIT clause and remove it for further processing
const limitRegex = /\sLIMIT\s(\d+)/i;
const limitMatch = query.match(limitRegex);

// 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
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);

// WHERE clause is the second part after splitting, if it exists
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;

// 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
// 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');
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
// Parse 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;
// Check for aggregate functions without GROUP BY
const hasAggregateWithoutGroupBy = checkAggregateWithoutGroupBy(query, groupByFields);

return {
fields: fields.split(',').map(field => field.trim()),
Expand All @@ -48,10 +69,19 @@ function parseQuery(query) {
joinTable,
joinCondition,
groupByFields,
hasAggregateWithoutGroupBy
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 = /(.*?)(=|!=|>|<|>=|<=)(.*)/;
Expand Down Expand Up @@ -84,3 +114,4 @@ function parseJoinClause(query) {
};
}
module.exports = { parseQuery, parseJoinClause };

2 changes: 1 addition & 1 deletion student.csv
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ id,name,age
1,John,30
2,Jane,25
3,Bob,22
4,Alice,24
4,Alice,24
Loading

0 comments on commit 27c49bb

Please sign in to comment.