Path: blob/main/singlestoredb/fusion/registry.py
469 views
#!/usr/bin/env python31import re2import sys3from typing import Any4from typing import Dict5from typing import List6from typing import Optional7from typing import Tuple8from typing import Type9from typing import Union1011from . import result12from .. import connection13from ..config import get_option14from .handler import SQLHandler1516_handlers: Dict[str, Type[SQLHandler]] = {}17_handlers_re: Optional[Any] = None181920def register_handler(handler: Type[SQLHandler], overwrite: bool = False) -> None:21"""22Register a new SQL handler.2324Parameters25----------26handler : SQLHandler subclass27The handler class to register28overwrite : bool, optional29Should an existing handler be overwritten if it uses the same command key?3031"""32global _handlers33global _handlers_re3435# Build key for handler36key = ' '.join(x.upper() for x in handler.command_key)3738# Check for existing handler with same key39if not overwrite and key in _handlers:40raise ValueError(f'command already exists, use overwrite=True to override: {key}')4142# Add handler to registry43_handlers[key] = handler4445# Build regex to detect fusion query46keys = sorted(_handlers.keys(), key=lambda x: (-len(x), x))47keys_str = '|'.join(x.replace(' ', '\\s+') for x in keys)48_handlers_re = re.compile(f'^\\s*({keys_str})(?:\\s+|;|$)', flags=re.I)495051def get_handler(sql: Union[str, bytes]) -> Optional[Type[SQLHandler]]:52"""53Return a fusion handler for the given query.5455Parameters56----------57sql : str or bytes58The SQL query5960Returns61-------62SQLHandler - if a matching one exists63None - if no matching handler could be found6465"""66if not get_option('fusion.enabled'):67return None6869if isinstance(sql, (bytes, bytearray)):70sql = sql.decode('utf-8')7172if _handlers_re is None:73return None7475m = _handlers_re.match(sql)76if m:77return _handlers[re.sub(r'\s+', r' ', m.group(1).strip().upper())]7879return None808182def execute(83connection: connection.Connection,84sql: str,85handler: Optional[Type[SQLHandler]] = None,86) -> result.FusionSQLResult:87"""88Execute a SQL query in the management interface.8990Parameters91----------92connection : Connection93The SingleStoreDB connection object94sql : str95The SQL query96handler : SQLHandler, optional97The handler to use for the commands. If not supplied, one will be98looked up in the registry.99100Returns101-------102FusionSQLResult103104"""105if not get_option('fusion.enabled'):106raise RuntimeError('management API queries have not been enabled')107108if handler is None:109handler = get_handler(sql)110if handler is None:111raise RuntimeError(f'could not find handler for query: {sql}')112113return handler(connection).execute(sql)114115116class ShowFusionCommandsHandler(SQLHandler):117"""118SHOW FUSION COMMANDS [ like ];119120# LIKE pattern121like = LIKE '<pattern>'122123Description124-----------125Displays a list of all the Fusion commands.126127Arguments128---------129* `<pattern>``: A pattern similar to SQL LIKE clause. Uses ``%`` as130the wildcard character.131132Remarks133-------134* Use the ``LIKE`` clause to specify a pattern and return only the135commands that match the specified pattern.136137Example138-------139The following command returns all the Fusion commands that start140with 'SHOW'::141142SHOW FUSION COMMANDS LIKE 'SHOW%'143144See Also145--------146* ``SHOW FUSION HELP``147148"""149150def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:151res = result.FusionSQLResult()152res.add_field('Command', result.STRING)153154data: List[Tuple[Any, ...]] = []155for _, v in sorted(_handlers.items()):156data.append((v.syntax.lstrip(),))157158res.set_rows(data)159160if params['like']:161res = res.like(Command=params['like'])162163return res164165166ShowFusionCommandsHandler.register()167168169class ShowFusionGrammarHandler(SQLHandler):170"""171SHOW FUSION GRAMMAR for_query;172173# Query to show grammar for174for_query = FOR '<query>'175176Description177-----------178Show the full grammar of a Fusion SQL command for a given query.179180Arguments181---------182* ``<command>``: A Fusion command.183184Example185-------186The following command displays the grammar for the187``CREATE WORKSPACE`` Fusion command::188189SHOW FUSION GRAMMAR FOR 'CREATE WORKSPACE';190191"""192193def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:194res = result.FusionSQLResult()195res.add_field('Grammar', result.STRING)196handler = get_handler(params['for_query'])197data: List[Tuple[Any, ...]] = []198if handler is not None:199data.append((handler._grammar,))200res.set_rows(data)201return res202203204ShowFusionGrammarHandler.register()205206207class ShowFusionHelpHandler(SQLHandler):208"""209SHOW FUSION HELP for_command;210211# Command to show help for212for_command = FOR '<command>'213214Description215-----------216Displays the documentation for a Fusion command.217218Arguments219---------220* ``<command>``: A Fusion command.221222Example223-------224The following command displays the documentation for225the ``CREATE WORKSPACE`` Fusion command.226227SHOW FUSION HELP FOR 'CREATE WORKSPACE';228229"""230231def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:232handler = get_handler(params['for_command'])233if handler is not None:234try:235from IPython.display import display236from IPython.display import Markdown237display(Markdown(handler.help))238except Exception:239print(handler.help)240else:241print(242f'No handler found for command \'{params["for_command"]}\'',243file=sys.stderr,244)245return None246247248ShowFusionHelpHandler.register()249250251