Skip to content

Commit f0d8e17

Browse files
committed
Polished codebase with Type Hints and Google-style Docstrings
1 parent 1a0f339 commit f0d8e17

3 files changed

Lines changed: 392 additions & 110 deletions

File tree

minidb/database.py

Lines changed: 155 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,35 @@
22
import json
33
import uuid
44
import copy
5+
from typing import List, Dict, Any, Optional, Union, Tuple
56
from .table import Table
67
from .parser import SQLParser
78
from .exceptions import DBError, TableNotFoundError
89

910
class TransactionManager:
10-
"""Manages database transactions with BEGIN, COMMIT, and ROLLBACK support."""
11+
"""Manages database transactions with BEGIN, COMMIT, and ROLLBACK support.
1112
12-
def __init__(self):
13-
self.session_id = None
14-
self.in_transaction = False
15-
self.staging_area = {} # {table_name: {'data': [...], 'modified': True}}
13+
Attributes:
14+
session_id (Optional[str]): Unique ID for the current transaction session.
15+
in_transaction (bool): True if a transaction is currently active.
16+
staging_area (Dict[str, Dict[str, Any]]): Buffer for uncommitted changes.
17+
"""
18+
19+
def __init__(self) -> None:
20+
"""Initializes the transaction manager in an idle state."""
21+
self.session_id: Optional[str] = None
22+
self.in_transaction: bool = False
23+
self.staging_area: Dict[str, Dict[str, Any]] = {} # {table_name: {'data': [...], 'modified': True}}
24+
25+
def begin(self) -> str:
26+
"""Starts a new transaction.
1627
17-
def begin(self):
18-
"""Start a new transaction."""
28+
Returns:
29+
str: Confirmation message with session ID.
30+
31+
Raises:
32+
DBError: If a transaction is already in progress.
33+
"""
1934
if self.in_transaction:
2035
raise DBError("Transaction already in progress. COMMIT or ROLLBACK first.")
2136

@@ -24,8 +39,18 @@ def begin(self):
2439
self.staging_area = {}
2540
return f"Transaction started (Session: {self.session_id[:8]})"
2641

27-
def commit(self, tables):
28-
"""Commit all staged changes to disk."""
42+
def commit(self, tables: Dict[str, Table]) -> str:
43+
"""Commits all staged changes to disk.
44+
45+
Args:
46+
tables: Dictionary of table objects to update.
47+
48+
Returns:
49+
str: Summary of the commit operation.
50+
51+
Raises:
52+
DBError: If no transaction is active or commit fails.
53+
"""
2954
if not self.in_transaction:
3055
raise DBError("No active transaction to commit.")
3156

@@ -50,23 +75,38 @@ def commit(self, tables):
5075
# If commit fails, keep transaction open for retry or rollback
5176
raise DBError(f"Commit failed: {e}. Transaction still active.")
5277

53-
def rollback(self):
54-
"""Discard all staged changes."""
78+
def rollback(self) -> str:
79+
"""Discards all staged changes.
80+
81+
Returns:
82+
str: Summary of the rollback operation.
83+
84+
Raises:
85+
DBError: If no active transaction exists.
86+
"""
5587
if not self.in_transaction:
5688
raise DBError("No active transaction to rollback.")
5789

5890
discarded_tables = list(self.staging_area.keys())
5991
self._clear()
6092
return f"Transaction rolled back. Discarded changes to: {discarded_tables if discarded_tables else 'none'}"
6193

62-
def _clear(self):
63-
"""Clear transaction state."""
94+
def _clear(self) -> None:
95+
"""Internal helper to reset the transaction state."""
6496
self.session_id = None
6597
self.in_transaction = False
6698
self.staging_area = {}
6799

