88
99from .error import DatabaseError , OperationalError , ProgrammingError , SqlSyntaxError
1010from .helper import SQLHelper
11+ from .sql .delete_builder import DeleteExecutionPlan
1112from .sql .insert_builder import InsertExecutionPlan
1213from .sql .parser import SQLParser
1314from .sql .query_builder import QueryExecutionPlan
15+ from .sql .update_builder import UpdateExecutionPlan
1416
1517_logger = logging .getLogger (__name__ )
1618
@@ -263,10 +265,6 @@ def execute(
263265class DeleteExecution (ExecutionStrategy ):
264266 """Strategy for executing DELETE statements."""
265267
266- def __init__ (self ) -> None :
267- """Initialize DELETE execution strategy."""
268- self ._execution_plan : Optional [Any ] = None
269-
270268 @property
271269 def execution_plan (self ) -> Any :
272270 return self ._execution_plan
@@ -275,8 +273,6 @@ def supports(self, context: ExecutionContext) -> bool:
275273 return context .query .lstrip ().upper ().startswith ("DELETE" )
276274
277275 def _parse_sql (self , sql : str ) -> Any :
278- from .sql .delete_builder import DeleteExecutionPlan
279-
280276 try :
281277 parser = SQLParser (sql )
282278 plan = parser .get_execution_plan ()
@@ -340,10 +336,104 @@ def execute(
340336 return self ._execute_execution_plan (self ._execution_plan , connection .database , parameters )
341337
342338
339+ class UpdateExecution (ExecutionStrategy ):
340+ """Strategy for executing UPDATE statements."""
341+
342+ @property
343+ def execution_plan (self ) -> Any :
344+ return self ._execution_plan
345+
346+ def supports (self , context : ExecutionContext ) -> bool :
347+ return context .query .lstrip ().upper ().startswith ("UPDATE" )
348+
349+ def _parse_sql (self , sql : str ) -> Any :
350+ try :
351+ parser = SQLParser (sql )
352+ plan = parser .get_execution_plan ()
353+
354+ if not isinstance (plan , UpdateExecutionPlan ):
355+ raise SqlSyntaxError ("Expected UPDATE execution plan" )
356+
357+ if not plan .validate ():
358+ raise SqlSyntaxError ("Generated update plan is invalid" )
359+
360+ return plan
361+ except SqlSyntaxError :
362+ raise
363+ except Exception as e :
364+ _logger .error (f"SQL parsing failed: { e } " )
365+ raise SqlSyntaxError (f"Failed to parse SQL: { e } " )
366+
367+ def _execute_execution_plan (
368+ self ,
369+ execution_plan : Any ,
370+ db : Any ,
371+ parameters : Optional [Union [Sequence [Any ], Dict [str , Any ]]] = None ,
372+ ) -> Optional [Dict [str , Any ]]:
373+ try :
374+ if not execution_plan .collection :
375+ raise ProgrammingError ("No collection specified in update" )
376+
377+ if not execution_plan .update_fields :
378+ raise ProgrammingError ("No fields to update specified" )
379+
380+ filter_conditions = execution_plan .filter_conditions or {}
381+ update_fields = execution_plan .update_fields or {}
382+
383+ # Replace placeholders if parameters provided
384+ # Note: We need to replace both update_fields and filter_conditions in one pass
385+ # to maintain correct parameter ordering (SET clause first, then WHERE clause)
386+ if parameters :
387+ # Combine structures for replacement in correct order
388+ combined = {"update_fields" : update_fields , "filter_conditions" : filter_conditions }
389+ replaced = SQLHelper .replace_placeholders_generic (combined , parameters , execution_plan .parameter_style )
390+ update_fields = replaced ["update_fields" ]
391+ filter_conditions = replaced ["filter_conditions" ]
392+
393+ # MongoDB update command format
394+ # https://www.mongodb.com/docs/manual/reference/command/update/
395+ command = {
396+ "update" : execution_plan .collection ,
397+ "updates" : [
398+ {
399+ "q" : filter_conditions , # query filter
400+ "u" : {"$set" : update_fields }, # update document using $set operator
401+ "multi" : True , # update all matching documents (like SQL UPDATE)
402+ "upsert" : False , # don't insert if no match
403+ }
404+ ],
405+ }
406+
407+ _logger .debug (f"Executing MongoDB update command: { command } " )
408+
409+ return db .command (command )
410+ except PyMongoError as e :
411+ _logger .error (f"MongoDB update failed: { e } " )
412+ raise DatabaseError (f"Update execution failed: { e } " )
413+ except (ProgrammingError , DatabaseError , OperationalError ):
414+ # Re-raise our own errors without wrapping
415+ raise
416+ except Exception as e :
417+ _logger .error (f"Unexpected error during update execution: { e } " )
418+ raise OperationalError (f"Update execution error: { e } " )
419+
420+ def execute (
421+ self ,
422+ context : ExecutionContext ,
423+ connection : Any ,
424+ parameters : Optional [Union [Sequence [Any ], Dict [str , Any ]]] = None ,
425+ ) -> Optional [Dict [str , Any ]]:
426+ _logger .debug (f"Using update execution for query: { context .query [:100 ]} " )
427+
428+ self ._execution_plan = self ._parse_sql (context .query )
429+
430+ return self ._execute_execution_plan (self ._execution_plan , connection .database , parameters )
431+
432+
343433class ExecutionPlanFactory :
344434 """Factory for creating appropriate execution strategy based on query context"""
345435
346- _strategies = [DeleteExecution (), InsertExecution (), StandardQueryExecution ()]
436+ _strategies = [StandardQueryExecution (), InsertExecution (), UpdateExecution (), DeleteExecution ()]
347437
348438 @classmethod
349439 def get_strategy (cls , context : ExecutionContext ) -> ExecutionStrategy :
0 commit comments