singlestoredb 0.4.0__py3-none-any.whl → 1.0.4__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.
Potentially problematic release.
This version of singlestoredb might be problematic. Click here for more details.
- singlestoredb/__init__.py +33 -1
- singlestoredb/alchemy/__init__.py +90 -0
- singlestoredb/auth.py +5 -1
- singlestoredb/config.py +116 -14
- singlestoredb/connection.py +483 -516
- singlestoredb/converters.py +238 -135
- singlestoredb/exceptions.py +30 -2
- singlestoredb/functions/__init__.py +1 -0
- singlestoredb/functions/decorator.py +142 -0
- singlestoredb/functions/dtypes.py +1639 -0
- singlestoredb/functions/ext/__init__.py +2 -0
- singlestoredb/functions/ext/arrow.py +375 -0
- singlestoredb/functions/ext/asgi.py +661 -0
- singlestoredb/functions/ext/json.py +427 -0
- singlestoredb/functions/ext/mmap.py +306 -0
- singlestoredb/functions/ext/rowdat_1.py +744 -0
- singlestoredb/functions/signature.py +673 -0
- singlestoredb/fusion/__init__.py +11 -0
- singlestoredb/fusion/graphql.py +213 -0
- singlestoredb/fusion/handler.py +621 -0
- singlestoredb/fusion/handlers/stage.py +257 -0
- singlestoredb/fusion/handlers/utils.py +162 -0
- singlestoredb/fusion/handlers/workspace.py +412 -0
- singlestoredb/fusion/registry.py +164 -0
- singlestoredb/fusion/result.py +399 -0
- singlestoredb/http/__init__.py +27 -0
- singlestoredb/{http.py → http/connection.py} +555 -154
- singlestoredb/management/__init__.py +3 -0
- singlestoredb/management/billing_usage.py +148 -0
- singlestoredb/management/cluster.py +14 -6
- singlestoredb/management/manager.py +100 -38
- singlestoredb/management/organization.py +188 -0
- singlestoredb/management/region.py +5 -5
- singlestoredb/management/utils.py +281 -2
- singlestoredb/management/workspace.py +1344 -49
- singlestoredb/{clients/pymysqlsv → mysql}/__init__.py +16 -21
- singlestoredb/{clients/pymysqlsv → mysql}/_auth.py +39 -8
- singlestoredb/{clients/pymysqlsv → mysql}/charset.py +26 -23
- singlestoredb/{clients/pymysqlsv/connections.py → mysql/connection.py} +532 -165
- singlestoredb/{clients/pymysqlsv → mysql}/constants/CLIENT.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/COMMAND.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/CR.py +0 -2
- singlestoredb/{clients/pymysqlsv → mysql}/constants/ER.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/FIELD_TYPE.py +1 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/FLAG.py +0 -1
- singlestoredb/{clients/pymysqlsv → mysql}/constants/SERVER_STATUS.py +0 -1
- singlestoredb/mysql/converters.py +271 -0
- singlestoredb/{clients/pymysqlsv → mysql}/cursors.py +228 -112
- singlestoredb/mysql/err.py +92 -0
- singlestoredb/{clients/pymysqlsv → mysql}/optionfile.py +5 -4
- singlestoredb/{clients/pymysqlsv → mysql}/protocol.py +49 -20
- singlestoredb/mysql/tests/__init__.py +19 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/base.py +32 -12
- singlestoredb/mysql/tests/conftest.py +37 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_DictCursor.py +11 -7
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_SSCursor.py +17 -12
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_basic.py +32 -24
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_connection.py +130 -119
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_converters.py +9 -7
- singlestoredb/mysql/tests/test_cursor.py +141 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_err.py +3 -2
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_issues.py +35 -27
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_load_local.py +13 -11
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_nextset.py +7 -3
- singlestoredb/{clients/pymysqlsv → mysql}/tests/test_optionfile.py +2 -1
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/__init__.py +1 -1
- singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +9 -0
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/capabilities.py +19 -17
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/dbapi20.py +31 -22
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +3 -4
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +24 -20
- singlestoredb/{clients/pymysqlsv → mysql}/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +4 -4
- singlestoredb/{clients/pymysqlsv → mysql}/times.py +3 -4
- singlestoredb/pytest.py +283 -0
- singlestoredb/tests/empty.sql +0 -0
- singlestoredb/tests/ext_funcs/__init__.py +385 -0
- singlestoredb/tests/test.sql +210 -0
- singlestoredb/tests/test2.sql +1 -0
- singlestoredb/tests/test_basics.py +482 -115
- singlestoredb/tests/test_config.py +13 -13
- singlestoredb/tests/test_connection.py +241 -305
- singlestoredb/tests/test_dbapi.py +27 -0
- singlestoredb/tests/test_ext_func.py +1193 -0
- singlestoredb/tests/test_ext_func_data.py +1101 -0
- singlestoredb/tests/test_fusion.py +465 -0
- singlestoredb/tests/test_http.py +32 -26
- singlestoredb/tests/test_management.py +588 -8
- singlestoredb/tests/test_plugin.py +33 -0
- singlestoredb/tests/test_results.py +11 -12
- singlestoredb/tests/test_udf.py +687 -0
- singlestoredb/tests/utils.py +3 -2
- singlestoredb/utils/config.py +58 -0
- singlestoredb/utils/debug.py +13 -0
- singlestoredb/utils/mogrify.py +151 -0
- singlestoredb/utils/results.py +4 -1
- singlestoredb-1.0.4.dist-info/METADATA +139 -0
- singlestoredb-1.0.4.dist-info/RECORD +112 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/WHEEL +1 -1
- singlestoredb-1.0.4.dist-info/entry_points.txt +2 -0
- singlestoredb/clients/pymysqlsv/converters.py +0 -365
- singlestoredb/clients/pymysqlsv/err.py +0 -144
- singlestoredb/clients/pymysqlsv/tests/__init__.py +0 -19
- singlestoredb/clients/pymysqlsv/tests/test_cursor.py +0 -133
- singlestoredb/clients/pymysqlsv/tests/thirdparty/test_MySQLdb/__init__.py +0 -9
- singlestoredb/drivers/__init__.py +0 -45
- singlestoredb/drivers/base.py +0 -198
- singlestoredb/drivers/cymysql.py +0 -38
- singlestoredb/drivers/http.py +0 -47
- singlestoredb/drivers/mariadb.py +0 -40
- singlestoredb/drivers/mysqlconnector.py +0 -49
- singlestoredb/drivers/mysqldb.py +0 -60
- singlestoredb/drivers/pymysql.py +0 -37
- singlestoredb/drivers/pymysqlsv.py +0 -35
- singlestoredb/drivers/pyodbc.py +0 -65
- singlestoredb-0.4.0.dist-info/METADATA +0 -111
- singlestoredb-0.4.0.dist-info/RECORD +0 -86
- /singlestoredb/{clients → fusion/handlers}/__init__.py +0 -0
- /singlestoredb/{clients/pymysqlsv → mysql}/constants/__init__.py +0 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/LICENSE +0 -0
- {singlestoredb-0.4.0.dist-info → singlestoredb-1.0.4.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from typing import Any
|
|
6
|
+
from typing import Iterable
|
|
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 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
|