68-
def stage_table(self, table_name, table_data):
69-
"""Stage a table's data for modification."""
100+
def stage_table(self, table_name: str, table_data: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
101+
"""Stages a table's data for modification within a transaction.
102+
103+
Args:
104+
table_name: Name of the table to stage.
105+
table_data: The current in-memory rows of the table.
106+
107+
Returns:
108+
List[Dict[str, Any]]: A deep copy of the row data for staging.
109+
"""
70110
if table_name not in self.staging_area:
71111
# Create a deep copy of the table data
72112
self.staging_area[table_name] = {
@@ -75,26 +115,46 @@ def stage_table(self, table_name, table_data):
75115
}
76116
return self.staging_area[table_name]['data']
77117

78-
def mark_modified(self, table_name):
79-
"""Mark a table as modified in the current transaction."""
118+
def mark_modified(self, table_name: str) -> None:
119+
"""Marks a staged table as modified so it can be committed.
120+
121+
Args:
122+
table_name: Name of the table to mark.
123+
"""
80124
if table_name in self.staging_area:
81125
self.staging_area[table_name]['modified'] = True
82126

83127
class MiniDB:
84-
def __init__(self, data_dir="data", metadata_file="metadata.json"):
128+
"""The central engine for managing multiple tables and executing queries.
129+
130+
Attributes:
131+
data_dir (str): Directory where all data and metadata files are stored.
132+
metadata_path (str): Full path to the metadata.json file.
133+
tables (Dict[str, Table]): Map of table names to Table objects.
134+
parser (SQLParser): The SQL command parser.
135+
transaction (TransactionManager): Manager for atomic operations.
136+
"""
137+
138+
def __init__(self, data_dir: str = "data", metadata_file: str = "metadata.json") -> None:
139+
"""Initializes the database engine and loads metadata.
140+
141+
Args:
142+
data_dir: Base directory for storage.
143+
metadata_file: Filename for schema persistence.
144+
"""
85145
self.data_dir = data_dir
86146
self.metadata_path = os.path.join(self.data_dir, metadata_file)
87-
self.tables = {}
147+
self.tables: Dict[str, Table] = {}
88148
self.parser = SQLParser()
89-
self.transaction = TransactionManager() # Transaction manager
149+
self.transaction = TransactionManager()
90150

91151
if not os.path.exists(self.data_dir):
92152
os.makedirs(self.data_dir)
93153

94154
self._load_metadata()
95155

96-
def _load_metadata(self):
97-
"""Loads table definitions from metadata.json."""
156+
def _load_metadata(self) -> None:
157+
"""Internal method to load table schemas from metadata.json."""
98158
if not os.path.exists(self.metadata_path):
99159
return
100160

@@ -114,8 +174,12 @@ def _load_metadata(self):
114174
except Exception as e:
115175
print(f"Warning: Failed to load metadata: {e}")
116176

117-
def _save_metadata(self):
118-
"""Saves current table definitions to metadata.json."""
177+
def _save_metadata(self) -> None:
178+
"""Internal method to persist current table schemas to metadata.json.
179+
180+
Raises:
181+
DBError: If saving fails.
182+
"""
119183
metadata = {}
120184
for name, table in self.tables.items():
121185
metadata[name] = {
@@ -132,8 +196,15 @@ def _save_metadata(self):
132196
except IOError as e:
133197
raise DBError(f"Failed to save metadata: {e}")
134198

135-
def execute_query(self, query_string):
136-
"""Parses and executes a SQL query."""
199+
def execute_query(self, query_string: str) -> Any:
200+
"""Parses and executes a SQL query on the system.
201+
202+
Args:
203+
query_string: The raw SQL string to execute.
204+
205+
Returns:
206+
Any: The result of the query (list of rows, success message, or error string).
207+
"""
137208
try:
138209
parsed = self.parser.parse(query_string)
139210
cmd_type = parsed['type']
@@ -395,12 +466,27 @@ def execute_query(self, query_string):
395466
except Exception as e:
396467
return f"Unexpected Error: {e}"
397468

398-
def get_tables(self):
399-
"""Returns a list of all table names."""
469+
def get_tables(self) -> List[str]:
470+
"""Returns a list of all table names currently registered in the database.
471+
472+
Returns:
473+
List[str]: List of table names.
474+
"""
400475
return list(self.tables.keys())
401476

402-
def _nested_loop_join(self, left_rows, right_rows, left_on, right_on):
403-
"""Simple Nested Loop Join implementation."""
477+
def _nested_loop_join(self, left_rows: List[Dict[str, Any]], right_rows: List[Dict[str, Any]],
478+
left_on: Tuple[str, str], right_on: Tuple[str, str]) -> List[Dict[str, Any]]:
479+
"""Performs a simple Nested Loop Join. Complexity: O(N*M).
480+
481+
Args:
482+
left_rows: Rows from the left table.
483+
right_rows: Rows from the right table.
484+
left_on: (table_name, column_name) for left join condition.
485+
right_on: (table_name, column_name) for right join condition.
486+
487+
Returns:
488+
List[Dict[str, Any]]: Joined results.
489+
"""
404490
result = []
405491
l_table, l_col = left_on
406492
r_table, r_col = right_on
@@ -411,8 +497,19 @@ def _nested_loop_join(self, left_rows, right_rows, left_on, right_on):
411497
result.append(self._merge_rows(l_row, r_row, r_table))
412498
return result
413499

414-
def _hash_join(self, left_rows, right_rows, left_on, right_on):
415-
"""Optimized Hash Join implementation O(N+M)."""
500+
def _hash_join(self, left_rows: List[Dict[str, Any]], right_rows: List[Dict[str, Any]],
501+
left_on: Tuple[str, str], right_on: Tuple[str, str]) -> List[Dict[str, Any]]:
502+
"""Performs an optimized Hash Join. Complexity: O(N+M).
503+
504+
Args:
505+
left_rows: Rows from the left table.
506+
right_rows: Rows from the right table.
507+
left_on: (table_name, column_name) for left condition.
508+
right_on: (table_name, column_name) for right condition.
509+
510+
Returns:
511+
List[Dict[str, Any]]: Joined results.
512+
"""
416513
result = []
417514
l_table, l_col = left_on
418515
r_table, r_col = right_on
@@ -429,7 +526,7 @@ def _hash_join(self, left_rows, right_rows, left_on, right_on):
429526
build_table, probe_table = r_table, l_table
430527
swapped = True
431528

432-
hash_map = {}
529+
hash_map: Dict[Any, List[Dict[str, Any]]] = {}
433530
for row in build_rows:
434531
key = row.get(build_col)
435532
if key not in hash_map:
@@ -448,8 +545,17 @@ def _hash_join(self, left_rows, right_rows, left_on, right_on):
448545
result.append(self._merge_rows(b_row, p_row, probe_table))
449546
return result
450547

451-
def _merge_rows(self, left_row, right_row, r_table_name):
452-
"""Helper to merge two rows and handle column name collisions."""
548+
def _merge_rows(self, left_row: Dict[str, Any], right_row: Dict[str, Any], r_table_name: str) -> Dict[str, Any]:
549+
"""Helper to merge two rows and handle column name collisions.
550+
551+
Args:
552+
left_row: Row from the left table.
553+
right_row: Row from the right table.
554+
r_table_name: Name of the right table for prefixing collisions.
555+
556+
Returns:
557+
Dict[str, Any]: Merged row dictionary.
558+
"""
453559
merged = left_row.copy()
454560
for k, v in right_row.items():
455561
if k in merged:
@@ -458,7 +564,20 @@ def _merge_rows(self, left_row, right_row, r_table_name):
458564
merged[k] = v
459565
return merged
460566

461-
def _create_table(self, name, columns, column_types=None, unique_columns=None, foreign_keys=None):
567+
def _create_table(self, name: str, columns: List[str], column_types: Optional[Dict[str, str]] = None,
568+
unique_columns: Optional[List[str]] = None, foreign_keys: Optional[Dict[str, str]] = None) -> str:
569+
"""Internal method to create and initialize a new table.
570+
571+
Args:
572+
name: Table name.
573+
columns: List of column names.
574+
column_types: Map of col -> type.
575+
unique_columns: List of columns with uniqueness constraints.
576+
foreign_keys: Reference map.
577+
578+
Returns:
579+
str: Status message.
580+
"""
462581
if name in self.tables:
463582
return f"Error: Table '{name}' already exists."
464583

0 commit comments

Comments
 (0)