reydb 1.1.47__py3-none-any.whl → 1.1.49__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.
- reydb/rbase.py +101 -2
- reydb/rbuild.py +17 -21
- reydb/rconfig.py +9 -14
- reydb/rconn.py +23 -44
- reydb/rdb.py +60 -1422
- reydb/rerror.py +1 -6
- reydb/rexec.py +820 -183
- reydb/rfile.py +4 -9
- reydb/rinfo.py +37 -95
- {reydb-1.1.47.dist-info → reydb-1.1.49.dist-info}/METADATA +1 -1
- reydb-1.1.49.dist-info/RECORD +16 -0
- reydb-1.1.47.dist-info/RECORD +0 -16
- {reydb-1.1.47.dist-info → reydb-1.1.49.dist-info}/WHEEL +0 -0
- {reydb-1.1.47.dist-info → reydb-1.1.49.dist-info}/licenses/LICENSE +0 -0
reydb/rexec.py
CHANGED
@@ -2,336 +2,973 @@
|
|
2
2
|
# -*- coding: utf-8 -*-
|
3
3
|
|
4
4
|
"""
|
5
|
-
@Time :
|
5
|
+
@Time : 2025-09-22 22:12:33
|
6
6
|
@Author : Rey
|
7
7
|
@Contact : reyxbo@163.com
|
8
|
-
@Explain :
|
8
|
+
@Explain : Execute methods.
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from typing import Any,
|
13
|
-
from
|
14
|
-
from
|
12
|
+
from typing import Any, Literal
|
13
|
+
from collections.abc import Iterable, Generator, Container
|
14
|
+
from enum import EnumType
|
15
|
+
from sqlalchemy import text as sqlalchemy_text
|
16
|
+
from sqlalchemy.sql.elements import TextClause
|
17
|
+
from reykit.rbase import throw, get_first_notnone
|
18
|
+
from reykit.rdata import FunctionGenerator, to_json
|
19
|
+
from reykit.rmonkey import monkey_sqlalchemy_result_more_fetch, monkey_sqlalchemy_row_index_field
|
20
|
+
from reykit.rre import findall
|
21
|
+
from reykit.rstdout import echo
|
22
|
+
from reykit.rtable import TableData, Table
|
23
|
+
from reykit.rwrap import wrap_runtime
|
15
24
|
|
16
25
|
from .rbase import DatabaseBase
|
17
26
|
from .rconn import DatabaseConnection
|
18
|
-
from .rdb import Database, Result
|
19
27
|
|
20
28
|
|
21
29
|
__all__ = (
|
22
|
-
'
|
30
|
+
'Result',
|
31
|
+
'DatabaseExecute'
|
23
32
|
)
|
24
33
|
|
25
34
|
|
35
|
+
# Monkey path.
|
36
|
+
Result_ = monkey_sqlalchemy_result_more_fetch()
|
37
|
+
Result = Result_
|
38
|
+
monkey_sqlalchemy_row_index_field()
|
39
|
+
|
40
|
+
|
26
41
|
class DatabaseExecute(DatabaseBase):
|
27
42
|
"""
|
28
43
|
Database execute type.
|
29
|
-
|
30
|
-
Examples
|
31
|
-
--------
|
32
|
-
Select.
|
33
|
-
>>> field = ['id', 'value']
|
34
|
-
>>> where = '`id` = ids'
|
35
|
-
>>> ids = (1, 2)
|
36
|
-
>>> result = DatabaseExecute.database.table(field, where, ids=ids)
|
37
|
-
|
38
|
-
Insert.
|
39
|
-
>>> data = [{'id': 1}, {'id': 2}]
|
40
|
-
>>> duplicate = 'ignore'
|
41
|
-
>>> result = DatabaseExecute.database.table + data
|
42
|
-
>>> result = DatabaseExecute.database.table + (data, duplicate)
|
43
|
-
>>> result = DatabaseExecute.database.table + {'data': data, 'duplicate': duplicate}
|
44
|
-
|
45
|
-
Update.
|
46
|
-
>>> data = [{'name': 'a', 'id': 1}, {'name': 'b', 'id': 2}]
|
47
|
-
>>> where_fields = 'id'
|
48
|
-
>>> result = DatabaseExecute.database.table & data
|
49
|
-
>>> result = DatabaseExecute.database.table & (data, where_fields)
|
50
|
-
>>> result = DatabaseExecute.database.table & {'data': data, 'where_fields': where_fields}
|
51
|
-
|
52
|
-
Delete.
|
53
|
-
>>> where = '`id` IN (1, 2)'
|
54
|
-
>>> report = True
|
55
|
-
>>> result = DatabaseExecute.database.table - where
|
56
|
-
>>> result = DatabaseExecute.database.table - (where, report)
|
57
|
-
>>> result = DatabaseExecute.database.table - {'where': where, 'report': report}
|
58
|
-
|
59
|
-
Copy.
|
60
|
-
>>> where = '`id` IN (1, 2)'
|
61
|
-
>>> limit = 1
|
62
|
-
>>> result = DatabaseExecute.database.table * where
|
63
|
-
>>> result = DatabaseExecute.database.table * (where, limit)
|
64
|
-
>>> result = DatabaseExecute.database.table * {'where': where, 'limit': limit}
|
65
|
-
|
66
|
-
Exist.
|
67
|
-
>>> where = '`id` IN (1, 2)'
|
68
|
-
>>> report = True
|
69
|
-
>>> result = where in DatabaseExecute.database.table
|
70
|
-
>>> result = (where, report) in DatabaseExecute.database.table
|
71
|
-
>>> result = {'where': where, 'report': report} in DatabaseExecute.database.table
|
72
|
-
|
73
|
-
Count.
|
74
|
-
>>> result = len(DatabaseExecute.database.table)
|
75
|
-
|
76
|
-
Default database.
|
77
|
-
>>> engine = Database(**server, database)
|
78
|
-
>>> result = engine.exe.table()
|
79
44
|
"""
|
80
45
|
|
81
46
|
|
82
|
-
def __init__(self,
|
47
|
+
def __init__(self, dbconn: DatabaseConnection) -> None:
|
83
48
|
"""
|
84
49
|
Build instance attributes.
|
85
50
|
|
86
51
|
Parameters
|
87
52
|
----------
|
88
|
-
|
53
|
+
dbconn : DatabaseConnection instance.
|
89
54
|
"""
|
90
55
|
|
91
|
-
#
|
92
|
-
self.
|
93
|
-
self._path: list[str] = []
|
56
|
+
# Build.
|
57
|
+
self.dbconn = dbconn
|
94
58
|
|
95
59
|
|
96
|
-
def
|
60
|
+
def execute(
|
61
|
+
self,
|
62
|
+
sql: str | TextClause,
|
63
|
+
data: TableData | None = None,
|
64
|
+
report: bool | None = None,
|
65
|
+
**kwdata: Any
|
66
|
+
) -> Result:
|
97
67
|
"""
|
98
|
-
|
68
|
+
Execute SQL.
|
99
69
|
|
100
70
|
Parameters
|
101
71
|
----------
|
102
|
-
|
72
|
+
sql : SQL in method `sqlalchemy.text` format, or `TextClause` object.
|
73
|
+
data : Data set for filling.
|
74
|
+
report : Whether report SQL execute information.
|
75
|
+
- `None`: Use attribute `default_report`.
|
76
|
+
- `bool`: Use this value.
|
77
|
+
kwdata : Keyword parameters for filling.
|
103
78
|
|
104
79
|
Returns
|
105
80
|
-------
|
106
|
-
|
81
|
+
Result object.
|
107
82
|
"""
|
108
83
|
|
109
|
-
#
|
110
|
-
|
111
|
-
|
84
|
+
# Handle parameter.
|
85
|
+
report = get_first_notnone(report, self.dbconn.db.default_report)
|
86
|
+
sql = self.handle_sql(sql)
|
87
|
+
if data is None:
|
88
|
+
if kwdata == {}:
|
89
|
+
data = []
|
90
|
+
else:
|
91
|
+
data = [kwdata]
|
92
|
+
else:
|
93
|
+
data_table = Table(data)
|
94
|
+
data = data_table.to_table()
|
95
|
+
for row in data:
|
96
|
+
row.update(kwdata)
|
97
|
+
data = self.handle_data(data, sql)
|
98
|
+
|
99
|
+
# Execute.
|
100
|
+
|
101
|
+
## Report.
|
102
|
+
if report:
|
103
|
+
execute = wrap_runtime(self.conn.execute, to_return=True)
|
104
|
+
result, report_runtime, *_ = execute(sql, data)
|
105
|
+
report_info = (
|
106
|
+
f'{report_runtime}\n'
|
107
|
+
f'Row Count: {result.rowcount}'
|
108
|
+
)
|
109
|
+
sqls = [
|
110
|
+
sql_part.strip()
|
111
|
+
for sql_part in sql.text.split(';')
|
112
|
+
if sql_part != ''
|
113
|
+
]
|
114
|
+
if data == []:
|
115
|
+
echo(report_info, *sqls, title='SQL')
|
116
|
+
else:
|
117
|
+
echo(report_info, *sqls, data, title='SQL')
|
118
|
+
|
119
|
+
## Not report.
|
120
|
+
else:
|
121
|
+
result = self.dbconn.conn.execute(sql, data)
|
122
|
+
|
123
|
+
# Automatic commit.
|
124
|
+
if self.dbconn.autocommit:
|
125
|
+
self.dbconn.conn.commit()
|
112
126
|
|
113
|
-
|
114
|
-
self._path.append(name)
|
115
|
-
|
116
|
-
return self
|
117
|
-
|
118
|
-
|
119
|
-
@property
|
120
|
-
def __get_path(self) -> tuple[str, str]:
|
121
|
-
"""
|
122
|
-
Get database name and table name.
|
123
|
-
|
124
|
-
Returns
|
125
|
-
-------
|
126
|
-
Database name and table name.
|
127
|
-
"""
|
127
|
+
return result
|
128
128
|
|
129
|
-
# Get.
|
130
|
-
path_len = len(self._path)
|
131
|
-
match path_len:
|
132
|
-
case 1:
|
133
|
-
database = self._rdatabase.database
|
134
|
-
table = self._path[0]
|
135
|
-
case 2:
|
136
|
-
database = self._path[0]
|
137
|
-
table = self._path[1]
|
138
|
-
case _:
|
139
|
-
throw(AssertionError, path_len)
|
140
129
|
|
141
|
-
|
130
|
+
__call__ = execute
|
142
131
|
|
143
132
|
|
144
|
-
def
|
133
|
+
def select(
|
145
134
|
self,
|
146
|
-
|
147
|
-
|
135
|
+
table: str,
|
136
|
+
fields: str | Iterable[str] | None = None,
|
137
|
+
where: str | None = None,
|
138
|
+
group: str | None = None,
|
139
|
+
having: str | None = None,
|
140
|
+
order: str | None = None,
|
141
|
+
limit: int | str | tuple[int, int] | None = None,
|
142
|
+
report: bool | None = None,
|
143
|
+
**kwdata: Any
|
148
144
|
) -> Result:
|
149
145
|
"""
|
150
|
-
|
146
|
+
Execute select SQL.
|
151
147
|
|
152
148
|
Parameters
|
153
149
|
----------
|
154
|
-
|
155
|
-
|
150
|
+
table : Table name.
|
151
|
+
fields : Select clause content.
|
152
|
+
- `None`: Is `SELECT *`.
|
153
|
+
- `str`: Join as `SELECT str`.
|
154
|
+
- `Iterable[str]`, Join as `SELECT ``str``: ...`.
|
155
|
+
`str and first character is ':'`: Use this syntax.
|
156
|
+
`str`: Use this field.
|
157
|
+
where : Clause `WHERE` content, join as `WHERE str`.
|
158
|
+
group : Clause `GROUP BY` content, join as `GROUP BY str`.
|
159
|
+
having : Clause `HAVING` content, join as `HAVING str`.
|
160
|
+
order : Clause `ORDER BY` content, join as `ORDER BY str`.
|
161
|
+
limit : Clause `LIMIT` content.
|
162
|
+
- `int | str`: Join as `LIMIT int/str`.
|
163
|
+
- `tuple[int, int]`: Join as `LIMIT int, int`.
|
164
|
+
report : Whether report SQL execute information.
|
165
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
166
|
+
- `int`: Use this value.
|
167
|
+
kwdata : Keyword parameters for filling.
|
156
168
|
|
157
169
|
Returns
|
158
170
|
-------
|
159
171
|
Result object.
|
172
|
+
|
173
|
+
Examples
|
174
|
+
--------
|
175
|
+
Parameter `fields`.
|
176
|
+
>>> fields = ['id', ':`id` + 1 AS `id_`']
|
177
|
+
>>> result = Database.execute.select('table', fields)
|
178
|
+
>>> print(result.to_table())
|
179
|
+
[{'id': 1, 'id_': 2}, ...]
|
180
|
+
|
181
|
+
Parameter `kwdata`.
|
182
|
+
>>> fields = '`id`, `id` + :value AS `id_`'
|
183
|
+
>>> result = Database.execute.select('table', fields, value=1)
|
184
|
+
>>> print(result.to_table())
|
185
|
+
[{'id': 1, 'id_': 2}, ...]
|
160
186
|
"""
|
161
187
|
|
162
|
-
#
|
163
|
-
|
188
|
+
# Generate SQL.
|
189
|
+
sql_list = []
|
190
|
+
|
191
|
+
## Part 'SELECT' syntax.
|
192
|
+
if fields is None:
|
193
|
+
fields = '*'
|
194
|
+
elif type(fields) != str:
|
195
|
+
fields = ', '.join(
|
196
|
+
[
|
197
|
+
field[1:]
|
198
|
+
if (
|
199
|
+
field.startswith(':')
|
200
|
+
and field != ':'
|
201
|
+
)
|
202
|
+
else f'`{field}`'
|
203
|
+
for field in fields
|
204
|
+
]
|
205
|
+
)
|
206
|
+
sql_select = f'SELECT {fields}'
|
207
|
+
sql_list.append(sql_select)
|
208
|
+
|
209
|
+
## Part 'FROM' syntax.
|
210
|
+
sql_from = f'FROM `{self.dbconn.db.database}`.`{table}`'
|
211
|
+
sql_list.append(sql_from)
|
212
|
+
|
213
|
+
## Part 'WHERE' syntax.
|
214
|
+
if where is not None:
|
215
|
+
sql_where = f'WHERE {where}'
|
216
|
+
sql_list.append(sql_where)
|
217
|
+
|
218
|
+
## Part 'GROUP BY' syntax.
|
219
|
+
if group is not None:
|
220
|
+
sql_group = f'GROUP BY {group}'
|
221
|
+
sql_list.append(sql_group)
|
222
|
+
|
223
|
+
## Part 'GROUP BY' syntax.
|
224
|
+
if having is not None:
|
225
|
+
sql_having = f'HAVING {having}'
|
226
|
+
sql_list.append(sql_having)
|
227
|
+
|
228
|
+
## Part 'ORDER BY' syntax.
|
229
|
+
if order is not None:
|
230
|
+
sql_order = f'ORDER BY {order}'
|
231
|
+
sql_list.append(sql_order)
|
232
|
+
|
233
|
+
## Part 'LIMIT' syntax.
|
234
|
+
if limit is not None:
|
235
|
+
if type(limit) in (str, int):
|
236
|
+
sql_limit = f'LIMIT {limit}'
|
237
|
+
else:
|
238
|
+
if len(limit) == 2:
|
239
|
+
sql_limit = f'LIMIT {limit[0]}, {limit[1]}'
|
240
|
+
else:
|
241
|
+
throw(ValueError, limit)
|
242
|
+
sql_list.append(sql_limit)
|
243
|
+
|
244
|
+
## Join sql part.
|
245
|
+
sql = '\n'.join(sql_list)
|
246
|
+
|
247
|
+
# Execute SQL.
|
248
|
+
result = self.execute(sql, report=report, **kwdata)
|
164
249
|
|
165
250
|
return result
|
166
251
|
|
167
252
|
|
168
|
-
def
|
253
|
+
def insert(
|
169
254
|
self,
|
170
|
-
|
255
|
+
table: str,
|
256
|
+
data: TableData,
|
257
|
+
duplicate: Literal['ignore', 'update'] | Container[str] | None = None,
|
258
|
+
report: bool | None = None,
|
259
|
+
**kwdata: Any
|
171
260
|
) -> Result:
|
172
261
|
"""
|
173
262
|
Insert the data of table in the datebase.
|
174
263
|
|
175
264
|
Parameters
|
176
265
|
----------
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
- `
|
266
|
+
table : Table name.
|
267
|
+
data : Insert data.
|
268
|
+
duplicate : Handle method when constraint error.
|
269
|
+
- `None`: Not handled.
|
270
|
+
- `ignore`: Use `UPDATE IGNORE INTO` clause.
|
271
|
+
- `update`: Use `ON DUPLICATE KEY UPDATE` clause and update all fields.
|
272
|
+
- `Container[str]`: Use `ON DUPLICATE KEY UPDATE` clause and update this fields.
|
273
|
+
report : Whether report SQL execute information.
|
274
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
275
|
+
- `int`: Use this value.
|
276
|
+
kwdata : Keyword parameters for filling.
|
277
|
+
- `str and first character is ':'`: Use this syntax.
|
278
|
+
- `Any`: Use this value.
|
181
279
|
|
182
280
|
Returns
|
183
281
|
-------
|
184
282
|
Result object.
|
283
|
+
|
284
|
+
Examples
|
285
|
+
--------
|
286
|
+
Parameter `data` and `kwdata`.
|
287
|
+
>>> data = [{'key': 'a'}, {'key': 'b'}]
|
288
|
+
>>> kwdata = {'value1': 1, 'value2': ':(SELECT 2)'}
|
289
|
+
>>> result = Database.execute.insert('table', data, **kwdata)
|
290
|
+
>>> print(result.rowcount)
|
291
|
+
2
|
292
|
+
>>> result = Database.execute.select('table')
|
293
|
+
>>> print(result.to_table())
|
294
|
+
[{'key': 'a', 'value1': 1, 'value2': 2}, {'key': 'b', 'value1': 1, 'value2': 2}]
|
185
295
|
"""
|
186
296
|
|
187
|
-
#
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
297
|
+
# Handle parameter.
|
298
|
+
|
299
|
+
## Data.
|
300
|
+
data_table = Table(data)
|
301
|
+
data = data_table.to_table()
|
302
|
+
|
303
|
+
## Check.
|
304
|
+
if data in ([], [{}]):
|
305
|
+
throw(ValueError, data)
|
306
|
+
|
307
|
+
## Keyword data.
|
308
|
+
kwdata_method = {}
|
309
|
+
kwdata_replace = {}
|
310
|
+
for key, value in kwdata.items():
|
311
|
+
if (
|
312
|
+
type(value) == str
|
313
|
+
and value.startswith(':')
|
314
|
+
and value != ':'
|
315
|
+
):
|
316
|
+
kwdata_method[key] = value[1:]
|
317
|
+
else:
|
318
|
+
kwdata_replace[key] = value
|
319
|
+
|
320
|
+
# Generate SQL.
|
321
|
+
|
322
|
+
## Part 'fields' syntax.
|
323
|
+
fields_replace = {
|
324
|
+
field
|
325
|
+
for row in data
|
326
|
+
for field in row
|
327
|
+
}
|
328
|
+
fields_replace = {
|
329
|
+
field
|
330
|
+
for field in fields_replace
|
331
|
+
if field not in kwdata
|
332
|
+
}
|
333
|
+
sql_fields_list = (
|
334
|
+
*kwdata_method,
|
335
|
+
*kwdata_replace,
|
336
|
+
*fields_replace
|
337
|
+
)
|
338
|
+
sql_fields = ', '.join(
|
339
|
+
[
|
340
|
+
f'`{field}`'
|
341
|
+
for field in sql_fields_list
|
342
|
+
]
|
343
|
+
)
|
344
|
+
|
345
|
+
## Part 'values' syntax.
|
346
|
+
sql_values_list = (
|
347
|
+
*kwdata_method.values(),
|
348
|
+
*[
|
349
|
+
':' + field
|
350
|
+
for field in (
|
351
|
+
*kwdata_replace,
|
352
|
+
*fields_replace
|
353
|
+
)
|
354
|
+
]
|
355
|
+
)
|
356
|
+
sql_values = ', '.join(sql_values_list)
|
357
|
+
|
358
|
+
## Join sql part.
|
359
|
+
match duplicate:
|
360
|
+
|
361
|
+
### Not handle.
|
362
|
+
case None:
|
363
|
+
sql = (
|
364
|
+
f'INSERT INTO `{self.dbconn.db.database}`.`{table}`({sql_fields})\n'
|
365
|
+
f'VALUES({sql_values})'
|
366
|
+
)
|
367
|
+
|
368
|
+
### Ignore.
|
369
|
+
case 'ignore':
|
370
|
+
sql = (
|
371
|
+
f'INSERT IGNORE INTO `{self.dbconn.db.database}`.`{table}`({sql_fields})\n'
|
372
|
+
f'VALUES({sql_values})'
|
373
|
+
)
|
374
|
+
|
375
|
+
### Update.
|
193
376
|
case _:
|
194
|
-
|
377
|
+
sql_fields_list_update = sql_fields_list
|
378
|
+
if duplicate != 'update':
|
379
|
+
sql_fields_list_update = [
|
380
|
+
field
|
381
|
+
for field in sql_fields_list
|
382
|
+
if field in duplicate
|
383
|
+
]
|
384
|
+
update_content = ',\n '.join(
|
385
|
+
[
|
386
|
+
f'`{field}` = VALUES(`{field}`)'
|
387
|
+
for field in sql_fields_list_update
|
388
|
+
]
|
389
|
+
)
|
390
|
+
sql = (
|
391
|
+
f'INSERT INTO `{self.dbconn.db.database}`.`{table}`({sql_fields})\n'
|
392
|
+
f'VALUES({sql_values})\n'
|
393
|
+
'ON DUPLICATE KEY UPDATE\n'
|
394
|
+
f' {update_content}'
|
395
|
+
)
|
396
|
+
|
397
|
+
# Execute SQL.
|
398
|
+
result = self.execute(sql, data, report, **kwdata_replace)
|
195
399
|
|
196
400
|
return result
|
197
401
|
|
198
402
|
|
199
|
-
def
|
403
|
+
def update(
|
200
404
|
self,
|
201
|
-
|
405
|
+
table: str,
|
406
|
+
data: TableData,
|
407
|
+
where_fields: str | Iterable[str] | None = None,
|
408
|
+
report: bool | None = None,
|
409
|
+
**kwdata: Any
|
202
410
|
) -> Result:
|
203
411
|
"""
|
204
412
|
Update the data of table in the datebase.
|
205
413
|
|
206
414
|
Parameters
|
207
415
|
----------
|
208
|
-
|
209
|
-
|
210
|
-
- `
|
211
|
-
|
416
|
+
table : Table name.
|
417
|
+
data : Update data, clause `SET` and `WHERE` and `ORDER BY` and `LIMIT` content.
|
418
|
+
- `Key`: Table field.
|
419
|
+
`literal['order']`: Clause `ORDER BY` content, join as `ORDER BY str`.
|
420
|
+
`literal['limit']`: Clause `LIMIT` content, join as `LIMIT str`.
|
421
|
+
`Other`: Clause `SET` and `WHERE` content.
|
422
|
+
- `Value`: Table value.
|
423
|
+
`list | tuple`: Join as `field IN :str`.
|
424
|
+
`Any`: Join as `field = :str`.
|
425
|
+
where_fields : Clause `WHERE` content fields.
|
426
|
+
- `None`: The first key value pair of each item is judged.
|
427
|
+
- `str`: This key value pair of each item is judged.
|
428
|
+
- `Iterable[str]`: Multiple judged, `and`: relationship.
|
429
|
+
report : Whether report SQL execute information.
|
430
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
431
|
+
- `int`: Use this value.
|
432
|
+
kwdata : Keyword parameters for filling.
|
433
|
+
- `str and first character is ':'`: Use this syntax.
|
434
|
+
- `Any`: Use this value.
|
212
435
|
|
213
436
|
Returns
|
214
437
|
-------
|
215
438
|
Result object.
|
439
|
+
|
440
|
+
Examples
|
441
|
+
--------
|
442
|
+
Parameter `data` and `kwdata`.
|
443
|
+
>>> data = [{'key': 'a'}, {'key': 'b'}]
|
444
|
+
>>> kwdata = {'value': 1, 'name': ':`key`'}
|
445
|
+
>>> result = Database.execute.update('table', data, **kwdata)
|
446
|
+
>>> print(result.rowcount)
|
447
|
+
2
|
448
|
+
>>> result = Database.execute.select('table')
|
449
|
+
>>> print(result.to_table())
|
450
|
+
[{'key': 'a', 'value': 1, 'name': 'a'}, {'key': 'b', 'value': 1, 'name': 'b'}]
|
216
451
|
"""
|
217
452
|
|
218
|
-
#
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
453
|
+
# Handle parameter.
|
454
|
+
|
455
|
+
## Data.
|
456
|
+
data_table = Table(data)
|
457
|
+
data = data_table.to_table()
|
458
|
+
|
459
|
+
## Check.
|
460
|
+
if data in ([], [{}]):
|
461
|
+
throw(ValueError, data)
|
462
|
+
|
463
|
+
## Keyword data.
|
464
|
+
kwdata_method = {}
|
465
|
+
kwdata_replace = {}
|
466
|
+
for key, value in kwdata.items():
|
467
|
+
if (
|
468
|
+
type(value) == str
|
469
|
+
and value.startswith(':')
|
470
|
+
and value != ':'
|
471
|
+
):
|
472
|
+
kwdata_method[key] = value[1:]
|
473
|
+
else:
|
474
|
+
kwdata_replace[key] = value
|
475
|
+
sql_set_list_kwdata = [
|
476
|
+
f'`{key}` = {value}'
|
477
|
+
for key, value in kwdata_method.items()
|
478
|
+
]
|
479
|
+
sql_set_list_kwdata.extend(
|
480
|
+
[
|
481
|
+
f'`{key}` = :{key}'
|
482
|
+
for key in kwdata_replace
|
483
|
+
]
|
484
|
+
)
|
485
|
+
|
486
|
+
# Generate SQL.
|
487
|
+
data_flatten = kwdata_replace
|
488
|
+
if where_fields is None:
|
489
|
+
no_where = True
|
490
|
+
else:
|
491
|
+
no_where = False
|
492
|
+
if type(where_fields) == str:
|
493
|
+
where_fields = [where_fields]
|
494
|
+
sqls_list = []
|
495
|
+
sql_update = f'UPDATE `{self.dbconn.db.database}`.`{table}`'
|
496
|
+
for index, row in enumerate(data):
|
497
|
+
sql_parts = [sql_update]
|
498
|
+
for key, value in row.items():
|
499
|
+
if key in ('order', 'limit'):
|
500
|
+
continue
|
501
|
+
index_key = f'{index}_{key}'
|
502
|
+
data_flatten[index_key] = value
|
503
|
+
if no_where:
|
504
|
+
for key in row:
|
505
|
+
where_fields = [key]
|
506
|
+
break
|
507
|
+
|
508
|
+
## Part 'SET' syntax.
|
509
|
+
sql_set_list = sql_set_list_kwdata.copy()
|
510
|
+
sql_set_list.extend(
|
511
|
+
[
|
512
|
+
f'`{key}` = :{index}_{key}'
|
513
|
+
for key in row
|
514
|
+
if (
|
515
|
+
key not in where_fields
|
516
|
+
and key not in kwdata
|
517
|
+
and key not in ('order', 'limit')
|
518
|
+
)
|
519
|
+
]
|
520
|
+
)
|
521
|
+
sql_set = 'SET ' + ',\n '.join(sql_set_list)
|
522
|
+
sql_parts.append(sql_set)
|
523
|
+
|
524
|
+
## Part 'WHERE' syntax.
|
525
|
+
sql_where_list = []
|
526
|
+
for field in where_fields:
|
527
|
+
index_field = f'{index}_{field}'
|
528
|
+
index_value = data_flatten[index_field]
|
529
|
+
if type(index_value) in (list, tuple):
|
530
|
+
sql_where_part = f'`{field}` IN :{index_field}'
|
531
|
+
else:
|
532
|
+
sql_where_part = f'`{field}` = :{index_field}'
|
533
|
+
sql_where_list.append(sql_where_part)
|
534
|
+
sql_where = 'WHERE ' + '\n AND '.join(sql_where_list)
|
535
|
+
sql_parts.append(sql_where)
|
536
|
+
|
537
|
+
## Part 'ORDER BY' syntax.
|
538
|
+
order = row.get('order')
|
539
|
+
if order is not None:
|
540
|
+
sql_order = f'ORDER BY {order}'
|
541
|
+
sql_parts.append(sql_order)
|
542
|
+
|
543
|
+
## Part 'LIMIT' syntax.
|
544
|
+
limit = row.get('limit')
|
545
|
+
if limit is not None:
|
546
|
+
sql_limit = f'LIMIT {limit}'
|
547
|
+
sql_parts.append(sql_limit)
|
548
|
+
|
549
|
+
## Join sql part.
|
550
|
+
sql = '\n'.join(sql_parts)
|
551
|
+
sqls_list.append(sql)
|
552
|
+
|
553
|
+
## Join sqls.
|
554
|
+
sqls = ';\n'.join(sqls_list)
|
555
|
+
|
556
|
+
# Execute SQL.
|
557
|
+
result = self.execute(sqls, data_flatten, report)
|
226
558
|
|
227
559
|
return result
|
228
560
|
|
229
561
|
|
230
|
-
def
|
562
|
+
def delete(
|
231
563
|
self,
|
232
|
-
|
564
|
+
table: str,
|
565
|
+
where: str | None = None,
|
566
|
+
order: str | None = None,
|
567
|
+
limit: int | str | None = None,
|
568
|
+
report: bool | None = None,
|
569
|
+
**kwdata: Any
|
233
570
|
) -> Result:
|
234
571
|
"""
|
235
572
|
Delete the data of table in the datebase.
|
236
573
|
|
237
574
|
Parameters
|
238
575
|
----------
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
576
|
+
table : Table name.
|
577
|
+
where : Clause `WHERE` content, join as `WHERE str`.
|
578
|
+
order : Clause `ORDER BY` content, join as `ORDER BY str`.
|
579
|
+
limit : Clause `LIMIT` content, join as `LIMIT int/str`.
|
580
|
+
report : Whether report SQL execute information.
|
581
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
582
|
+
- `int`: Use this value.
|
583
|
+
kwdata : Keyword parameters for filling.
|
243
584
|
|
244
585
|
Returns
|
245
586
|
-------
|
246
587
|
Result object.
|
588
|
+
|
589
|
+
Examples
|
590
|
+
--------
|
591
|
+
Parameter `where` and `kwdata`.
|
592
|
+
>>> where = '`id` IN :ids'
|
593
|
+
>>> ids = (1, 2)
|
594
|
+
>>> result = Database.execute.delete('table', where, ids=ids)
|
595
|
+
>>> print(result.rowcount)
|
596
|
+
2
|
247
597
|
"""
|
248
598
|
|
249
|
-
#
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
599
|
+
# Generate SQL.
|
600
|
+
sqls = []
|
601
|
+
|
602
|
+
## Part 'DELETE' syntax.
|
603
|
+
sql_delete = f'DELETE FROM `{self.dbconn.db.database}`.`{table}`'
|
604
|
+
sqls.append(sql_delete)
|
605
|
+
|
606
|
+
## Part 'WHERE' syntax.
|
607
|
+
if where is not None:
|
608
|
+
sql_where = f'WHERE {where}'
|
609
|
+
sqls.append(sql_where)
|
610
|
+
|
611
|
+
## Part 'ORDER BY' syntax.
|
612
|
+
if order is not None:
|
613
|
+
sql_order = f'ORDER BY {order}'
|
614
|
+
sqls.append(sql_order)
|
615
|
+
|
616
|
+
## Part 'LIMIT' syntax.
|
617
|
+
if limit is not None:
|
618
|
+
sql_limit = f'LIMIT {limit}'
|
619
|
+
sqls.append(sql_limit)
|
620
|
+
|
621
|
+
## Join sqls.
|
622
|
+
sqls = '\n'.join(sqls)
|
623
|
+
|
624
|
+
# Execute SQL.
|
625
|
+
result = self.execute(sqls, report=report, **kwdata)
|
257
626
|
|
258
627
|
return result
|
259
628
|
|
260
629
|
|
261
|
-
def
|
630
|
+
def copy(
|
262
631
|
self,
|
263
|
-
|
632
|
+
table: str,
|
633
|
+
where: str | None = None,
|
634
|
+
limit: int | str | tuple[int, int] | None = None,
|
635
|
+
report: bool | None = None,
|
636
|
+
**kwdata: Any
|
264
637
|
) -> Result:
|
265
638
|
"""
|
266
639
|
Copy record of table in the datebase.
|
267
640
|
|
268
641
|
Parameters
|
269
642
|
----------
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
- `str`:
|
643
|
+
table : Table name.
|
644
|
+
where : Clause `WHERE` content, join as `WHERE str`.
|
645
|
+
limit : Clause `LIMIT` content.
|
646
|
+
- `int | str`: Join as `LIMIT int/str`.
|
647
|
+
- `tuple[int, int]`: Join as `LIMIT int, int`.
|
648
|
+
report : Whether report SQL execute information.
|
649
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
650
|
+
- `int`: Use this value.
|
651
|
+
kwdata : Keyword parameters for filling.
|
652
|
+
- `In 'WHERE' syntax`: Fill 'WHERE' syntax.
|
653
|
+
- `Not in 'WHERE' syntax`: Fill 'INSERT' and 'SELECT' syntax.
|
654
|
+
`str and first character is ':'`: Use this syntax.
|
655
|
+
`Any`: Use this value.
|
274
656
|
|
275
657
|
Returns
|
276
658
|
-------
|
277
659
|
Result object.
|
660
|
+
|
661
|
+
Examples
|
662
|
+
--------
|
663
|
+
Parameter `where` and `kwdata`.
|
664
|
+
>>> where = '`id` IN :ids'
|
665
|
+
>>> ids = (1, 2, 3)
|
666
|
+
>>> result = Database.execute.copy('table', where, 2, ids=ids, id=None, time=':NOW()')
|
667
|
+
>>> print(result.rowcount)
|
668
|
+
2
|
278
669
|
"""
|
279
670
|
|
280
|
-
#
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
671
|
+
# Handle parameter.
|
672
|
+
table_info: list[dict] = self.dbconn.db.info(self.dbconn.db.database)(table)()
|
673
|
+
field_key = 'COLUMN_NAME'
|
674
|
+
fields = [
|
675
|
+
row[field_key]
|
676
|
+
for row in table_info
|
677
|
+
]
|
678
|
+
pattern = '(?<!\\\\):(\\w+)'
|
679
|
+
if type(where) == str:
|
680
|
+
where_keys = findall(pattern, where)
|
681
|
+
else:
|
682
|
+
where_keys = ()
|
683
|
+
|
684
|
+
# Generate SQL.
|
685
|
+
sqls = []
|
686
|
+
|
687
|
+
## Part 'INSERT' syntax.
|
688
|
+
sql_fields = ', '.join(
|
689
|
+
f'`{field}`'
|
690
|
+
for field in fields
|
691
|
+
if field not in kwdata
|
692
|
+
)
|
693
|
+
if kwdata != {}:
|
694
|
+
sql_fields_kwdata = ', '.join(
|
695
|
+
f'`{field}`'
|
696
|
+
for field in kwdata
|
697
|
+
if field not in where_keys
|
698
|
+
)
|
699
|
+
sql_fields_filter = filter(
|
700
|
+
lambda sql: sql != '',
|
701
|
+
(
|
702
|
+
sql_fields,
|
703
|
+
sql_fields_kwdata
|
704
|
+
)
|
705
|
+
)
|
706
|
+
sql_fields = ', '.join(sql_fields_filter)
|
707
|
+
sql_insert = f'INSERT INTO `{self.dbconn.db.database}`.`{table}`({sql_fields})'
|
708
|
+
sqls.append(sql_insert)
|
709
|
+
|
710
|
+
## Part 'SELECT' syntax.
|
711
|
+
sql_values = ', '.join(
|
712
|
+
f'`{field}`'
|
713
|
+
for field in fields
|
714
|
+
if field not in kwdata
|
715
|
+
)
|
716
|
+
if kwdata != {}:
|
717
|
+
sql_values_kwdata = ', '.join(
|
718
|
+
value[1:]
|
719
|
+
if (
|
720
|
+
type(value) == str
|
721
|
+
and value.startswith(':')
|
722
|
+
and value != ':'
|
723
|
+
)
|
724
|
+
else f':{field}'
|
725
|
+
for field, value in kwdata.items()
|
726
|
+
if field not in where_keys
|
727
|
+
)
|
728
|
+
sql_values_filter = filter(
|
729
|
+
lambda sql: sql != '',
|
730
|
+
(
|
731
|
+
sql_values,
|
732
|
+
sql_values_kwdata
|
733
|
+
)
|
734
|
+
)
|
735
|
+
sql_values = ', '.join(sql_values_filter)
|
736
|
+
sql_select = (
|
737
|
+
f'SELECT {sql_values}\n'
|
738
|
+
f'FROM `{self.dbconn.db.database}`.`{table}`'
|
739
|
+
)
|
740
|
+
sqls.append(sql_select)
|
741
|
+
|
742
|
+
## Part 'WHERE' syntax.
|
743
|
+
if where is not None:
|
744
|
+
sql_where = f'WHERE {where}'
|
745
|
+
sqls.append(sql_where)
|
746
|
+
|
747
|
+
## Part 'LIMIT' syntax.
|
748
|
+
if limit is not None:
|
749
|
+
if type(limit) in (str, int):
|
750
|
+
sql_limit = f'LIMIT {limit}'
|
751
|
+
else:
|
752
|
+
if len(limit) == 2:
|
753
|
+
sql_limit = f'LIMIT {limit[0]}, {limit[1]}'
|
754
|
+
else:
|
755
|
+
throw(ValueError, limit)
|
756
|
+
sqls.append(sql_limit)
|
757
|
+
|
758
|
+
## Join.
|
759
|
+
sql = '\n'.join(sqls)
|
760
|
+
|
761
|
+
# Execute SQL.
|
762
|
+
result = self.execute(sql, report=report, **kwdata)
|
288
763
|
|
289
764
|
return result
|
290
765
|
|
291
766
|
|
292
|
-
def
|
767
|
+
def count(
|
768
|
+
self,
|
769
|
+
table: str,
|
770
|
+
where: str | None = None,
|
771
|
+
report: bool | None = None,
|
772
|
+
**kwdata: Any
|
773
|
+
) -> int:
|
774
|
+
"""
|
775
|
+
Count records.
|
776
|
+
|
777
|
+
Parameters
|
778
|
+
----------
|
779
|
+
table : Table name.
|
780
|
+
where : Match condition, `WHERE` clause content, join as `WHERE str`.
|
781
|
+
- `None`: Match all.
|
782
|
+
- `str`: Match condition.
|
783
|
+
report : Whether report SQL execute information.
|
784
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
785
|
+
- `int`: Use this value.
|
786
|
+
kwdata : Keyword parameters for filling.
|
787
|
+
|
788
|
+
Returns
|
789
|
+
-------
|
790
|
+
Record count.
|
791
|
+
|
792
|
+
Examples
|
793
|
+
--------
|
794
|
+
Parameter `where` and `kwdata`.
|
795
|
+
>>> where = '`id` IN :ids'
|
796
|
+
>>> ids = (1, 2)
|
797
|
+
>>> result = Database.execute.count('table', where, ids=ids)
|
798
|
+
>>> print(result)
|
799
|
+
2
|
800
|
+
"""
|
801
|
+
|
802
|
+
# Execute.
|
803
|
+
result = self.select(table, '1', where=where, report=report, **kwdata)
|
804
|
+
count = len(tuple(result))
|
805
|
+
|
806
|
+
return count
|
807
|
+
|
808
|
+
|
809
|
+
def exist(
|
293
810
|
self,
|
294
|
-
|
811
|
+
table: str,
|
812
|
+
where: str | None = None,
|
813
|
+
report: bool | None = None,
|
814
|
+
**kwdata: Any
|
295
815
|
) -> bool:
|
296
816
|
"""
|
297
817
|
Judge the exist of record.
|
298
818
|
|
299
819
|
Parameters
|
300
820
|
----------
|
301
|
-
|
302
|
-
|
303
|
-
- `
|
304
|
-
- `str`:
|
821
|
+
table : Table name.
|
822
|
+
where : Match condition, `WHERE` clause content, join as `WHERE str`.
|
823
|
+
- `None`: Match all.
|
824
|
+
- `str`: Match condition.
|
825
|
+
report : Whether report SQL execute information.
|
826
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
827
|
+
- `int`: Use this value.
|
828
|
+
kwdata : Keyword parameters for filling.
|
305
829
|
|
306
830
|
Returns
|
307
831
|
-------
|
308
|
-
|
832
|
+
Judged result.
|
833
|
+
|
834
|
+
Examples
|
835
|
+
--------
|
836
|
+
Parameter `where` and `kwdata`.
|
837
|
+
>>> data = [{'id': 1}]
|
838
|
+
>>> Database.execute.insert('table', data)
|
839
|
+
>>> where = '`id` = :id_'
|
840
|
+
>>> id_ = 1
|
841
|
+
>>> result = Database.execute.exist('table', where, id_=id_)
|
842
|
+
>>> print(result)
|
843
|
+
True
|
309
844
|
"""
|
310
845
|
|
311
|
-
#
|
312
|
-
|
313
|
-
case tuple():
|
314
|
-
result = self._rdatabase.execute_exist(self.__get_path, *params)
|
315
|
-
case dict():
|
316
|
-
result = self._rdatabase.execute_exist(self.__get_path, **params)
|
317
|
-
case _:
|
318
|
-
result = self._rdatabase.execute_exist(self.__get_path, params)
|
846
|
+
# Execute.
|
847
|
+
result = self.count(table, where, report, **kwdata)
|
319
848
|
|
320
|
-
|
849
|
+
# Judge.
|
850
|
+
judge = result != 0
|
321
851
|
|
852
|
+
return judge
|
322
853
|
|
323
|
-
|
324
|
-
|
325
|
-
|
854
|
+
|
855
|
+
def generator(
|
856
|
+
self,
|
857
|
+
sql: str | TextClause,
|
858
|
+
data: TableData,
|
859
|
+
report: bool | None = None,
|
860
|
+
**kwdata: Any
|
861
|
+
) -> Generator[Result, Any, None]:
|
326
862
|
"""
|
327
|
-
|
863
|
+
Return a generator that can execute SQL.
|
864
|
+
|
865
|
+
Parameters
|
866
|
+
----------
|
867
|
+
sql : SQL in method `sqlalchemy.text` format, or `TextClause` object.
|
868
|
+
data : Data set for filling.
|
869
|
+
report : Whether report SQL execute information.
|
870
|
+
- `None`: Use attribute `default_report`.
|
871
|
+
- `bool`: Use this value.
|
872
|
+
kwdata : Keyword parameters for filling.
|
328
873
|
|
329
874
|
Returns
|
330
875
|
-------
|
331
|
-
|
876
|
+
Generator.
|
332
877
|
"""
|
333
878
|
|
334
|
-
#
|
335
|
-
|
879
|
+
# Instance.
|
880
|
+
func_generator = FunctionGenerator(
|
881
|
+
self.execute,
|
882
|
+
sql=sql,
|
883
|
+
report=report,
|
884
|
+
**kwdata
|
885
|
+
)
|
336
886
|
|
337
|
-
|
887
|
+
# Add.
|
888
|
+
for row in data:
|
889
|
+
func_generator(**row)
|
890
|
+
|
891
|
+
return func_generator.generator
|
892
|
+
|
893
|
+
|
894
|
+
def handle_sql(self, sql: str | TextClause) -> TextClause:
|
895
|
+
"""
|
896
|
+
Handle SQL.
|
897
|
+
|
898
|
+
Parameters
|
899
|
+
----------
|
900
|
+
sql : SQL in method `sqlalchemy.text` format, or TextClause object.
|
901
|
+
|
902
|
+
Returns
|
903
|
+
-------
|
904
|
+
TextClause instance.
|
905
|
+
"""
|
906
|
+
|
907
|
+
# Handle parameter.
|
908
|
+
if type(sql) == TextClause:
|
909
|
+
sql = sql.text
|
910
|
+
|
911
|
+
# Handle.
|
912
|
+
sql = sql.strip()
|
913
|
+
if sql[-1] != ';':
|
914
|
+
sql += ';'
|
915
|
+
sql = sqlalchemy_text(sql)
|
916
|
+
|
917
|
+
return sql
|
918
|
+
|
919
|
+
|
920
|
+
def handle_data(
|
921
|
+
self,
|
922
|
+
data: list[dict],
|
923
|
+
sql: str | TextClause,
|
924
|
+
) -> list[dict]:
|
925
|
+
"""
|
926
|
+
Handle data based on the content of SQL.
|
927
|
+
|
928
|
+
Parameters
|
929
|
+
----------
|
930
|
+
data : Data set for filling.
|
931
|
+
sql : SQL in method `sqlalchemy.text` format, or TextClause object.
|
932
|
+
|
933
|
+
Returns
|
934
|
+
-------
|
935
|
+
Filled data.
|
936
|
+
"""
|
937
|
+
|
938
|
+
# Handle parameter.
|
939
|
+
if type(sql) == TextClause:
|
940
|
+
sql = sql.text
|
941
|
+
|
942
|
+
# Extract keys.
|
943
|
+
pattern = '(?<!\\\\):(\\w+)'
|
944
|
+
sql_keys = findall(pattern, sql)
|
945
|
+
|
946
|
+
# Extract keys of syntax "in".
|
947
|
+
pattern = '[iI][nN]\\s+(?<!\\\\):(\\w+)'
|
948
|
+
sql_keys_in = findall(pattern, sql)
|
949
|
+
|
950
|
+
# Loop.
|
951
|
+
for row in data:
|
952
|
+
if row == {}:
|
953
|
+
continue
|
954
|
+
for key in sql_keys:
|
955
|
+
value = row.get(key)
|
956
|
+
|
957
|
+
# Empty string.
|
958
|
+
if value == '':
|
959
|
+
value = None
|
960
|
+
|
961
|
+
# Convert.
|
962
|
+
elif (
|
963
|
+
type(value) in (list, dict)
|
964
|
+
and key not in sql_keys_in
|
965
|
+
):
|
966
|
+
value = to_json(value)
|
967
|
+
|
968
|
+
# Enum.
|
969
|
+
elif isinstance(type(value), EnumType):
|
970
|
+
value = value.value
|
971
|
+
|
972
|
+
row[key] = value
|
973
|
+
|
974
|
+
return data
|