Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
singlestore-labs
GitHub Repository: singlestore-labs/singlestoredb-python
Path: blob/main/singlestoredb/fusion/registry.py
469 views
1
#!/usr/bin/env python3
2
import re
3
import sys
4
from typing import Any
5
from typing import Dict
6
from typing import List
7
from typing import Optional
8
from typing import Tuple
9
from typing import Type
10
from typing import Union
11
12
from . import result
13
from .. import connection
14
from ..config import get_option
15
from .handler import SQLHandler
16
17
_handlers: Dict[str, Type[SQLHandler]] = {}
18
_handlers_re: Optional[Any] = None
19
20
21
def register_handler(handler: Type[SQLHandler], overwrite: bool = False) -> None:
22
"""
23
Register a new SQL handler.
24
25
Parameters
26
----------
27
handler : SQLHandler subclass
28
The handler class to register
29
overwrite : bool, optional
30
Should an existing handler be overwritten if it uses the same command key?
31
32
"""
33
global _handlers
34
global _handlers_re
35
36
# Build key for handler
37
key = ' '.join(x.upper() for x in handler.command_key)
38
39
# Check for existing handler with same key
40
if not overwrite and key in _handlers:
41
raise ValueError(f'command already exists, use overwrite=True to override: {key}')
42
43
# Add handler to registry
44
_handlers[key] = handler
45
46
# Build regex to detect fusion query
47
keys = sorted(_handlers.keys(), key=lambda x: (-len(x), x))
48
keys_str = '|'.join(x.replace(' ', '\\s+') for x in keys)
49
_handlers_re = re.compile(f'^\\s*({keys_str})(?:\\s+|;|$)', flags=re.I)
50
51
52
def get_handler(sql: Union[str, bytes]) -> Optional[Type[SQLHandler]]:
53
"""
54
Return a fusion handler for the given query.
55
56
Parameters
57
----------
58
sql : str or bytes
59
The SQL query
60
61
Returns
62
-------
63
SQLHandler - if a matching one exists
64
None - if no matching handler could be found
65
66
"""
67
if not get_option('fusion.enabled'):
68
return None
69
70
if isinstance(sql, (bytes, bytearray)):
71
sql = sql.decode('utf-8')
72
73
if _handlers_re is None:
74
return None
75
76
m = _handlers_re.match(sql)
77
if m:
78
return _handlers[re.sub(r'\s+', r' ', m.group(1).strip().upper())]
79
80
return None
81
82
83
def execute(
84
connection: connection.Connection,
85
sql: str,
86
handler: Optional[Type[SQLHandler]] = None,
87
) -> result.FusionSQLResult:
88
"""
89
Execute a SQL query in the management interface.
90
91
Parameters
92
----------
93
connection : Connection
94
The SingleStoreDB connection object
95
sql : str
96
The SQL query
97
handler : SQLHandler, optional
98
The handler to use for the commands. If not supplied, one will be
99
looked up in the registry.
100
101
Returns
102
-------
103
FusionSQLResult
104
105
"""
106
if not get_option('fusion.enabled'):
107
raise RuntimeError('management API queries have not been enabled')
108
109
if handler is None:
110
handler = get_handler(sql)
111
if handler is None:
112
raise RuntimeError(f'could not find handler for query: {sql}')
113
114
return handler(connection).execute(sql)
115
116
117
class ShowFusionCommandsHandler(SQLHandler):
118
"""
119
SHOW FUSION COMMANDS [ like ];
120
121
# LIKE pattern
122
like = LIKE '<pattern>'
123
124
Description
125
-----------
126
Displays a list of all the Fusion commands.
127
128
Arguments
129
---------
130
* `<pattern>``: A pattern similar to SQL LIKE clause. Uses ``%`` as
131
the wildcard character.
132
133
Remarks
134
-------
135
* Use the ``LIKE`` clause to specify a pattern and return only the
136
commands that match the specified pattern.
137
138
Example
139
-------
140
The following command returns all the Fusion commands that start
141
with 'SHOW'::
142
143
SHOW FUSION COMMANDS LIKE 'SHOW%'
144
145
See Also
146
--------
147
* ``SHOW FUSION HELP``
148
149
"""
150
151
def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:
152
res = result.FusionSQLResult()
153
res.add_field('Command', result.STRING)
154
155
data: List[Tuple[Any, ...]] = []
156
for _, v in sorted(_handlers.items()):
157
data.append((v.syntax.lstrip(),))
158
159
res.set_rows(data)
160
161
if params['like']:
162
res = res.like(Command=params['like'])
163
164
return res
165
166
167
ShowFusionCommandsHandler.register()
168
169
170
class ShowFusionGrammarHandler(SQLHandler):
171
"""
172
SHOW FUSION GRAMMAR for_query;
173
174
# Query to show grammar for
175
for_query = FOR '<query>'
176
177
Description
178
-----------
179
Show the full grammar of a Fusion SQL command for a given query.
180
181
Arguments
182
---------
183
* ``<command>``: A Fusion command.
184
185
Example
186
-------
187
The following command displays the grammar for the
188
``CREATE WORKSPACE`` Fusion command::
189
190
SHOW FUSION GRAMMAR FOR 'CREATE WORKSPACE';
191
192
"""
193
194
def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:
195
res = result.FusionSQLResult()
196
res.add_field('Grammar', result.STRING)
197
handler = get_handler(params['for_query'])
198
data: List[Tuple[Any, ...]] = []
199
if handler is not None:
200
data.append((handler._grammar,))
201
res.set_rows(data)
202
return res
203
204
205
ShowFusionGrammarHandler.register()
206
207
208
class ShowFusionHelpHandler(SQLHandler):
209
"""
210
SHOW FUSION HELP for_command;
211
212
# Command to show help for
213
for_command = FOR '<command>'
214
215
Description
216
-----------
217
Displays the documentation for a Fusion command.
218
219
Arguments
220
---------
221
* ``<command>``: A Fusion command.
222
223
Example
224
-------
225
The following command displays the documentation for
226
the ``CREATE WORKSPACE`` Fusion command.
227
228
SHOW FUSION HELP FOR 'CREATE WORKSPACE';
229
230
"""
231
232
def run(self, params: Dict[str, Any]) -> Optional[result.FusionSQLResult]:
233
handler = get_handler(params['for_command'])
234
if handler is not None:
235
try:
236
from IPython.display import display
237
from IPython.display import Markdown
238
display(Markdown(handler.help))
239
except Exception:
240
print(handler.help)
241
else:
242
print(
243
f'No handler found for command \'{params["for_command"]}\'',
244
file=sys.stderr,
245
)
246
return None
247
248
249
ShowFusionHelpHandler.register()
250
251