@@ -356,6 +356,78 @@ def _stringify_dict_keys(self, obj):
356356 else :
357357 return obj
358358
359+ async def explain_query (self , sql : str , params : Optional [Dict [str , Any ]] = None ) -> List [TextContent ]:
360+ """Explain a SQL query against YDB
361+
362+ Args:
363+ sql: SQL query to execute
364+ params: Optional query parameters
365+
366+ Returns:
367+ Execution plan of the query as TextContent object with JSON-formatted execution plan
368+ """
369+ # Check if there's an authentication error
370+ if self .auth_error :
371+ return [TextContent (type = "text" , text = json .dumps ({"error" : self .auth_error }, indent = 2 ))]
372+
373+ try :
374+ pool = await self .get_pool ()
375+ ydb_params = None
376+ if params :
377+ ydb_params = {}
378+ for key , value in params .items ():
379+ param_key = key if key .startswith ("$" ) else f"${ key } "
380+ ydb_params [param_key ] = value
381+
382+ structured_plan = await pool .explain_with_retries (
383+ query = sql ,
384+ parameters = ydb_params ,
385+ result_format = ydb .QueryExplainResultFormat .DICT ,
386+ )
387+
388+ safe_plan = self ._stringify_dict_keys (structured_plan )
389+ formatted_plan = json .dumps (safe_plan , indent = 2 , cls = CustomJSONEncoder )
390+ logger .info (f"Query plan: { formatted_plan } " )
391+ return [TextContent (type = "text" , text = formatted_plan )]
392+ except Exception as e :
393+ error_message = str (e )
394+ safe_error = self ._stringify_dict_keys ({"error" : error_message })
395+ return [TextContent (type = "text" , text = json .dumps (safe_error , indent = 2 ))]
396+
397+ async def explain_query_with_params (self , sql : str , params : str ) -> List [TextContent ]:
398+ """Explain a SQL query against YDB
399+
400+ Args:
401+ sql: SQL query to execute
402+ params: Optional query parameters
403+
404+ Returns:
405+ Execution plan of the query as TextContent object with JSON-formatted execution plan
406+ """
407+ """Run a parameterized SQL query with JSON parameters.
408+
409+ Args:
410+ sql: SQL query to execute
411+ params: Parameters as a JSON string
412+
413+ Returns:
414+ Query results as a list of TextContent objects or a dictionary
415+ """
416+ # Handle authentication errors
417+ if self .auth_error :
418+ logger .error (f"Authentication error: { self .auth_error } " )
419+ safe_error = self ._stringify_dict_keys ({"error" : f"Authentication error: { self .auth_error } " })
420+ return [TextContent (type = "text" , text = json .dumps (safe_error , indent = 2 ))]
421+
422+ try :
423+ ydb_params = self ._parse_str_to_ydb_params (params )
424+ except json .JSONDecodeError as e :
425+ logger .error (f"Error parsing JSON parameters: { str (e )} " )
426+ safe_error = self ._stringify_dict_keys ({"error" : f"Error parsing JSON parameters: { str (e )} " })
427+ return [TextContent (type = "text" , text = json .dumps (safe_error , indent = 2 ))]
428+
429+ return await self .explain_query (sql , ydb_params )
430+
359431 async def query (self , sql : str , params : Optional [Dict [str , Any ]] = None ) -> List [TextContent ]:
360432 """Run a SQL query against YDB.
361433
@@ -444,14 +516,25 @@ async def query_with_params(self, sql: str, params: str) -> List[TextContent]:
444516 logger .error (f"Authentication error: { self .auth_error } " )
445517 safe_error = self ._stringify_dict_keys ({"error" : f"Authentication error: { self .auth_error } " })
446518 return [TextContent (type = "text" , text = json .dumps (safe_error , indent = 2 ))]
447- parsed_params = {}
448519 try :
449- if params and params .strip ():
450- parsed_params = json .loads (params )
520+ ydb_params = self ._parse_str_to_ydb_params (params )
521+
522+ return await self .query (sql , ydb_params )
451523 except json .JSONDecodeError as e :
452524 logger .error (f"Error parsing JSON parameters: { str (e )} " )
453525 safe_error = self ._stringify_dict_keys ({"error" : f"Error parsing JSON parameters: { str (e )} " })
454526 return [TextContent (type = "text" , text = json .dumps (safe_error , indent = 2 ))]
527+ except Exception as e :
528+ error_message = f"Error executing parameterized query: { str (e )} "
529+ logger .error (error_message )
530+ safe_error = self ._stringify_dict_keys ({"error" : error_message })
531+ return [TextContent (type = "text" , text = json .dumps (safe_error , indent = 2 ))]
532+
533+ def _parse_str_to_ydb_params (self , params : str ) -> Dict :
534+ parsed_params = {}
535+ if params and params .strip ():
536+ parsed_params = json .loads (params )
537+
455538 # Convert [value, type] to YDB type if needed
456539 ydb_params = {}
457540 for key , value in parsed_params .items ():
@@ -465,13 +548,9 @@ async def query_with_params(self, sql: str, params: str) -> List[TextContent]:
465548 ydb_params [param_key ] = param_value
466549 else :
467550 ydb_params [param_key ] = value
468- try :
469- return await self .query (sql , ydb_params )
470- except Exception as e :
471- error_message = f"Error executing parameterized query: { str (e )} "
472- logger .error (error_message )
473- safe_error = self ._stringify_dict_keys ({"error" : error_message })
474- return [TextContent (type = "text" , text = json .dumps (safe_error , indent = 2 ))]
551+
552+ return ydb_params
553+
475554
476555 def register_tools (self ):
477556 """Register YDB query tools.
@@ -483,6 +562,30 @@ def register_tools(self):
483562 """
484563 # Define tool specifications
485564 tool_specs = [
565+ {
566+ "name" : "ydb_explain_query" ,
567+ "description" : "Explain a SQL query against YDB" ,
568+ "handler" : self .explain_query , # Use real handler
569+ "parameters" : {
570+ "properties" : {"sql" : {"type" : "string" , "title" : "Sql" }},
571+ "required" : ["sql" ],
572+ "type" : "object" ,
573+ },
574+ },
575+ {
576+ "name" : "ydb_explain_query_with_params" ,
577+ "description" : "Explain a parametrized SQL query with JSON parameters" ,
578+ "handler" : self .explain_query_with_params , # Use real handler
579+ "parameters" : {
580+ "properties" : {
581+ "sql" : {"type" : "string" , "title" : "Sql" },
582+ "params" : {"type" : "string" , "title" : "Params" },
583+ },
584+ "required" : ["sql" , "params" ],
585+ "type" : "object" ,
586+ },
587+
588+ },
486589 {
487590 "name" : "ydb_query" ,
488591 "description" : "Run a SQL query against YDB database" ,
0 commit comments