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.
Files changed (183) hide show
  1. singlestoredb/__init__.py +75 -0
  2. singlestoredb/ai/__init__.py +2 -0
  3. singlestoredb/ai/chat.py +139 -0
  4. singlestoredb/ai/embeddings.py +128 -0
  5. singlestoredb/alchemy/__init__.py +90 -0
  6. singlestoredb/apps/__init__.py +3 -0
  7. singlestoredb/apps/_cloud_functions.py +90 -0
  8. singlestoredb/apps/_config.py +72 -0
  9. singlestoredb/apps/_connection_info.py +18 -0
  10. singlestoredb/apps/_dashboards.py +47 -0
  11. singlestoredb/apps/_process.py +32 -0
  12. singlestoredb/apps/_python_udfs.py +100 -0
  13. singlestoredb/apps/_stdout_supress.py +30 -0
  14. singlestoredb/apps/_uvicorn_util.py +36 -0
  15. singlestoredb/auth.py +245 -0
  16. singlestoredb/config.py +484 -0
  17. singlestoredb/connection.py +1487 -0
  18. singlestoredb/converters.py +950 -0
  19. singlestoredb/docstring/__init__.py +33 -0
  20. singlestoredb/docstring/attrdoc.py +126 -0
  21. singlestoredb/docstring/common.py +230 -0
  22. singlestoredb/docstring/epydoc.py +267 -0
  23. singlestoredb/docstring/google.py +412 -0
  24. singlestoredb/docstring/numpydoc.py +562 -0
  25. singlestoredb/docstring/parser.py +100 -0
  26. singlestoredb/docstring/py.typed +1 -0
  27. singlestoredb/docstring/rest.py +256 -0
  28. singlestoredb/docstring/tests/__init__.py +1 -0
  29. singlestoredb/docstring/tests/_pydoctor.py +21 -0
  30. singlestoredb/docstring/tests/test_epydoc.py +729 -0
  31. singlestoredb/docstring/tests/test_google.py +1007 -0
  32. singlestoredb/docstring/tests/test_numpydoc.py +1100 -0
  33. singlestoredb/docstring/tests/test_parse_from_object.py +109 -0
  34. singlestoredb/docstring/tests/test_parser.py +248 -0
  35. singlestoredb/docstring/tests/test_rest.py +547 -0
  36. singlestoredb/docstring/tests/test_util.py +70 -0
  37. singlestoredb/docstring/util.py +141 -0
  38. singlestoredb/exceptions.py +120 -0
  39. singlestoredb/functions/__init__.py +16 -0
  40. singlestoredb/functions/decorator.py +201 -0
  41. singlestoredb/functions/dtypes.py +1793 -0
  42. singlestoredb/functions/ext/__init__.py +1 -0
  43. singlestoredb/functions/ext/arrow.py +375 -0
  44. singlestoredb/functions/ext/asgi.py +2133 -0
  45. singlestoredb/functions/ext/json.py +420 -0
  46. singlestoredb/functions/ext/mmap.py +413 -0
  47. singlestoredb/functions/ext/rowdat_1.py +724 -0
  48. singlestoredb/functions/ext/timer.py +89 -0
  49. singlestoredb/functions/ext/utils.py +218 -0
  50. singlestoredb/functions/signature.py +1578 -0
  51. singlestoredb/functions/typing/__init__.py +41 -0
  52. singlestoredb/functions/typing/numpy.py +20 -0
  53. singlestoredb/functions/typing/pandas.py +2 -0
  54. singlestoredb/functions/typing/polars.py +2 -0
  55. singlestoredb/functions/typing/pyarrow.py +2 -0
  56. singlestoredb/functions/utils.py +421 -0
  57. singlestoredb/fusion/__init__.py +11 -0
  58. singlestoredb/fusion/graphql.py +213 -0
  59. singlestoredb/fusion/handler.py +916 -0
  60. singlestoredb/fusion/handlers/__init__.py +0 -0
  61. singlestoredb/fusion/handlers/export.py +525 -0
  62. singlestoredb/fusion/handlers/files.py +690 -0
  63. singlestoredb/fusion/handlers/job.py +660 -0
  64. singlestoredb/fusion/handlers/models.py +250 -0
  65. singlestoredb/fusion/handlers/stage.py +502 -0
  66. singlestoredb/fusion/handlers/utils.py +324 -0
  67. singlestoredb/fusion/handlers/workspace.py +956 -0
  68. singlestoredb/fusion/registry.py +249 -0
  69. singlestoredb/fusion/result.py +399 -0
  70. singlestoredb/http/__init__.py +27 -0
  71. singlestoredb/http/connection.py +1267 -0
  72. singlestoredb/magics/__init__.py +34 -0
  73. singlestoredb/magics/run_personal.py +137 -0
  74. singlestoredb/magics/run_shared.py +134 -0
  75. singlestoredb/management/__init__.py +9 -0
  76. singlestoredb/management/billing_usage.py +148 -0
  77. singlestoredb/management/cluster.py +462 -0
  78. singlestoredb/management/export.py +295 -0
  79. singlestoredb/management/files.py +1102 -0
  80. singlestoredb/management/inference_api.py +105 -0
  81. singlestoredb/management/job.py +887 -0
  82. singlestoredb/management/manager.py +373 -0
  83. singlestoredb/management/organization.py +226 -0
  84. singlestoredb/management/region.py +169 -0
  85. singlestoredb/management/utils.py +423 -0
  86. singlestoredb/management/workspace.py +1927 -0
  87. singlestoredb/mysql/__init__.py +177 -0
  88. singlestoredb/mysql/_auth.py +298 -0
  89. singlestoredb/mysql/charset.py +214 -0
  90. singlestoredb/mysql/connection.py +2032 -0
  91. singlestoredb/mysql/constants/CLIENT.py +38 -0
  92. singlestoredb/mysql/constants/COMMAND.py +32 -0
  93. singlestoredb/mysql/constants/CR.py +78 -0
  94. singlestoredb/mysql/constants/ER.py +474 -0
  95. singlestoredb/mysql/constants/EXTENDED_TYPE.py +3 -0
  96. singlestoredb/mysql/constants/FIELD_TYPE.py +48 -0
  97. singlestoredb/mysql/constants/FLAG.py +15 -0
  98. singlestoredb/mysql/constants/SERVER_STATUS.py +10 -0
  99. singlestoredb/mysql/constants/VECTOR_TYPE.py +6 -0
  100. singlestoredb/mysql/constants/__init__.py +0 -0
  101. singlestoredb/mysql/converters.py +271 -0
  102. singlestoredb/mysql/cursors.py +896 -0
  103. singlestoredb/mysql/err.py +92 -0
  104. singlestoredb/mysql/optionfile.py +20 -0
  105. singlestoredb/mysql/protocol.py +450 -0
  106. singlestoredb/mysql/tests/__init__.py +19 -0
  107. singlestoredb/mysql/tests/base.py +126 -0
  108. singlestoredb/mysql/tests/conftest.py +37 -0
  109. singlestoredb/mysql/tests/test_DictCursor.py +132 -0
  110. singlestoredb/mysql/tests/test_SSCursor.py +141 -0
  111. singlestoredb/mysql/tests/test_basic.py +452 -0
  112. singlestoredb/mysql/tests/test_connection.py +851 -0
  113. singlestoredb/mysql/tests/test_converters.py +58 -0
  114. singlestoredb/mysql/tests/test_cursor.py +141 -0
  115. singlestoredb/mysql/tests/test_err.py +16 -0
  116. singlestoredb/mysql/tests/test_issues.py +514 -0
  117. singlestoredb/mysql/tests/test_load_local.py +75 -0
  118. singlestoredb/mysql/tests/test_nextset.py +88 -0
  119. singlestoredb/mysql/tests/test_optionfile.py +27 -0
  120. singlestoredb/mysql/tests/thirdparty/__init__.py +6 -0
  121. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
  122. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +323 -0
  123. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +865 -0
  124. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +110 -0
  125. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +224 -0
  126. singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +101 -0
  127. singlestoredb/mysql/times.py +23 -0
  128. singlestoredb/notebook/__init__.py +16 -0
  129. singlestoredb/notebook/_objects.py +213 -0
  130. singlestoredb/notebook/_portal.py +352 -0
  131. singlestoredb/py.typed +0 -0
  132. singlestoredb/pytest.py +352 -0
  133. singlestoredb/server/__init__.py +0 -0
  134. singlestoredb/server/docker.py +452 -0
  135. singlestoredb/server/free_tier.py +267 -0
  136. singlestoredb/tests/__init__.py +0 -0
  137. singlestoredb/tests/alltypes.sql +307 -0
  138. singlestoredb/tests/alltypes_no_nulls.sql +208 -0
  139. singlestoredb/tests/empty.sql +0 -0
  140. singlestoredb/tests/ext_funcs/__init__.py +702 -0
  141. singlestoredb/tests/local_infile.csv +3 -0
  142. singlestoredb/tests/test.ipynb +18 -0
  143. singlestoredb/tests/test.sql +680 -0
  144. singlestoredb/tests/test2.ipynb +18 -0
  145. singlestoredb/tests/test2.sql +1 -0
  146. singlestoredb/tests/test_basics.py +1332 -0
  147. singlestoredb/tests/test_config.py +318 -0
  148. singlestoredb/tests/test_connection.py +3103 -0
  149. singlestoredb/tests/test_dbapi.py +27 -0
  150. singlestoredb/tests/test_exceptions.py +45 -0
  151. singlestoredb/tests/test_ext_func.py +1472 -0
  152. singlestoredb/tests/test_ext_func_data.py +1101 -0
  153. singlestoredb/tests/test_fusion.py +1527 -0
  154. singlestoredb/tests/test_http.py +288 -0
  155. singlestoredb/tests/test_management.py +1599 -0
  156. singlestoredb/tests/test_plugin.py +33 -0
  157. singlestoredb/tests/test_results.py +171 -0
  158. singlestoredb/tests/test_types.py +132 -0
  159. singlestoredb/tests/test_udf.py +737 -0
  160. singlestoredb/tests/test_udf_returns.py +459 -0
  161. singlestoredb/tests/test_vectorstore.py +51 -0
  162. singlestoredb/tests/test_xdict.py +333 -0
  163. singlestoredb/tests/utils.py +141 -0
  164. singlestoredb/types.py +373 -0
  165. singlestoredb/utils/__init__.py +0 -0
  166. singlestoredb/utils/config.py +950 -0
  167. singlestoredb/utils/convert_rows.py +69 -0
  168. singlestoredb/utils/debug.py +13 -0
  169. singlestoredb/utils/dtypes.py +205 -0
  170. singlestoredb/utils/events.py +65 -0
  171. singlestoredb/utils/mogrify.py +151 -0
  172. singlestoredb/utils/results.py +585 -0
  173. singlestoredb/utils/xdict.py +425 -0
  174. singlestoredb/vectorstore.py +192 -0
  175. singlestoredb/warnings.py +5 -0
  176. singlestoredb-1.16.1.dist-info/METADATA +165 -0
  177. singlestoredb-1.16.1.dist-info/RECORD +183 -0
  178. singlestoredb-1.16.1.dist-info/WHEEL +5 -0
  179. singlestoredb-1.16.1.dist-info/entry_points.txt +2 -0
  180. singlestoredb-1.16.1.dist-info/licenses/LICENSE +201 -0
  181. singlestoredb-1.16.1.dist-info/top_level.txt +3 -0
  182. sqlx/__init__.py +4 -0
  183. 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