22import json
33import uuid
44import copy
5+ from typing import List , Dict , Any , Optional , Union , Tuple
56from .table import Table
67from .parser import SQLParser
78from .exceptions import DBError , TableNotFoundError
89
910class 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
83127class 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