Skip to content

Commit fbf8917

Browse files
committed
Implement support for Nested Subqueries and Column Projection
1 parent c0a82d6 commit fbf8917

3 files changed

Lines changed: 51 additions & 15 deletions

File tree

minidb/database.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -210,26 +210,44 @@ def execute_query(self, query_string):
210210

211211
if cmd_type == 'SELECT':
212212
limit = parsed.get('limit')
213-
# SELECT always reads from current state
213+
condition = parsed.get('condition')
214+
columns = parsed.get('columns', '*')
215+
216+
# Handle nested subquery in WHERE clause
217+
if condition and isinstance(condition['value'], str) and condition['value'].startswith('(') and 'SELECT' in condition['value'].upper():
218+
sub_sql = condition['value'][1:-1].strip()
219+
sub_res = self.execute_query(sub_sql)
220+
221+
if isinstance(sub_res, list):
222+
# Flatten subquery result to a simple list of values
223+
if sub_res and isinstance(sub_res[0], dict):
224+
flattened = []
225+
for row in sub_res:
226+
flattened.extend(row.values())
227+
condition['value'] = flattened
228+
else:
229+
condition['value'] = sub_res
230+
231+
# Execution Logic
214232
if self.transaction.in_transaction and table_name in self.transaction.staging_area:
215233
# Read from staging area if modified in transaction
216234
staged_data = self.transaction.staging_area[table_name]['data']
217-
condition = parsed.get('condition')
218235
res = staged_data
219236
if condition:
220-
# Apply WHERE filter to staged data
221237
res = [row for row in staged_data
222238
if table._matches_condition(row, condition['column'],
223239
condition['operator'], condition['value'])]
224240
if limit:
225-
return res[:limit]
226-
return res
241+
res = res[:limit]
227242
else:
228243
# Read from disk
229-
condition = parsed.get('condition')
230244
if condition:
231-
return table.select_where(condition['column'], condition['operator'], condition['value'], limit=limit)
232-
return table.select_all(limit=limit)
245+
res = table.select_where(condition['column'], condition['operator'], condition['value'], limit=limit)
246+
else:
247+
res = table.select_all(limit=limit)
248+
249+
# Apply column projection
250+
return table.project_columns(res, columns)
233251

234252
if cmd_type == 'DELETE':
235253
condition = parsed['condition']

minidb/parser.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ def __init__(self):
88
'CREATE': re.compile(r"CREATE\s+TABLE\s+(\w+)\s*\((.*)\)", re.IGNORECASE),
99
'INSERT': re.compile(r"INSERT\s+INTO\s+(\w+)\s+VALUES\s*\((.*)\)", re.IGNORECASE),
1010
'SELECT_JOIN': re.compile(r"SELECT\s+\*\s+FROM\s+(\w+)\s+JOIN\s+(\w+)\s+ON\s+(\w+)\.(\w+)\s*=\s*(\w+)\.(\w+)", re.IGNORECASE),
11-
'SELECT': re.compile(r"SELECT\s+\*\s+FROM\s+(\w+)(?:\s+WHERE\s+(\w+)\s*(>=|<=|!=|>|<|=)\s*(.*?))?(?:\s+LIMIT\s+(\d+))?$", re.IGNORECASE),
11+
'SELECT': re.compile(r"SELECT\s+(\*|[\w,\s]+)\s+FROM\s+(\w+)(?:\s+WHERE\s+(\w+)\s*(>=|<=|!=|>|<|=|\s+IN\s+)\s*(.*?))?(?:\s+LIMIT\s+(\d+))?$", re.IGNORECASE),
1212
'DELETE': re.compile(r"DELETE\s+FROM\s+(\w+)\s+WHERE\s+(\w+)\s*(>=|<=|!=|>|<|=)\s*(.*)", re.IGNORECASE),
1313
'UPDATE': re.compile(r"UPDATE\s+(\w+)\s+SET\s+(\w+)\s*=\s*(.*)\s+WHERE\s+(\w+)\s*(>=|<=|!=|>|<|=)\s*(.*)", re.IGNORECASE),
1414
'ALTER_TABLE': re.compile(r"ALTER\s+TABLE\s+(\w+)\s+ADD\s+(\w+)\s+(\w+)", re.IGNORECASE),
@@ -90,19 +90,21 @@ def _process_match(self, cmd_type, match):
9090
}
9191

9292
elif cmd_type == 'SELECT':
93-
table_name = match.group(1)
93+
columns = match.group(1).strip()
94+
table_name = match.group(2)
9495
condition = None
95-
if match.group(2) and match.group(3) and match.group(4):
96+
if match.group(3) and match.group(4) and match.group(5):
9697
condition = {
97-
'column': match.group(2),
98-
'operator': match.group(3),
99-
'value': self._infer_type(match.group(4).strip())
98+
'column': match.group(3),
99+
'operator': match.group(4).strip().upper(),
100+
'value': self._infer_type(match.group(5).strip())
100101
}
101102
return {
102103
'type': 'SELECT',
104+
'columns': columns,
103105
'table': table_name,
104106
'condition': condition,
105-
'limit': int(match.group(5)) if len(match.groups()) >= 5 and match.group(5) else None
107+
'limit': int(match.group(6)) if len(match.groups()) >= 6 and match.group(6) else None
106108
}
107109

108110
elif cmd_type == 'SELECT_JOIN':

minidb/table.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,9 +226,25 @@ def _evaluate_condition(self, row_value, operator, target_value):
226226
if operator == '<': return row_value < target_value
227227
if operator == '>=': return row_value >= target_value
228228
if operator == '<=': return row_value <= target_value
229+
if operator == 'IN':
230+
if isinstance(target_value, list):
231+
return row_value in target_value
232+
return row_value == target_value
229233

230234
return False
231235

236+
def project_columns(self, rows, columns_str):
237+
"""Helper to return only specific columns from a list of rows."""
238+
if columns_str == '*':
239+
return rows
240+
241+
cols = [c.strip() for c in columns_str.split(',')]
242+
result = []
243+
for row in rows:
244+
new_row = {k: row[k] for k in cols if k in row}
245+
result.append(new_row)
246+
return result
247+
232248
def add_column(self, column_name, column_type=None):
233249
"""Adds a new column to the table schema and updates all existing rows."""
234250
# Check if column already exists

0 commit comments

Comments
 (0)