sqlengine-lite 2.1.1__py3-none-any.whl → 2.2.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.
- sqlengine/__init__.py +7 -3
- sqlengine/_internal/__init__.py +4 -0
- sqlengine/_internal/connection_manager.py +266 -0
- sqlengine/{utils → _internal}/repr.py +1 -30
- sqlengine/{utils → _internal}/sqlgen.py +2 -4
- sqlengine/{utils → _internal}/statements.py +71 -49
- sqlengine/_internal/types.py +105 -0
- sqlengine/exceptions.py +20 -0
- sqlengine/schema.py +1 -1
- sqlengine/sqltable.py +231 -313
- sqlengine/utils/__init__.py +2 -3
- sqlengine/utils/connection.py +29 -20
- sqlengine/utils/convert.py +129 -0
- {sqlengine_lite-2.1.1.dist-info → sqlengine_lite-2.2.1.dist-info}/METADATA +94 -20
- sqlengine_lite-2.2.1.dist-info/RECORD +18 -0
- sqlengine/utils/types.py +0 -65
- sqlengine_lite-2.1.1.dist-info/RECORD +0 -14
- {sqlengine_lite-2.1.1.dist-info → sqlengine_lite-2.2.1.dist-info}/WHEEL +0 -0
- {sqlengine_lite-2.1.1.dist-info → sqlengine_lite-2.2.1.dist-info}/licenses/LICENSE +0 -0
- {sqlengine_lite-2.1.1.dist-info → sqlengine_lite-2.2.1.dist-info}/top_level.txt +0 -0
sqlengine/__init__.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
from .
|
|
2
|
-
from .
|
|
1
|
+
from ._internal import sqlgen
|
|
2
|
+
from ._internal import ConnectionManager
|
|
3
|
+
from ._internal.types import Schema, Primary, register_type
|
|
3
4
|
from .sqltable import SqlTableMixin
|
|
4
5
|
|
|
5
6
|
__author__ = "suffermuffin"
|
|
6
7
|
|
|
7
|
-
__all__ = [
|
|
8
|
+
__all__ = [
|
|
9
|
+
"sqlgen", "Schema", "SqlTableMixin",
|
|
10
|
+
"Primary", "ConnectionManager", "register_type"
|
|
11
|
+
]
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from typing import overload, Literal, Sequence
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
|
|
8
|
+
from .types import SqlValue, SqlRow
|
|
9
|
+
from ..exceptions import TransactionError, NestedTransactionError, OutsideTransactionError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger("sqlengine")
|
|
13
|
+
logger.setLevel(os.getenv("SQL_ENGINE_LOG_LEVEL", "WARNING").upper())
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ConnectionManager:
|
|
17
|
+
|
|
18
|
+
"""
|
|
19
|
+
Connection manager for sqlite3
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
|
|
23
|
+
database (str): database filename to connect to. If `":memory:"` is passed, then database will be set in memory and you will have to
|
|
24
|
+
create table manually with `create_table()` method inside `transaction()` block.
|
|
25
|
+
**connection_params: Params to create connection with. Reference: https://docs.python.org/3/library/sqlite3.html#sqlite3.connect
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
_trans : sqlite3.Connection
|
|
29
|
+
_trans_cursor : sqlite3.Cursor
|
|
30
|
+
|
|
31
|
+
def __init__(self, database : str, **connection_params) -> None:
|
|
32
|
+
|
|
33
|
+
self.database = database
|
|
34
|
+
self.connection_params = connection_params
|
|
35
|
+
|
|
36
|
+
self._is_managed_transaction = False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@overload
|
|
40
|
+
def _fetch(self, query : str, args : SqlRow,
|
|
41
|
+
method : Literal["fetchone"]) -> SqlRow: ...
|
|
42
|
+
@overload
|
|
43
|
+
def _fetch(self, query : str, args : SqlRow,
|
|
44
|
+
method : Literal["fetchall"]) -> list[SqlRow]: ...
|
|
45
|
+
|
|
46
|
+
def _fetch(
|
|
47
|
+
self,
|
|
48
|
+
query : str,
|
|
49
|
+
args : SqlRow = (),
|
|
50
|
+
method : Literal["fetchone", "fetchall"] = "fetchall"
|
|
51
|
+
) -> SqlRow | list[SqlRow]:
|
|
52
|
+
|
|
53
|
+
logger.debug(f"{self.database}: {query} {args}")
|
|
54
|
+
|
|
55
|
+
if self.in_transaction():
|
|
56
|
+
self._trans_cursor.execute(query, args)
|
|
57
|
+
return getattr(self._trans_cursor, method)()
|
|
58
|
+
|
|
59
|
+
with self.connect() as conn:
|
|
60
|
+
cursor = conn.cursor()
|
|
61
|
+
cursor.execute(query, args)
|
|
62
|
+
return getattr(cursor, method)()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@overload
|
|
66
|
+
def _execute(self, query : str, args : tuple[SqlValue, ...], method : Literal["execute"]) -> None: ...
|
|
67
|
+
@overload
|
|
68
|
+
def _execute(self, query : str, args : Sequence[SqlRow], method : Literal["executemany"]) -> None: ...
|
|
69
|
+
|
|
70
|
+
def _execute(
|
|
71
|
+
self,
|
|
72
|
+
query : str,
|
|
73
|
+
args : tuple[SqlValue, ...] | Sequence[SqlRow] = (),
|
|
74
|
+
method : Literal["execute", "executemany"] = "execute"
|
|
75
|
+
) -> None:
|
|
76
|
+
"""
|
|
77
|
+
Shortcut to connect() -> execute[<many>]() -> commit() for single operations.
|
|
78
|
+
Can be used in transaction using `transaction()` manager.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
query (str): SQL query to execute on SQLite3 DB
|
|
82
|
+
*args (Any): Arguments to the execution
|
|
83
|
+
method (str): "execute" or "executemany"
|
|
84
|
+
"""
|
|
85
|
+
logger.debug(f"{self.database}: {query} {args}")
|
|
86
|
+
|
|
87
|
+
if self.in_transaction():
|
|
88
|
+
getattr(self._trans_cursor, method)(query, args)
|
|
89
|
+
return
|
|
90
|
+
|
|
91
|
+
with self.connect() as conn:
|
|
92
|
+
cursor = conn.cursor()
|
|
93
|
+
getattr(cursor, method)(query, args)
|
|
94
|
+
conn.commit()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def execute(self, query : str, *args : SqlValue) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Shortcut to connect() -> execute() -> commit() for single operations.
|
|
100
|
+
Can be used in transaction using `transaction()` manager.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
query (str): SQL query to execute on SQLite3 DB
|
|
104
|
+
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
105
|
+
"""
|
|
106
|
+
return self._execute(query, args, method="execute")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def executemany(self, query : str, args : Sequence[SqlRow]) -> None:
|
|
110
|
+
"""
|
|
111
|
+
Shortcut to connect() -> executemany() -> commit() for single operations.
|
|
112
|
+
Can be used in transaction using `transaction()` manager.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
query (str): SQL query to execute on SQLite3 DB
|
|
116
|
+
args (list[tuple[SqlValue, ...]]): Arguments to the execution
|
|
117
|
+
"""
|
|
118
|
+
return self._execute(query, args, method="executemany")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def fetchone(self, query : str, *args : SqlValue) -> SqlRow:
|
|
122
|
+
"""
|
|
123
|
+
Fetch first row based on `query`
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
query (str): SQL query
|
|
127
|
+
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
row (SqlRow): Single row
|
|
131
|
+
"""
|
|
132
|
+
return self._fetch(query, args, method="fetchone")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def fetchmany(self, query : str, *args : SqlValue, size : int = 1) -> list[SqlRow]:
|
|
136
|
+
"""
|
|
137
|
+
Fetch first `size` rows based on `query`
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
query (str): SQL query
|
|
141
|
+
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
142
|
+
size (str): Number of rows to return
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
rows (list[SqlRow]): list of `size` rows
|
|
146
|
+
"""
|
|
147
|
+
logger.debug(f"{self.database}: {query} {args}")
|
|
148
|
+
|
|
149
|
+
if self.in_transaction():
|
|
150
|
+
self._trans_cursor.execute(query, args)
|
|
151
|
+
return self._trans_cursor.fetchmany(size)
|
|
152
|
+
|
|
153
|
+
with self.connect() as conn:
|
|
154
|
+
cursor = conn.cursor()
|
|
155
|
+
cursor.execute(query, args)
|
|
156
|
+
return cursor.fetchmany(size)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def fetchall(self, query : str, *args : SqlValue) -> list[SqlRow]:
|
|
160
|
+
"""
|
|
161
|
+
Fetch all rows based on `query`
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
query (str): SQL query
|
|
165
|
+
*args (tuple[SqlValue, ...]): Arguments to the execution
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
rows (list[SqlRow]): list of rows
|
|
169
|
+
"""
|
|
170
|
+
return self._fetch(query, args, method="fetchall")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def in_transaction(self) -> bool:
|
|
174
|
+
""" Returns True if instance is in transaction """
|
|
175
|
+
return hasattr(self, "_trans") and hasattr(self, "_trans_cursor")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def connect(self) -> sqlite3.Connection:
|
|
179
|
+
""" Shortcut to sqlite3 connection context manager """
|
|
180
|
+
return sqlite3.connect(self.database, **self.connection_params)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def open(self) -> None:
|
|
184
|
+
""" Opens unmanaged transaction """
|
|
185
|
+
if self.in_transaction():
|
|
186
|
+
raise NestedTransactionError("Can't re-open existing connection")
|
|
187
|
+
|
|
188
|
+
self._trans = self.connect()
|
|
189
|
+
self._trans_cursor = self._trans.cursor()
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def close(self) -> None:
|
|
193
|
+
""" Closes unmanaged transaction """
|
|
194
|
+
if not self.in_transaction():
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
if self._is_managed_transaction:
|
|
198
|
+
raise TransactionError("Can't manually close managed transaction")
|
|
199
|
+
|
|
200
|
+
self._trans_cursor.close()
|
|
201
|
+
self._trans.close()
|
|
202
|
+
del(self._trans_cursor)
|
|
203
|
+
del(self._trans)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def commit(self) -> None:
|
|
207
|
+
if not self.in_transaction():
|
|
208
|
+
raise OutsideTransactionError("Can't commit outside transaction mode")
|
|
209
|
+
|
|
210
|
+
self._trans.commit()
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def rollback(self) -> None:
|
|
214
|
+
if not self.in_transaction():
|
|
215
|
+
raise OutsideTransactionError("Can't rollback outside transaction mode")
|
|
216
|
+
|
|
217
|
+
self._trans.rollback()
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@contextmanager
|
|
221
|
+
def transaction(self, autocommit : bool = True):
|
|
222
|
+
"""
|
|
223
|
+
Creates context manager to use class methods in transaction
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
autocommit (bool): If `True`, will commit changes at the end of transaction
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
self.open()
|
|
230
|
+
self._is_managed_transaction = True
|
|
231
|
+
logger.debug(f"{self.database}: Transaction started")
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
yield
|
|
235
|
+
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"{self.database}: Error while in transaction: {e}")
|
|
238
|
+
logger.debug(e, exc_info=True)
|
|
239
|
+
self._trans.rollback()
|
|
240
|
+
raise e
|
|
241
|
+
|
|
242
|
+
else:
|
|
243
|
+
if autocommit:
|
|
244
|
+
self._trans.commit()
|
|
245
|
+
|
|
246
|
+
finally:
|
|
247
|
+
self._is_managed_transaction = False
|
|
248
|
+
self.close()
|
|
249
|
+
logger.debug(f"{self.database}: Transaction finished")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def tx_conn(self) -> sqlite3.Connection:
|
|
254
|
+
""" Gives access to connection while in transaction """
|
|
255
|
+
if not self.in_transaction():
|
|
256
|
+
raise OutsideTransactionError("`tx_conn` is not available outside the transaction mode")
|
|
257
|
+
return self._trans
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def tx_cursor(self) -> sqlite3.Cursor:
|
|
262
|
+
""" Gives access to connection cursor while in transaction """
|
|
263
|
+
if not self.in_transaction():
|
|
264
|
+
raise OutsideTransactionError("`tx_cursor` is not available outside the transaction mode")
|
|
265
|
+
return self._trans_cursor
|
|
266
|
+
|
|
@@ -1,12 +1,5 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
1
|
from html import escape
|
|
3
|
-
from typing import Sequence
|
|
4
|
-
|
|
5
|
-
import csv
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from .statements import Select, Where
|
|
9
|
-
from ..sqltable import SqlTableMixin
|
|
2
|
+
from typing import Sequence
|
|
10
3
|
|
|
11
4
|
from .types import SqlRow
|
|
12
5
|
|
|
@@ -57,25 +50,3 @@ def to_html(tablename : str, columns : list[str], repr_rows : Sequence[SqlRow],
|
|
|
57
50
|
|
|
58
51
|
return "".join(html)
|
|
59
52
|
|
|
60
|
-
|
|
61
|
-
def to_csv(builder : Select | Where[Select] | SqlTableMixin, path : str) -> None:
|
|
62
|
-
|
|
63
|
-
from ..sqltable import SqlTableMixin
|
|
64
|
-
from .statements import Where
|
|
65
|
-
|
|
66
|
-
match builder:
|
|
67
|
-
case Where():
|
|
68
|
-
builder = builder.then
|
|
69
|
-
case SqlTableMixin():
|
|
70
|
-
builder = builder.select
|
|
71
|
-
|
|
72
|
-
if builder._aggregate:
|
|
73
|
-
raise AssertionError("Aggregated queries are not supported")
|
|
74
|
-
|
|
75
|
-
columns = builder._table.columns if len(builder._columns) == 0 or "*" in builder._columns else builder._columns
|
|
76
|
-
repr_rows = builder.fetchall()
|
|
77
|
-
|
|
78
|
-
with open(path, 'w', newline='') as file:
|
|
79
|
-
writer = csv.writer(file)
|
|
80
|
-
writer.writerow(columns)
|
|
81
|
-
writer.writerows(repr_rows)
|
|
@@ -4,9 +4,7 @@ from typing import Sequence
|
|
|
4
4
|
def format_list(items : Sequence | set, brackets : bool = True) -> str:
|
|
5
5
|
""" Formats list into `(item1, item2, ...)` format """
|
|
6
6
|
items_str = ', '.join([str(i) for i in items])
|
|
7
|
-
if
|
|
8
|
-
return items_str
|
|
9
|
-
return f'({items_str})'
|
|
7
|
+
return f'({items_str})' if brackets else items_str
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
def create_table(
|
|
@@ -40,7 +38,7 @@ def values_placeholder(n_values : int) -> str:
|
|
|
40
38
|
def bulk_placeholder(n_values : int, n_rows : int) -> str:
|
|
41
39
|
""" Creates placeholders `(?, ?, ..), (?, ?, ..), ...` for each row """
|
|
42
40
|
place_holder = values_placeholder(n_values)
|
|
43
|
-
return
|
|
41
|
+
return format_list([place_holder]*n_rows, False)
|
|
44
42
|
|
|
45
43
|
|
|
46
44
|
def insert(tablename : str, columns : list[str], values : str) -> str:
|
|
@@ -1,20 +1,17 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from abc import ABC, abstractmethod
|
|
4
|
-
|
|
5
|
-
if TYPE_CHECKING:
|
|
6
|
-
from .statements import Statement
|
|
7
|
-
from ..sqltable import SqlTableMixin
|
|
1
|
+
from typing import Sequence, Literal, Generator, Self
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
8
3
|
|
|
9
4
|
from . import sqlgen as sql
|
|
10
|
-
from .
|
|
5
|
+
from .connection_manager import ConnectionManager
|
|
6
|
+
|
|
7
|
+
from .types import SqlValue, SqlRow, Schema
|
|
11
8
|
from .repr import to_html
|
|
9
|
+
from ..exceptions import SqlEngineError, OutsideTransactionError
|
|
12
10
|
|
|
13
11
|
|
|
14
|
-
class Where[T : Statement]:
|
|
12
|
+
class Where[T : "Statement"]:
|
|
15
13
|
""" Where clause build helper """
|
|
16
14
|
|
|
17
|
-
|
|
18
15
|
def __init__(self, statement : T):
|
|
19
16
|
|
|
20
17
|
self._statement = statement
|
|
@@ -146,12 +143,11 @@ class Statement(ABC):
|
|
|
146
143
|
Statement object that helps you build queries and execute them
|
|
147
144
|
"""
|
|
148
145
|
|
|
149
|
-
|
|
146
|
+
def __init__(self, connection : ConnectionManager, tableschema : Schema) -> None:
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
self.
|
|
154
|
-
self._where: Where[Self] = Where(self)
|
|
148
|
+
self._tableschema = tableschema
|
|
149
|
+
self._connection = connection
|
|
150
|
+
self._where = Where(self)
|
|
155
151
|
|
|
156
152
|
self._custom_query : str | None = None
|
|
157
153
|
self._custom_args : tuple[SqlValue, ...] = ()
|
|
@@ -175,7 +171,7 @@ class Statement(ABC):
|
|
|
175
171
|
|
|
176
172
|
|
|
177
173
|
def reset(self) -> None:
|
|
178
|
-
"""
|
|
174
|
+
""" Reset statement to reuse the object """
|
|
179
175
|
self._where.reset()
|
|
180
176
|
self._custom_query = None
|
|
181
177
|
self._custom_args = ()
|
|
@@ -209,18 +205,17 @@ class Statement(ABC):
|
|
|
209
205
|
|
|
210
206
|
|
|
211
207
|
class MutationalStatement(Statement, ABC):
|
|
212
|
-
|
|
213
208
|
|
|
214
209
|
def execute(self) -> None:
|
|
210
|
+
""" Execute built statement """
|
|
215
211
|
query, args = self.build()
|
|
216
|
-
self.
|
|
212
|
+
self._connection.execute(query, *args)
|
|
217
213
|
|
|
218
214
|
|
|
219
215
|
class Select(Statement):
|
|
220
216
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
super().__init__(table)
|
|
217
|
+
def __init__(self, connection : ConnectionManager, tableschema : Schema) -> None:
|
|
218
|
+
super().__init__(connection, tableschema)
|
|
224
219
|
|
|
225
220
|
self._columns : list[str] = []
|
|
226
221
|
self._order_by : list[str] = []
|
|
@@ -229,48 +224,54 @@ class Select(Statement):
|
|
|
229
224
|
|
|
230
225
|
|
|
231
226
|
def __call__(self, *columns : str) -> Self:
|
|
227
|
+
""" Shortcut to columns selector """
|
|
232
228
|
return self.columns(*columns)
|
|
233
229
|
|
|
234
230
|
|
|
235
231
|
def columns(self, *columns : str) -> Self:
|
|
236
|
-
"""
|
|
232
|
+
""" Columns selector """
|
|
237
233
|
self._columns.extend(columns)
|
|
238
234
|
return self
|
|
239
235
|
|
|
240
236
|
|
|
241
237
|
def aggregate(self, by : Literal['COUNT', 'SUM', 'AVG', 'MIN', 'MAX']) -> Self:
|
|
242
|
-
|
|
238
|
+
""" Aggregate by provided method """
|
|
243
239
|
if self._aggregate:
|
|
244
|
-
raise
|
|
240
|
+
raise SqlEngineError("Can't aggregate columns multiple times")
|
|
245
241
|
|
|
246
242
|
self._aggregate = by
|
|
247
243
|
return self
|
|
248
244
|
|
|
249
245
|
|
|
250
246
|
def order_by(self, column : str, ascending : bool = True) -> Self:
|
|
247
|
+
""" Orders returned rows by provided column """
|
|
251
248
|
order = "ASC" if ascending else "DESC"
|
|
252
249
|
self._order_by.append(f"{column} {order}")
|
|
253
250
|
return self
|
|
254
251
|
|
|
255
252
|
|
|
256
253
|
def limit(self, n : int) -> Self:
|
|
254
|
+
""" Limit number of returned rows """
|
|
257
255
|
self._limit = n
|
|
258
256
|
return self
|
|
259
257
|
|
|
260
258
|
|
|
261
259
|
def fetchone(self) -> SqlRow:
|
|
260
|
+
""" Fetch first row """
|
|
262
261
|
query, args = self.build()
|
|
263
|
-
return self.
|
|
262
|
+
return self._connection.fetchone(query, *args)
|
|
264
263
|
|
|
265
264
|
|
|
266
265
|
def fetchmany(self, size : int = 1) -> list[SqlRow]:
|
|
266
|
+
""" Fetch first `size` rows """
|
|
267
267
|
query, args = self.build()
|
|
268
|
-
return self.
|
|
268
|
+
return self._connection.fetchmany(query, *args, size=size)
|
|
269
269
|
|
|
270
270
|
|
|
271
271
|
def fetchall(self) -> list[SqlRow]:
|
|
272
|
+
""" Fetch all rows """
|
|
272
273
|
query, args = self.build()
|
|
273
|
-
return self.
|
|
274
|
+
return self._connection.fetchall(query, *args)
|
|
274
275
|
|
|
275
276
|
|
|
276
277
|
def fetchmany_iterator(self, batch_size: int) -> Generator[list[SqlRow], None, None]:
|
|
@@ -282,17 +283,19 @@ class Select(Statement):
|
|
|
282
283
|
|
|
283
284
|
Examples:
|
|
284
285
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
286
|
+
```python
|
|
287
|
+
with table.transaction():
|
|
288
|
+
for batch in table.select.where.gt("Age", 30).then.fetchmany_iterator(1000):
|
|
289
|
+
process_batch(batch)
|
|
290
|
+
```
|
|
288
291
|
"""
|
|
289
|
-
if not self.
|
|
290
|
-
raise
|
|
291
|
-
to keep open the transaction of the table
|
|
292
|
+
if not self._connection.in_transaction():
|
|
293
|
+
raise OutsideTransactionError("To use the `fetchall_iterator()` method you have \
|
|
294
|
+
to keep open the transaction of the table")
|
|
292
295
|
|
|
293
296
|
query, exec_args = self.build()
|
|
294
297
|
|
|
295
|
-
iter_cursor = self.
|
|
298
|
+
iter_cursor = self._connection.tx_conn.cursor()
|
|
296
299
|
iter_cursor.execute(query, exec_args)
|
|
297
300
|
|
|
298
301
|
while batch := iter_cursor.fetchmany(batch_size):
|
|
@@ -300,15 +303,26 @@ class Select(Statement):
|
|
|
300
303
|
|
|
301
304
|
|
|
302
305
|
def __iter__(self) -> Generator[SqlRow, None, None]:
|
|
303
|
-
"""
|
|
306
|
+
"""
|
|
307
|
+
Select statement rows iterator
|
|
308
|
+
|
|
309
|
+
Examples:
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
with table.transaction():
|
|
313
|
+
# here `then` is used to link back to the `select` instance from `where` object
|
|
314
|
+
for row in table.select.where.gt("Age", 30).then:
|
|
315
|
+
process_row(row)
|
|
316
|
+
```
|
|
317
|
+
"""
|
|
304
318
|
|
|
305
|
-
if not self.
|
|
306
|
-
raise
|
|
307
|
-
to keep open the transaction of the table
|
|
319
|
+
if not self._connection.in_transaction():
|
|
320
|
+
raise OutsideTransactionError("To use the __iter__ method you have \
|
|
321
|
+
to keep open the transaction of the table")
|
|
308
322
|
|
|
309
323
|
query, exec_args = self.build()
|
|
310
324
|
|
|
311
|
-
iter_cursor = self.
|
|
325
|
+
iter_cursor = self._connection.tx_conn.cursor()
|
|
312
326
|
iter_cursor.execute(query, exec_args)
|
|
313
327
|
|
|
314
328
|
while row := iter_cursor.fetchone():
|
|
@@ -330,7 +344,7 @@ class Select(Statement):
|
|
|
330
344
|
else:
|
|
331
345
|
limit = None
|
|
332
346
|
|
|
333
|
-
query = sql.select(self.
|
|
347
|
+
query = sql.select(self._tableschema["tablename"], columns, where_clause, order, limit)
|
|
334
348
|
|
|
335
349
|
return query, args
|
|
336
350
|
|
|
@@ -342,43 +356,51 @@ class Select(Statement):
|
|
|
342
356
|
self._limit = None
|
|
343
357
|
|
|
344
358
|
|
|
359
|
+
def _resolve_columns(self) -> list[str]:
|
|
360
|
+
return (
|
|
361
|
+
self._tableschema["columns"]
|
|
362
|
+
if len(self._columns) == 0 or "*" in self._columns
|
|
363
|
+
else self._columns
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
|
|
345
367
|
def _repr_html_(self) -> str | None:
|
|
346
368
|
|
|
347
369
|
if self._aggregate:
|
|
348
370
|
return None
|
|
349
371
|
|
|
350
372
|
limit = 26
|
|
351
|
-
columns = self.
|
|
373
|
+
columns = self._resolve_columns()
|
|
352
374
|
repr_rows = self.fetchmany(limit)
|
|
353
375
|
|
|
354
|
-
return to_html(self.
|
|
376
|
+
return to_html(self._tableschema["tablename"], columns, repr_rows, limit=limit-1)
|
|
355
377
|
|
|
356
378
|
|
|
357
379
|
class Delete(MutationalStatement):
|
|
358
380
|
|
|
359
|
-
|
|
360
381
|
def _build(self, where_clause : str, *args : SqlValue) -> tuple[str, tuple[SqlValue, ...]]:
|
|
361
382
|
|
|
362
383
|
if not where_clause:
|
|
363
|
-
raise
|
|
384
|
+
raise SqlEngineError("Delete statement must have a where clause")
|
|
364
385
|
|
|
365
|
-
query = sql.delete_rows(self.
|
|
386
|
+
query = sql.delete_rows(self._tableschema["tablename"], where_clause)
|
|
366
387
|
return query, args
|
|
367
388
|
|
|
389
|
+
|
|
368
390
|
def _reset(self) -> None:
|
|
369
391
|
pass
|
|
370
392
|
|
|
371
393
|
|
|
372
394
|
class Update(MutationalStatement):
|
|
373
395
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
super().__init__(table)
|
|
396
|
+
def __init__(self, connection : ConnectionManager, tableschema : Schema) -> None:
|
|
397
|
+
super().__init__(connection, tableschema)
|
|
377
398
|
self._set_clauses : list[str] = []
|
|
378
399
|
self._set_args : list[SqlValue] = []
|
|
379
400
|
|
|
380
401
|
|
|
381
402
|
def __call__(self, column : str, value : SqlValue) -> Self:
|
|
403
|
+
""" Shortcut to set value to a column """
|
|
382
404
|
return self.set(column, value)
|
|
383
405
|
|
|
384
406
|
|
|
@@ -391,7 +413,7 @@ class Update(MutationalStatement):
|
|
|
391
413
|
|
|
392
414
|
def _build(self, where_clause : str, *args : SqlValue) -> tuple[str, tuple[SqlValue, ...]]:
|
|
393
415
|
set_clause = sql.format_list(self._set_clauses, brackets=False)
|
|
394
|
-
query = f"UPDATE {self.
|
|
416
|
+
query = f"UPDATE {self._tableschema["tablename"]} SET {set_clause} WHERE {where_clause};"
|
|
395
417
|
return query, (*self._set_args, *args)
|
|
396
418
|
|
|
397
419
|
|