77from pymongo .errors import PyMongoError
88
99from .error import DatabaseError , OperationalError , ProgrammingError , SqlSyntaxError
10+ from .helper import SQLHelper
11+ from .sql .insert_builder import InsertExecutionPlan
1012from .sql .parser import SQLParser
1113from .sql .query_builder import QueryExecutionPlan
1214
@@ -30,7 +32,7 @@ class ExecutionStrategy(ABC):
3032
3133 @property
3234 @abstractmethod
33- def execution_plan (self ) -> QueryExecutionPlan :
35+ def execution_plan (self ) -> Union [ QueryExecutionPlan , InsertExecutionPlan ] :
3436 """Name of the execution plan"""
3537 pass
3638
@@ -60,7 +62,7 @@ def supports(self, context: ExecutionContext) -> bool:
6062 pass
6163
6264
63- class StandardExecution (ExecutionStrategy ):
65+ class StandardQueryExecution (ExecutionStrategy ):
6466 """Standard execution strategy for simple SELECT queries without subqueries"""
6567
6668 @property
@@ -70,7 +72,8 @@ def execution_plan(self) -> QueryExecutionPlan:
7072
7173 def supports (self , context : ExecutionContext ) -> bool :
7274 """Support simple queries without subqueries"""
73- return "standard" in context .execution_mode .lower ()
75+ normalized = context .query .lstrip ().upper ()
76+ return "standard" in context .execution_mode .lower () and normalized .startswith ("SELECT" )
7477
7578 def _parse_sql (self , sql : str ) -> QueryExecutionPlan :
7679 """Parse SQL statement and return QueryExecutionPlan"""
@@ -91,29 +94,7 @@ def _parse_sql(self, sql: str) -> QueryExecutionPlan:
9194
9295 def _replace_placeholders (self , obj : Any , parameters : Sequence [Any ]) -> Any :
9396 """Recursively replace ? placeholders with parameter values in filter/projection dicts"""
94- param_index = [0 ] # Use list to allow modification in nested function
95-
96- def replace_recursive (value : Any ) -> Any :
97- if isinstance (value , str ):
98- # Replace ? with the next parameter value
99- if value == "?" :
100- if param_index [0 ] < len (parameters ):
101- result = parameters [param_index [0 ]]
102- param_index [0 ] += 1
103- return result
104- else :
105- raise ProgrammingError (
106- f"Not enough parameters provided: expected at least { param_index [0 ] + 1 } "
107- )
108- return value
109- elif isinstance (value , dict ):
110- return {k : replace_recursive (v ) for k , v in value .items ()}
111- elif isinstance (value , list ):
112- return [replace_recursive (item ) for item in value ]
113- else :
114- return value
115-
116- return replace_recursive (obj )
97+ return SQLHelper .replace_placeholders_generic (obj , parameters , "qmark" )
11798
11899 def _execute_execution_plan (
119100 self ,
@@ -202,10 +183,87 @@ def execute(
202183 return self ._execute_execution_plan (self ._execution_plan , connection .database , processed_params )
203184
204185
186+ class InsertExecution (ExecutionStrategy ):
187+ """Execution strategy for INSERT statements."""
188+
189+ @property
190+ def execution_plan (self ) -> InsertExecutionPlan :
191+ return self ._execution_plan
192+
193+ def supports (self , context : ExecutionContext ) -> bool :
194+ return context .query .lstrip ().upper ().startswith ("INSERT" )
195+
196+ def _parse_sql (self , sql : str ) -> InsertExecutionPlan :
197+ try :
198+ parser = SQLParser (sql )
199+ plan = parser .get_execution_plan ()
200+
201+ if not isinstance (plan , InsertExecutionPlan ):
202+ raise SqlSyntaxError ("Expected INSERT execution plan" )
203+
204+ if not plan .validate ():
205+ raise SqlSyntaxError ("Generated insert plan is invalid" )
206+
207+ return plan
208+ except SqlSyntaxError :
209+ raise
210+ except Exception as e :
211+ _logger .error (f"SQL parsing failed: { e } " )
212+ raise SqlSyntaxError (f"Failed to parse SQL: { e } " )
213+
214+ def _replace_placeholders (
215+ self ,
216+ documents : Sequence [Dict [str , Any ]],
217+ parameters : Optional [Union [Sequence [Any ], Dict [str , Any ]]],
218+ style : Optional [str ],
219+ ) -> Sequence [Dict [str , Any ]]:
220+ return SQLHelper .replace_placeholders_generic (documents , parameters , style )
221+
222+ def _execute_execution_plan (
223+ self ,
224+ execution_plan : InsertExecutionPlan ,
225+ db : Any ,
226+ parameters : Optional [Union [Sequence [Any ], Dict [str , Any ]]] = None ,
227+ ) -> Optional [Dict [str , Any ]]:
228+ try :
229+ if not execution_plan .collection :
230+ raise ProgrammingError ("No collection specified in insert" )
231+
232+ docs = execution_plan .insert_documents or []
233+ docs = self ._replace_placeholders (docs , parameters , execution_plan .parameter_style )
234+
235+ command = {"insert" : execution_plan .collection , "documents" : docs }
236+
237+ _logger .debug (f"Executing MongoDB insert command: { command } " )
238+
239+ return db .command (command )
240+ except PyMongoError as e :
241+ _logger .error (f"MongoDB insert failed: { e } " )
242+ raise DatabaseError (f"Insert execution failed: { e } " )
243+ except (ProgrammingError , DatabaseError , OperationalError ):
244+ # Re-raise our own errors without wrapping
245+ raise
246+ except Exception as e :
247+ _logger .error (f"Unexpected error during insert execution: { e } " )
248+ raise OperationalError (f"Insert execution error: { e } " )
249+
250+ def execute (
251+ self ,
252+ context : ExecutionContext ,
253+ connection : Any ,
254+ parameters : Optional [Union [Sequence [Any ], Dict [str , Any ]]] = None ,
255+ ) -> Optional [Dict [str , Any ]]:
256+ _logger .debug (f"Using insert execution for query: { context .query [:100 ]} " )
257+
258+ self ._execution_plan = self ._parse_sql (context .query )
259+
260+ return self ._execute_execution_plan (self ._execution_plan , connection .database , parameters )
261+
262+
205263class ExecutionPlanFactory :
206264 """Factory for creating appropriate execution strategy based on query context"""
207265
208- _strategies = [StandardExecution ()]
266+ _strategies = [InsertExecution (), StandardQueryExecution ()]
209267
210268 @classmethod
211269 def get_strategy (cls , context : ExecutionContext ) -> ExecutionStrategy :
@@ -216,7 +274,7 @@ def get_strategy(cls, context: ExecutionContext) -> ExecutionStrategy:
216274 return strategy
217275
218276 # Fallback to standard execution
219- return StandardExecution ()
277+ return StandardQueryExecution ()
220278
221279 @classmethod
222280 def register_strategy (cls , strategy : ExecutionStrategy ) -> None :
0 commit comments