singlestoredb 1.16.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- singlestoredb/__init__.py +75 -0
- singlestoredb/ai/__init__.py +2 -0
- singlestoredb/ai/chat.py +139 -0
- singlestoredb/ai/embeddings.py +128 -0
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/apps/__init__.py +3 -0
- singlestoredb/apps/_cloud_functions.py +90 -0
- singlestoredb/apps/_config.py +72 -0
- singlestoredb/apps/_connection_info.py +18 -0
- singlestoredb/apps/_dashboards.py +47 -0
- singlestoredb/apps/_process.py +32 -0
- singlestoredb/apps/_python_udfs.py +100 -0
- singlestoredb/apps/_stdout_supress.py +30 -0
- singlestoredb/apps/_uvicorn_util.py +36 -0
- singlestoredb/auth.py +245 -0
- singlestoredb/config.py +484 -0
- singlestoredb/connection.py +1487 -0
- singlestoredb/converters.py +950 -0
- singlestoredb/docstring/__init__.py +33 -0
- singlestoredb/docstring/attrdoc.py +126 -0
- singlestoredb/docstring/common.py +230 -0
- singlestoredb/docstring/epydoc.py +267 -0
- singlestoredb/docstring/google.py +412 -0
- singlestoredb/docstring/numpydoc.py +562 -0
- singlestoredb/docstring/parser.py +100 -0
- singlestoredb/docstring/py.typed +1 -0
- singlestoredb/docstring/rest.py +256 -0
- singlestoredb/docstring/tests/__init__.py +1 -0
- singlestoredb/docstring/tests/_pydoctor.py +21 -0
- singlestoredb/docstring/tests/test_epydoc.py +729 -0
- singlestoredb/docstring/tests/test_google.py +1007 -0
- singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
- singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
- singlestoredb/docstring/tests/test_parser.py +248 -0
- singlestoredb/docstring/tests/test_rest.py +547 -0
- singlestoredb/docstring/tests/test_util.py +70 -0
- singlestoredb/docstring/util.py +141 -0
- singlestoredb/exceptions.py +120 -0
- singlestoredb/functions/__init__.py +16 -0
- singlestoredb/functions/decorator.py +201 -0
- singlestoredb/functions/dtypes.py +1793 -0
- singlestoredb/functions/ext/__init__.py +1 -0
- singlestoredb/functions/ext/arrow.py +375 -0
- singlestoredb/functions/ext/asgi.py +2133 -0
- singlestoredb/functions/ext/json.py +420 -0
- singlestoredb/functions/ext/mmap.py +413 -0
- singlestoredb/functions/ext/rowdat_1.py +724 -0
- singlestoredb/functions/ext/timer.py +89 -0
- singlestoredb/functions/ext/utils.py +218 -0
- singlestoredb/functions/signature.py +1578 -0
- singlestoredb/functions/typing/__init__.py +41 -0
- singlestoredb/functions/typing/numpy.py +20 -0
- singlestoredb/functions/typing/pandas.py +2 -0
- singlestoredb/functions/typing/polars.py +2 -0
- singlestoredb/functions/typing/pyarrow.py +2 -0
- singlestoredb/functions/utils.py +421 -0
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/graphql.py +213 -0
- singlestoredb/fusion/handler.py +916 -0
- singlestoredb/fusion/handlers/__init__.py +0 -0
- singlestoredb/fusion/handlers/export.py +525 -0
- singlestoredb/fusion/handlers/files.py +690 -0
- singlestoredb/fusion/handlers/job.py +660 -0
- singlestoredb/fusion/handlers/models.py +250 -0
- singlestoredb/fusion/handlers/stage.py +502 -0
- singlestoredb/fusion/handlers/utils.py +324 -0
- singlestoredb/fusion/handlers/workspace.py +956 -0
- singlestoredb/fusion/registry.py +249 -0
- singlestoredb/fusion/result.py +399 -0
- singlestoredb/http/__init__.py +27 -0
- singlestoredb/http/connection.py +1267 -0
- singlestoredb/magics/__init__.py +34 -0
- singlestoredb/magics/run_personal.py +137 -0
- singlestoredb/magics/run_shared.py +134 -0
- singlestoredb/management/__init__.py +9 -0
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +462 -0
- singlestoredb/management/export.py +295 -0
- singlestoredb/management/files.py +1102 -0
- singlestoredb/management/inference_api.py +105 -0
- singlestoredb/management/job.py +887 -0
- singlestoredb/management/manager.py +373 -0
- singlestoredb/management/organization.py +226 -0
- singlestoredb/management/region.py +169 -0
- singlestoredb/management/utils.py +423 -0
- singlestoredb/management/workspace.py +1927 -0
- singlestoredb/mysql/__init__.py +177 -0
- singlestoredb/mysql/_auth.py +298 -0
- singlestoredb/mysql/charset.py +214 -0
- singlestoredb/mysql/connection.py +2032 -0
- singlestoredb/mysql/constants/CLIENT.py +38 -0
- singlestoredb/mysql/constants/COMMAND.py +32 -0
- singlestoredb/mysql/constants/CR.py +78 -0
- singlestoredb/mysql/constants/ER.py +474 -0
- singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
- singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
- singlestoredb/mysql/constants/FLAG.py +15 -0
- singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
- singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
- singlestoredb/mysql/constants/__init__.py +0 -0
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/mysql/cursors.py +896 -0
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/mysql/optionfile.py +20 -0
- singlestoredb/mysql/protocol.py +450 -0
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/mysql/tests/base.py +126 -0
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/mysql/tests/test_DictCursor.py +132 -0
- singlestoredb/mysql/tests/test_SSCursor.py +141 -0
- singlestoredb/mysql/tests/test_basic.py +452 -0
- singlestoredb/mysql/tests/test_connection.py +851 -0
- singlestoredb/mysql/tests/test_converters.py +58 -0
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/mysql/tests/test_err.py +16 -0
- singlestoredb/mysql/tests/test_issues.py +514 -0
- singlestoredb/mysql/tests/test_load_local.py +75 -0
- singlestoredb/mysql/tests/test_nextset.py +88 -0
- singlestoredb/mysql/tests/test_optionfile.py +27 -0
- singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
- singlestoredb/mysql/times.py +23 -0
- singlestoredb/notebook/__init__.py +16 -0
- singlestoredb/notebook/_objects.py +213 -0
- singlestoredb/notebook/_portal.py +352 -0
- singlestoredb/py.typed +0 -0
- singlestoredb/pytest.py +352 -0
- singlestoredb/server/__init__.py +0 -0
- singlestoredb/server/docker.py +452 -0
- singlestoredb/server/free_tier.py +267 -0
- singlestoredb/tests/__init__.py +0 -0
- singlestoredb/tests/alltypes.sql +307 -0
- singlestoredb/tests/alltypes_no_nulls.sql +208 -0
- singlestoredb/tests/empty.sql +0 -0
- singlestoredb/tests/ext_funcs/__init__.py +702 -0
- singlestoredb/tests/local_infile.csv +3 -0
- singlestoredb/tests/test.ipynb +18 -0
- singlestoredb/tests/test.sql +680 -0
- singlestoredb/tests/test2.ipynb +18 -0
- singlestoredb/tests/test2.sql +1 -0
- singlestoredb/tests/test_basics.py +1332 -0
- singlestoredb/tests/test_config.py +318 -0
- singlestoredb/tests/test_connection.py +3103 -0
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_exceptions.py +45 -0
- singlestoredb/tests/test_ext_func.py +1472 -0
- singlestoredb/tests/test_ext_func_data.py +1101 -0
- singlestoredb/tests/test_fusion.py +1527 -0
- singlestoredb/tests/test_http.py +288 -0
- singlestoredb/tests/test_management.py +1599 -0
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +171 -0
- singlestoredb/tests/test_types.py +132 -0
- singlestoredb/tests/test_udf.py +737 -0
- singlestoredb/tests/test_udf_returns.py +459 -0
- singlestoredb/tests/test_vectorstore.py +51 -0
- singlestoredb/tests/test_xdict.py +333 -0
- singlestoredb/tests/utils.py +141 -0
- singlestoredb/types.py +373 -0
- singlestoredb/utils/__init__.py +0 -0
- singlestoredb/utils/config.py +950 -0
- singlestoredb/utils/convert_rows.py +69 -0
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/dtypes.py +205 -0
- singlestoredb/utils/events.py +65 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +585 -0
- singlestoredb/utils/xdict.py +425 -0
- singlestoredb/vectorstore.py +192 -0
- singlestoredb/warnings.py +5 -0
- singlestoredb-1.16.1.dist-info/METADATA +165 -0
- singlestoredb-1.16.1.dist-info/RECORD +183 -0
- singlestoredb-1.16.1.dist-info/WHEEL +5 -0
- singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
- singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
- singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
- sqlx/__init__.py +4 -0
- sqlx/magic.py +113 -0
|
@@ -0,0 +1,249 @@
|
|
|
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()
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from typing import Any
|
|
7
|
+
from typing import List
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from typing import Tuple
|
|
10
|
+
from typing import Union
|
|
11
|
+
|
|
12
|
+
from .. import connection
|
|
13
|
+
from ..mysql.constants.FIELD_TYPE import BLOB # noqa: F401
|
|
14
|
+
from ..mysql.constants.FIELD_TYPE import BOOL # noqa: F401
|
|
15
|
+
from ..mysql.constants.FIELD_TYPE import DATE # noqa: F401
|
|
16
|
+
from ..mysql.constants.FIELD_TYPE import DATETIME # noqa: F401
|
|
17
|
+
from ..mysql.constants.FIELD_TYPE import DOUBLE # noqa: F401
|
|
18
|
+
from ..mysql.constants.FIELD_TYPE import JSON # noqa: F401
|
|
19
|
+
from ..mysql.constants.FIELD_TYPE import LONGLONG as INTEGER # noqa: F401
|
|
20
|
+
from ..mysql.constants.FIELD_TYPE import STRING # noqa: F401
|
|
21
|
+
from ..utils.results import Description
|
|
22
|
+
from ..utils.results import format_results
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FusionField(object):
|
|
26
|
+
"""Field for PyMySQL compatibility."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, name: str, flags: int, charset: int) -> None:
|
|
29
|
+
self.name = name
|
|
30
|
+
self.flags = flags
|
|
31
|
+
self.charsetnr = charset
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FusionSQLColumn(object):
|
|
35
|
+
"""Column accessor for a FusionSQLResult."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, result: FusionSQLResult, index: int) -> None:
|
|
38
|
+
self._result = result
|
|
39
|
+
self._index = index
|
|
40
|
+
|
|
41
|
+
def __getitem__(self, index: Any) -> Any:
|
|
42
|
+
return self._result.rows[index][self._index]
|
|
43
|
+
|
|
44
|
+
def __iter__(self) -> Iterable[Any]:
|
|
45
|
+
def gen() -> Iterable[Any]:
|
|
46
|
+
for row in iter(self._result):
|
|
47
|
+
yield row[self._index]
|
|
48
|
+
return gen()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class FieldIndexDict(dict): # type: ignore
|
|
52
|
+
"""Case-insensitive dictionary for column name lookups."""
|
|
53
|
+
|
|
54
|
+
def __getitem__(self, key: str) -> int:
|
|
55
|
+
return super().__getitem__(key.lower())
|
|
56
|
+
|
|
57
|
+
def __setitem__(self, key: str, value: int) -> None:
|
|
58
|
+
super().__setitem__(key.lower(), value)
|
|
59
|
+
|
|
60
|
+
def __contains__(self, key: object) -> bool:
|
|
61
|
+
if not isinstance(key, str):
|
|
62
|
+
return False
|
|
63
|
+
return super().__contains__(str(key).lower())
|
|
64
|
+
|
|
65
|
+
def copy(self) -> FieldIndexDict:
|
|
66
|
+
out = type(self)()
|
|
67
|
+
for k, v in self.items():
|
|
68
|
+
out[k.lower()] = v
|
|
69
|
+
return out
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class FusionSQLResult(object):
|
|
73
|
+
"""Result for Fusion SQL commands."""
|
|
74
|
+
|
|
75
|
+
def __init__(self) -> None:
|
|
76
|
+
self.connection: Any = None
|
|
77
|
+
self.affected_rows: Optional[int] = None
|
|
78
|
+
self.insert_id: int = 0
|
|
79
|
+
self.server_status: Optional[int] = None
|
|
80
|
+
self.warning_count: int = 0
|
|
81
|
+
self.message: Optional[str] = None
|
|
82
|
+
self.description: List[Description] = []
|
|
83
|
+
self.rows: Any = []
|
|
84
|
+
self.has_next: bool = False
|
|
85
|
+
self.unbuffered_active: bool = False
|
|
86
|
+
self.converters: List[Any] = []
|
|
87
|
+
self.fields: List[FusionField] = []
|
|
88
|
+
self._field_indexes: FieldIndexDict = FieldIndexDict()
|
|
89
|
+
self._row_idx: int = -1
|
|
90
|
+
|
|
91
|
+
def copy(self) -> FusionSQLResult:
|
|
92
|
+
"""Copy the result."""
|
|
93
|
+
out = type(self)()
|
|
94
|
+
for k, v in vars(self).items():
|
|
95
|
+
if isinstance(v, list):
|
|
96
|
+
setattr(out, k, list(v))
|
|
97
|
+
elif isinstance(v, dict):
|
|
98
|
+
setattr(out, k, v.copy())
|
|
99
|
+
else:
|
|
100
|
+
setattr(out, k, v)
|
|
101
|
+
return out
|
|
102
|
+
|
|
103
|
+
def _read_rowdata_packet_unbuffered(self, size: int = 1) -> Optional[List[Any]]:
|
|
104
|
+
if not self.rows:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
out = []
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
for i in range(1, size + 1):
|
|
111
|
+
out.append(self.rows[self._row_idx + i])
|
|
112
|
+
except IndexError:
|
|
113
|
+
self._row_idx = -1
|
|
114
|
+
self.rows = []
|
|
115
|
+
return None
|
|
116
|
+
else:
|
|
117
|
+
self._row_idx += size
|
|
118
|
+
|
|
119
|
+
return out
|
|
120
|
+
|
|
121
|
+
def _finish_unbuffered_query(self) -> None:
|
|
122
|
+
self._row_idx = -1
|
|
123
|
+
self.rows = []
|
|
124
|
+
self.affected_rows = None
|
|
125
|
+
|
|
126
|
+
def format_results(self, connection: connection.Connection) -> None:
|
|
127
|
+
"""
|
|
128
|
+
Format the results using the connection converters and options.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
connection : Connection
|
|
133
|
+
The connection containing the converters and options
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
self.converters = []
|
|
137
|
+
|
|
138
|
+
for item in self.description:
|
|
139
|
+
self.converters.append((
|
|
140
|
+
item.charset,
|
|
141
|
+
connection.decoders.get(item.type_code),
|
|
142
|
+
))
|
|
143
|
+
|
|
144
|
+
# Convert values
|
|
145
|
+
for i, row in enumerate(self.rows):
|
|
146
|
+
new_row = []
|
|
147
|
+
for (_, converter), value in zip(self.converters, row):
|
|
148
|
+
new_row.append(converter(value) if converter is not None else value)
|
|
149
|
+
self.rows[i] = tuple(new_row)
|
|
150
|
+
|
|
151
|
+
self.rows[:] = format_results(
|
|
152
|
+
connection._results_type, self.description, self.rows,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
def __iter__(self) -> Iterable[Tuple[Any, ...]]:
|
|
156
|
+
return iter(self.rows)
|
|
157
|
+
|
|
158
|
+
def __len__(self) -> int:
|
|
159
|
+
return len(self.rows)
|
|
160
|
+
|
|
161
|
+
def __getitem__(self, key: Any) -> Tuple[Any, ...]:
|
|
162
|
+
if isinstance(key, str):
|
|
163
|
+
return self.__getattr__(key)
|
|
164
|
+
return self.rows[key]
|
|
165
|
+
|
|
166
|
+
def __getattr__(self, name: str) -> Any:
|
|
167
|
+
return FusionSQLColumn(self, self._field_indexes[name])
|
|
168
|
+
|
|
169
|
+
def add_field(self, name: str, dtype: int) -> None:
|
|
170
|
+
"""
|
|
171
|
+
Add a new field / column to the data set.
|
|
172
|
+
|
|
173
|
+
Parameters
|
|
174
|
+
----------
|
|
175
|
+
name : str
|
|
176
|
+
The name of the field / column
|
|
177
|
+
dtype : int
|
|
178
|
+
The MySQL field type: BLOB, BOOL, DATE, DATETIME,
|
|
179
|
+
DOUBLE, JSON, INTEGER, or STRING
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
charset = 0
|
|
183
|
+
if dtype in (JSON, STRING):
|
|
184
|
+
encoding = 'utf-8'
|
|
185
|
+
elif dtype == BLOB:
|
|
186
|
+
charset = 63
|
|
187
|
+
encoding = None
|
|
188
|
+
else:
|
|
189
|
+
encoding = 'ascii'
|
|
190
|
+
self.description.append(
|
|
191
|
+
Description(name, dtype, None, None, 0, 0, True, 0, charset),
|
|
192
|
+
)
|
|
193
|
+
self.fields.append(FusionField(name, 0, charset))
|
|
194
|
+
self._field_indexes[name] = len(self.fields) - 1
|
|
195
|
+
self.converters.append((encoding, None))
|
|
196
|
+
|
|
197
|
+
def set_rows(self, data: List[Tuple[Any, ...]]) -> None:
|
|
198
|
+
"""
|
|
199
|
+
Set the rows of the result.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
data : List[Tuple[Any, ...]]
|
|
204
|
+
The data should be a list of tuples where each element of the
|
|
205
|
+
tuple corresponds to a field added to the result with
|
|
206
|
+
the :meth:`add_field` method.
|
|
207
|
+
|
|
208
|
+
"""
|
|
209
|
+
self.rows = list(data)
|
|
210
|
+
self.affected_rows = 0
|
|
211
|
+
|
|
212
|
+
def like(self, **kwargs: str) -> FusionSQLResult:
|
|
213
|
+
"""
|
|
214
|
+
Return a new result containing only rows that match all `kwargs` like patterns.
|
|
215
|
+
|
|
216
|
+
Parameters
|
|
217
|
+
----------
|
|
218
|
+
**kwargs : str
|
|
219
|
+
Each parameter name corresponds to a column name in the result. The value
|
|
220
|
+
of the parameters is a LIKE pattern to match.
|
|
221
|
+
|
|
222
|
+
Returns
|
|
223
|
+
-------
|
|
224
|
+
FusionSQLResult
|
|
225
|
+
|
|
226
|
+
"""
|
|
227
|
+
likers = []
|
|
228
|
+
for k, v in kwargs.items():
|
|
229
|
+
if k not in self._field_indexes:
|
|
230
|
+
raise KeyError(f'field name does not exist in results: {k}')
|
|
231
|
+
if not v:
|
|
232
|
+
continue
|
|
233
|
+
regex = re.compile(
|
|
234
|
+
'^{}$'.format(
|
|
235
|
+
re.sub(r'\\%', r'.*', re.sub(r'([^\w])', r'\\\1', v)),
|
|
236
|
+
), flags=re.I | re.M,
|
|
237
|
+
)
|
|
238
|
+
likers.append((self._field_indexes[k], regex))
|
|
239
|
+
|
|
240
|
+
filtered_rows = []
|
|
241
|
+
for row in self.rows:
|
|
242
|
+
found = True
|
|
243
|
+
for i, liker in likers:
|
|
244
|
+
if row[i] is None or not liker.match(row[i]):
|
|
245
|
+
found = False
|
|
246
|
+
break
|
|
247
|
+
if found:
|
|
248
|
+
filtered_rows.append(row)
|
|
249
|
+
|
|
250
|
+
out = self.copy()
|
|
251
|
+
out.rows[:] = filtered_rows
|
|
252
|
+
return out
|
|
253
|
+
|
|
254
|
+
like_all = like
|
|
255
|
+
|
|
256
|
+
def like_any(self, **kwargs: str) -> FusionSQLResult:
|
|
257
|
+
"""
|
|
258
|
+
Return a new result containing only rows that match any `kwargs` like patterns.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
**kwargs : str
|
|
263
|
+
Each parameter name corresponds to a column name in the result. The value
|
|
264
|
+
of the parameters is a LIKE pattern to match.
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
FusionSQLResult
|
|
269
|
+
|
|
270
|
+
"""
|
|
271
|
+
likers = []
|
|
272
|
+
for k, v in kwargs.items():
|
|
273
|
+
if k not in self._field_indexes:
|
|
274
|
+
raise KeyError(f'field name does not exist in results: {k}')
|
|
275
|
+
if not v:
|
|
276
|
+
continue
|
|
277
|
+
regex = re.compile(
|
|
278
|
+
'^{}$'.format(
|
|
279
|
+
re.sub(r'\\%', r'.*', re.sub(r'([^\w])', r'\\\1', v)),
|
|
280
|
+
), flags=re.I | re.M,
|
|
281
|
+
)
|
|
282
|
+
likers.append((self._field_indexes[k], regex))
|
|
283
|
+
|
|
284
|
+
filtered_rows = []
|
|
285
|
+
for row in self.rows:
|
|
286
|
+
found = False
|
|
287
|
+
for i, liker in likers:
|
|
288
|
+
if liker.match(row[i]):
|
|
289
|
+
found = True
|
|
290
|
+
break
|
|
291
|
+
if found:
|
|
292
|
+
filtered_rows.append(row)
|
|
293
|
+
|
|
294
|
+
out = self.copy()
|
|
295
|
+
out.rows[:] = filtered_rows
|
|
296
|
+
return out
|
|
297
|
+
|
|
298
|
+
def filter(self, **kwargs: str) -> FusionSQLResult:
|
|
299
|
+
"""
|
|
300
|
+
Return a new result containing only rows that match all `kwargs` values.
|
|
301
|
+
|
|
302
|
+
Parameters
|
|
303
|
+
----------
|
|
304
|
+
**kwargs : str
|
|
305
|
+
Each parameter name corresponds to a column name in the result. The value
|
|
306
|
+
of the parameters is the value to match.
|
|
307
|
+
|
|
308
|
+
Returns
|
|
309
|
+
-------
|
|
310
|
+
FusionSQLResult
|
|
311
|
+
|
|
312
|
+
"""
|
|
313
|
+
if not kwargs:
|
|
314
|
+
return self.copy()
|
|
315
|
+
|
|
316
|
+
values = []
|
|
317
|
+
for k, v in kwargs.items():
|
|
318
|
+
if k not in self._field_indexes:
|
|
319
|
+
raise KeyError(f'field name does not exist in results: {k}')
|
|
320
|
+
values.append((self._field_indexes[k], v))
|
|
321
|
+
|
|
322
|
+
filtered_rows = []
|
|
323
|
+
for row in self.rows:
|
|
324
|
+
found = True
|
|
325
|
+
for i, val in values:
|
|
326
|
+
if row[0] != val:
|
|
327
|
+
found = False
|
|
328
|
+
break
|
|
329
|
+
if found:
|
|
330
|
+
filtered_rows.append(row)
|
|
331
|
+
|
|
332
|
+
out = self.copy()
|
|
333
|
+
out.rows[:] = filtered_rows
|
|
334
|
+
return out
|
|
335
|
+
|
|
336
|
+
def limit(self, n_rows: int) -> FusionSQLResult:
|
|
337
|
+
"""
|
|
338
|
+
Return a new result containing only `n_rows` rows.
|
|
339
|
+
|
|
340
|
+
Parameters
|
|
341
|
+
----------
|
|
342
|
+
n_rows : int
|
|
343
|
+
The number of rows to limit the result to
|
|
344
|
+
|
|
345
|
+
Returns
|
|
346
|
+
-------
|
|
347
|
+
FusionSQLResult
|
|
348
|
+
|
|
349
|
+
"""
|
|
350
|
+
out = self.copy()
|
|
351
|
+
if n_rows:
|
|
352
|
+
out.rows[:] = out.rows[:n_rows]
|
|
353
|
+
return out
|
|
354
|
+
|
|
355
|
+
def sort_by(
|
|
356
|
+
self,
|
|
357
|
+
by: Union[str, List[str]],
|
|
358
|
+
ascending: Union[bool, List[bool]] = True,
|
|
359
|
+
) -> FusionSQLResult:
|
|
360
|
+
"""
|
|
361
|
+
Return a new result with rows sorted in specified order.
|
|
362
|
+
|
|
363
|
+
Parameters
|
|
364
|
+
----------
|
|
365
|
+
by : str or List[str]
|
|
366
|
+
Name or names of columns to sort by
|
|
367
|
+
ascending : bool or List[bool], optional
|
|
368
|
+
Should the sort order be ascending? If not all sort columns
|
|
369
|
+
use the same ordering, a list of booleans can be supplied to
|
|
370
|
+
indicate the order for each column.
|
|
371
|
+
|
|
372
|
+
Returns
|
|
373
|
+
-------
|
|
374
|
+
FusionSQLResult
|
|
375
|
+
|
|
376
|
+
"""
|
|
377
|
+
if not by:
|
|
378
|
+
return self.copy()
|
|
379
|
+
|
|
380
|
+
if isinstance(by, str):
|
|
381
|
+
by = [by]
|
|
382
|
+
by = list(reversed(by))
|
|
383
|
+
|
|
384
|
+
if isinstance(ascending, bool):
|
|
385
|
+
ascending = [ascending]
|
|
386
|
+
ascending = list(reversed(ascending))
|
|
387
|
+
|
|
388
|
+
out = self.copy()
|
|
389
|
+
for i, byvar in enumerate(by):
|
|
390
|
+
out.rows.sort(
|
|
391
|
+
key=lambda x: (
|
|
392
|
+
0 if x[self._field_indexes[byvar]] is None else 1,
|
|
393
|
+
x[self._field_indexes[byvar]],
|
|
394
|
+
),
|
|
395
|
+
reverse=not ascending[i],
|
|
396
|
+
)
|
|
397
|
+
return out
|
|
398
|
+
|
|
399
|
+
order_by = sort_by
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from ..exceptions import DatabaseError
|
|
2
|
+
from ..exceptions import DataError
|
|
3
|
+
from ..exceptions import Error
|
|
4
|
+
from ..exceptions import IntegrityError
|
|
5
|
+
from ..exceptions import InterfaceError
|
|
6
|
+
from ..exceptions import InternalError
|
|
7
|
+
from ..exceptions import ManagementError
|
|
8
|
+
from ..exceptions import NotSupportedError
|
|
9
|
+
from ..exceptions import OperationalError
|
|
10
|
+
from ..exceptions import ProgrammingError
|
|
11
|
+
from ..exceptions import Warning
|
|
12
|
+
from ..types import BINARY
|
|
13
|
+
from ..types import Binary
|
|
14
|
+
from ..types import Date
|
|
15
|
+
from ..types import DateFromTicks
|
|
16
|
+
from ..types import DATETIME
|
|
17
|
+
from ..types import NUMBER
|
|
18
|
+
from ..types import ROWID
|
|
19
|
+
from ..types import STRING
|
|
20
|
+
from ..types import Time
|
|
21
|
+
from ..types import TimeFromTicks
|
|
22
|
+
from ..types import Timestamp
|
|
23
|
+
from ..types import TimestampFromTicks
|
|
24
|
+
from .connection import apilevel
|
|
25
|
+
from .connection import connect
|
|
26
|
+
from .connection import paramstyle
|
|
27
|
+
from .connection import threadsafety
|