reykit 1.0.0__py3-none-any.whl → 1.0.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.
- reydb/__init__.py +25 -0
- reydb/rall.py +17 -0
- reydb/rbuild.py +1235 -0
- reydb/rconnection.py +2276 -0
- reydb/rexecute.py +354 -0
- reydb/rfile.py +416 -0
- reydb/rinformation.py +515 -0
- reydb/rparameter.py +243 -0
- reykit/__init__.py +1 -1
- reykit/rrandom.py +40 -9
- {reykit-1.0.0.dist-info → reykit-1.0.1.dist-info}/METADATA +1 -1
- reykit-1.0.1.dist-info/RECORD +64 -0
- reyweb/__init__.py +19 -0
- reyweb/rall.py +12 -0
- reyweb/rbaidu/__init__.py +21 -0
- reyweb/rbaidu/rbaidu_base.py +186 -0
- reyweb/rbaidu/rbaidu_chat.py +299 -0
- reyweb/rbaidu/rbaidu_image.py +183 -0
- reyweb/rbaidu/rbaidu_voice.py +256 -0
- reywechat/__init__.py +32 -0
- reywechat/data/client_api.dll +0 -0
- reywechat/rall.py +20 -0
- reywechat/rclient.py +912 -0
- reywechat/rdatabase.py +1189 -0
- reywechat/rexception.py +65 -0
- reywechat/rexecute.py +201 -0
- reywechat/rlog.py +198 -0
- reywechat/rreceive.py +1232 -0
- reywechat/rschedule.py +136 -0
- reywechat/rsend.py +630 -0
- reywechat/rwechat.py +201 -0
- reyworm/__init__.py +24 -0
- reyworm/rall.py +16 -0
- reyworm/rbrowser.py +134 -0
- reyworm/rcalendar.py +159 -0
- reyworm/rnews.py +126 -0
- reyworm/rsecurity.py +239 -0
- reyworm/rtranslate.py +75 -0
- reykit-1.0.0.dist-info/RECORD +0 -30
- {reykit-1.0.0.dist-info → reykit-1.0.1.dist-info}/WHEEL +0 -0
- {reykit-1.0.0.dist-info → reykit-1.0.1.dist-info}/top_level.txt +0 -0
reydb/rconnection.py
ADDED
@@ -0,0 +1,2276 @@
|
|
1
|
+
# !/usr/bin/env python
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
"""
|
5
|
+
@Time : 2022-12-05 14:10:02
|
6
|
+
@Author : Rey
|
7
|
+
@Contact : reyxbo@163.com
|
8
|
+
@Explain : Database connection methods.
|
9
|
+
"""
|
10
|
+
|
11
|
+
|
12
|
+
from __future__ import annotations
|
13
|
+
from typing import Any, Union, Optional, Literal, NoReturn, Self, overload, override
|
14
|
+
from types import TracebackType
|
15
|
+
from collections.abc import Iterable, Generator
|
16
|
+
from urllib.parse import quote as urllib_quote
|
17
|
+
from sqlalchemy import create_engine as sqlalchemy_create_engine, text
|
18
|
+
from sqlalchemy.engine.base import Engine, Connection
|
19
|
+
from sqlalchemy.engine.cursor import CursorResult
|
20
|
+
from sqlalchemy.engine.url import URL
|
21
|
+
from sqlalchemy.sql.elements import TextClause
|
22
|
+
from sqlalchemy.exc import OperationalError
|
23
|
+
from pandas import DataFrame
|
24
|
+
from reykit.rdata import objs_in, RGenerator
|
25
|
+
from reykit.rexception import throw
|
26
|
+
from reykit.rmonkey import monkey_patch_sqlalchemy_result_more_fetch, monkey_patch_sqlalchemy_row_index_field
|
27
|
+
from reykit.rregex import search, findall
|
28
|
+
from reykit.rstdout import echo
|
29
|
+
from reykit.rsystem import get_first_notnull
|
30
|
+
from reykit.rtable import Table, to_table
|
31
|
+
from reykit.rtext import join_data_text, to_json
|
32
|
+
from reykit.rwrap import wrap_runtime, wrap_retry
|
33
|
+
|
34
|
+
|
35
|
+
__all__ = (
|
36
|
+
'RResult',
|
37
|
+
'RDatabase',
|
38
|
+
'RDBConnection'
|
39
|
+
)
|
40
|
+
|
41
|
+
|
42
|
+
# Monkey path.
|
43
|
+
monkey_result_type = monkey_patch_sqlalchemy_result_more_fetch()
|
44
|
+
RResult = monkey_result_type
|
45
|
+
monkey_patch_sqlalchemy_row_index_field()
|
46
|
+
|
47
|
+
|
48
|
+
class RDatabase(object):
|
49
|
+
"""
|
50
|
+
Rey's `database` type.
|
51
|
+
"""
|
52
|
+
|
53
|
+
|
54
|
+
# Values to be converted to 'NULL'.
|
55
|
+
nulls: tuple = ('', ' ', b'', [], (), {}, set())
|
56
|
+
|
57
|
+
# Default value.
|
58
|
+
default_report: bool = False
|
59
|
+
|
60
|
+
|
61
|
+
@overload
|
62
|
+
def __init__(
|
63
|
+
self,
|
64
|
+
host: None = None,
|
65
|
+
port: Optional[Union[str, int]] = None,
|
66
|
+
username: Optional[str] = None,
|
67
|
+
password: Optional[str] = None,
|
68
|
+
database: Optional[str] = None,
|
69
|
+
drivername: Optional[str] = None,
|
70
|
+
pool_size: int = 5,
|
71
|
+
max_overflow: int = 10,
|
72
|
+
pool_timeout: float = 30.0,
|
73
|
+
pool_recycle: Optional[int] = None,
|
74
|
+
retry: bool = False,
|
75
|
+
url: None = None,
|
76
|
+
engine: None = None,
|
77
|
+
**query: str
|
78
|
+
) -> NoReturn: ...
|
79
|
+
|
80
|
+
@overload
|
81
|
+
def __init__(
|
82
|
+
self,
|
83
|
+
host: Optional[str] = None,
|
84
|
+
port: None = None,
|
85
|
+
username: Optional[str] = None,
|
86
|
+
password: Optional[str] = None,
|
87
|
+
database: Optional[str] = None,
|
88
|
+
drivername: Optional[str] = None,
|
89
|
+
pool_size: int = 5,
|
90
|
+
max_overflow: int = 10,
|
91
|
+
pool_timeout: float = 30.0,
|
92
|
+
pool_recycle: Optional[int] = None,
|
93
|
+
retry: bool = False,
|
94
|
+
url: None = None,
|
95
|
+
engine: None = None,
|
96
|
+
**query: str
|
97
|
+
) -> NoReturn: ...
|
98
|
+
|
99
|
+
@overload
|
100
|
+
def __init__(
|
101
|
+
self,
|
102
|
+
host: Optional[str] = None,
|
103
|
+
port: Optional[Union[str, int]] = None,
|
104
|
+
username: None = None,
|
105
|
+
password: Optional[str] = None,
|
106
|
+
database: Optional[str] = None,
|
107
|
+
drivername: Optional[str] = None,
|
108
|
+
pool_size: int = 5,
|
109
|
+
max_overflow: int = 10,
|
110
|
+
pool_timeout: float = 30.0,
|
111
|
+
pool_recycle: Optional[int] = None,
|
112
|
+
retry: bool = False,
|
113
|
+
url: None = None,
|
114
|
+
engine: None = None,
|
115
|
+
**query: str
|
116
|
+
) -> NoReturn: ...
|
117
|
+
|
118
|
+
@overload
|
119
|
+
def __init__(
|
120
|
+
self,
|
121
|
+
host: Optional[str] = None,
|
122
|
+
port: Optional[Union[str, int]] = None,
|
123
|
+
username: Optional[str] = None,
|
124
|
+
password: None = None,
|
125
|
+
database: Optional[str] = None,
|
126
|
+
drivername: Optional[str] = None,
|
127
|
+
pool_size: int = 5,
|
128
|
+
max_overflow: int = 10,
|
129
|
+
pool_timeout: float = 30.0,
|
130
|
+
pool_recycle: Optional[int] = None,
|
131
|
+
retry: bool = False,
|
132
|
+
url: None = None,
|
133
|
+
engine: None = None,
|
134
|
+
**query: str
|
135
|
+
) -> NoReturn: ...
|
136
|
+
|
137
|
+
@overload
|
138
|
+
def __init__(
|
139
|
+
self,
|
140
|
+
host: Optional[str] = None,
|
141
|
+
port: Optional[Union[str, int]] = None,
|
142
|
+
username: Optional[str] = None,
|
143
|
+
password: Optional[str] = None,
|
144
|
+
database: Optional[str] = None,
|
145
|
+
drivername: Optional[str] = None,
|
146
|
+
pool_size: int = 5,
|
147
|
+
max_overflow: int = 10,
|
148
|
+
pool_timeout: float = 30.0,
|
149
|
+
pool_recycle: Optional[int] = None,
|
150
|
+
retry: bool = False,
|
151
|
+
url: Optional[Union[str, URL]] = None,
|
152
|
+
engine: Optional[Union[Engine, Connection]] = None,
|
153
|
+
**query: str
|
154
|
+
) -> None: ...
|
155
|
+
|
156
|
+
def __init__(
|
157
|
+
self,
|
158
|
+
host: Optional[str] = None,
|
159
|
+
port: Optional[Union[str, int]] = None,
|
160
|
+
username: Optional[str] = None,
|
161
|
+
password: Optional[str] = None,
|
162
|
+
database: Optional[str] = None,
|
163
|
+
drivername: Optional[str] = None,
|
164
|
+
pool_size: int = 5,
|
165
|
+
max_overflow: int = 10,
|
166
|
+
pool_timeout: float = 30.0,
|
167
|
+
pool_recycle: Optional[int] = None,
|
168
|
+
retry: bool = False,
|
169
|
+
url: Optional[Union[str, URL]] = None,
|
170
|
+
engine: Optional[Union[Engine, Connection]] = None,
|
171
|
+
**query: str
|
172
|
+
) -> None:
|
173
|
+
"""
|
174
|
+
Build `database` attributes.
|
175
|
+
|
176
|
+
Parameters
|
177
|
+
----------
|
178
|
+
host : Server host.
|
179
|
+
port : Server port.
|
180
|
+
username : Server username.
|
181
|
+
password : Server password.
|
182
|
+
database : Database name in the server.
|
183
|
+
drivername : Database backend and driver name.
|
184
|
+
- `None`: Automatic select and try.
|
185
|
+
- `str`: Use this value.
|
186
|
+
pool_size : Number of connections `keep open`.
|
187
|
+
max_overflow : Number of connections `allowed overflow`.
|
188
|
+
pool_timeout : Number of seconds `wait create` connection.
|
189
|
+
pool_recycle : Number of seconds `recycle` connection.
|
190
|
+
- `None`, Use database variable `wait_timeout`: value.
|
191
|
+
- `Literal[-1]`: No recycle.
|
192
|
+
- `int`: Use this value.
|
193
|
+
retry : Whether retry execute.
|
194
|
+
url: Get parameter from server `URL`, but preferred input parameters.
|
195
|
+
Parameters include `username`, `password`, `host`, `port`, `database`, `drivername`, `query`.
|
196
|
+
engine : Use existing `Engine` or `Connection` object, and get parameter from it.
|
197
|
+
Parameters include `username`, `password`, `host`, `port`, `database`, `drivername`, `query`,
|
198
|
+
`pool_size`, `max_overflow`, `pool_timeout`, `pool_recycle`.
|
199
|
+
query : Server parameters.
|
200
|
+
"""
|
201
|
+
|
202
|
+
# Handle parameter.
|
203
|
+
if port.__class__ == int:
|
204
|
+
port = str(port)
|
205
|
+
|
206
|
+
# Set attribute.
|
207
|
+
self.retry = retry
|
208
|
+
|
209
|
+
# From existing Engine or Connection object.
|
210
|
+
if engine is not None:
|
211
|
+
|
212
|
+
## Extract Engine object from Connection boject.
|
213
|
+
if engine.__class__ == Connection:
|
214
|
+
engine = engine.engine
|
215
|
+
|
216
|
+
## Extract parameter.
|
217
|
+
params = self.extract_engine(engine)
|
218
|
+
|
219
|
+
## Set.
|
220
|
+
self.drivername: str = params['drivername']
|
221
|
+
self.username: str = params['username']
|
222
|
+
self.password: str = params['password']
|
223
|
+
self.host: str = params['host']
|
224
|
+
self.port: str = params['port']
|
225
|
+
self.database: Optional[str] = params['database']
|
226
|
+
self.query: dict = params['query']
|
227
|
+
self.pool_size: int = params['pool_size']
|
228
|
+
self.max_overflow: int = params['max_overflow']
|
229
|
+
self.pool_timeout: float = params['pool_timeout']
|
230
|
+
self.pool_recycle: int = params['pool_recycle']
|
231
|
+
self.engine = engine
|
232
|
+
|
233
|
+
# From parameters create.
|
234
|
+
else:
|
235
|
+
|
236
|
+
## Extract parameters from URL.
|
237
|
+
if url is not None:
|
238
|
+
params = self.extract_url(url)
|
239
|
+
else:
|
240
|
+
params = dict.fromkeys(
|
241
|
+
(
|
242
|
+
'drivername',
|
243
|
+
'username',
|
244
|
+
'password',
|
245
|
+
'host',
|
246
|
+
'port',
|
247
|
+
'database',
|
248
|
+
'query'
|
249
|
+
)
|
250
|
+
)
|
251
|
+
|
252
|
+
## Set parameters by priority.
|
253
|
+
self.drivername: str = get_first_notnull(drivername, params['drivername'])
|
254
|
+
self.username: str = get_first_notnull(username, params['username'], default='exception')
|
255
|
+
self.password: str = get_first_notnull(password, params['password'], default='exception')
|
256
|
+
self.host: str = get_first_notnull(host, params['host'], default='exception')
|
257
|
+
self.port: str = get_first_notnull(port, params['port'], default='exception')
|
258
|
+
self.database: Optional[str] = get_first_notnull(database, params['database'])
|
259
|
+
self.query: dict = get_first_notnull(query, params['query'])
|
260
|
+
self.pool_size = pool_size
|
261
|
+
self.max_overflow = max_overflow
|
262
|
+
self.pool_timeout = pool_timeout
|
263
|
+
|
264
|
+
## Create Engine object.
|
265
|
+
if pool_recycle is None:
|
266
|
+
self.pool_recycle = -1
|
267
|
+
self.engine = self.create_engine()
|
268
|
+
wait_timeout = int(self.variables['wait_timeout'])
|
269
|
+
self.pool_recycle = wait_timeout
|
270
|
+
self.engine.pool._recycle = wait_timeout
|
271
|
+
else:
|
272
|
+
self.pool_recycle = pool_recycle
|
273
|
+
self.engine = self.create_engine()
|
274
|
+
|
275
|
+
|
276
|
+
def extract_url(self, url: Union[str, URL]) -> dict[
|
277
|
+
Literal['drivername', 'username', 'password', 'host', 'port', 'database', 'query'],
|
278
|
+
Any
|
279
|
+
]:
|
280
|
+
"""
|
281
|
+
Extract parameters from URL of string.
|
282
|
+
|
283
|
+
Parameters
|
284
|
+
----------
|
285
|
+
url : URL of string.
|
286
|
+
|
287
|
+
Returns
|
288
|
+
-------
|
289
|
+
Extracted parameters.
|
290
|
+
"""
|
291
|
+
|
292
|
+
# Extract.
|
293
|
+
match url:
|
294
|
+
|
295
|
+
## When str object.
|
296
|
+
case str():
|
297
|
+
pattern = r'^([\w\+]+)://(\w+):(\w+)@(\d+\.\d+\.\d+\.\d+):(\d+)[/]?([\w/]+)?[\?]?([\w&=]+)?$'
|
298
|
+
result = search(pattern, url)
|
299
|
+
if result is None:
|
300
|
+
throw(ValueError, url)
|
301
|
+
(
|
302
|
+
drivername,
|
303
|
+
username,
|
304
|
+
password,
|
305
|
+
host,
|
306
|
+
port,
|
307
|
+
database,
|
308
|
+
query_str
|
309
|
+
) = result
|
310
|
+
if query_str is not None:
|
311
|
+
pattern = r'(\w+)=(\w+)'
|
312
|
+
query_findall = findall(pattern, query_str)
|
313
|
+
query = {key: value for key, value in query_findall}
|
314
|
+
else:
|
315
|
+
query = {}
|
316
|
+
|
317
|
+
## When URL object.
|
318
|
+
case URL():
|
319
|
+
drivername = url.drivername
|
320
|
+
username = url.username
|
321
|
+
password = url.password
|
322
|
+
host = url.host
|
323
|
+
port = url.port
|
324
|
+
database = url.database
|
325
|
+
query = dict(url.query)
|
326
|
+
|
327
|
+
# Generate parameter.
|
328
|
+
params = {
|
329
|
+
'drivername': drivername,
|
330
|
+
'username': username,
|
331
|
+
'password': password,
|
332
|
+
'host': host,
|
333
|
+
'port': port,
|
334
|
+
'database': database,
|
335
|
+
'query': query
|
336
|
+
}
|
337
|
+
|
338
|
+
return params
|
339
|
+
|
340
|
+
|
341
|
+
def extract_engine(self, engine: Union[Engine, Connection]) -> dict[
|
342
|
+
Literal[
|
343
|
+
'drivername', 'username', 'password', 'host', 'port', 'database', 'query',
|
344
|
+
'pool_size', 'max_overflow', 'pool_timeout', 'pool_recycle'
|
345
|
+
],
|
346
|
+
Any
|
347
|
+
]:
|
348
|
+
"""
|
349
|
+
Extract parameters from `Engine` or `Connection` object.
|
350
|
+
|
351
|
+
Parameters
|
352
|
+
----------
|
353
|
+
engine : Engine or Connection object.
|
354
|
+
|
355
|
+
Returns
|
356
|
+
-------
|
357
|
+
Extracted parameters.
|
358
|
+
"""
|
359
|
+
|
360
|
+
## Extract Engine object from Connection boject.
|
361
|
+
if engine.__class__ == Connection:
|
362
|
+
engine = engine.engine
|
363
|
+
|
364
|
+
## Extract.
|
365
|
+
drivername = engine.url.drivername
|
366
|
+
username = engine.url.username
|
367
|
+
password = engine.url.password
|
368
|
+
host = engine.url.host
|
369
|
+
port = engine.url.port
|
370
|
+
database = engine.url.database
|
371
|
+
query = dict(engine.url.query)
|
372
|
+
pool_size = engine.pool._pool.maxsize
|
373
|
+
max_overflow = engine.pool._max_overflow
|
374
|
+
pool_timeout = engine.pool._timeout
|
375
|
+
pool_recycle = engine.pool._recycle
|
376
|
+
|
377
|
+
# Generate parameter.
|
378
|
+
params = {
|
379
|
+
'drivername': drivername,
|
380
|
+
'username': username,
|
381
|
+
'password': password,
|
382
|
+
'host': host,
|
383
|
+
'port': port,
|
384
|
+
'database': database,
|
385
|
+
'query': query,
|
386
|
+
'pool_size': pool_size,
|
387
|
+
'max_overflow': max_overflow,
|
388
|
+
'pool_timeout': pool_timeout,
|
389
|
+
'pool_recycle': pool_recycle
|
390
|
+
}
|
391
|
+
|
392
|
+
return params
|
393
|
+
|
394
|
+
|
395
|
+
@overload
|
396
|
+
def extract_path(
|
397
|
+
self,
|
398
|
+
path: str,
|
399
|
+
main: Literal['table'] = 'table'
|
400
|
+
) -> tuple[Optional[str], str, Optional[str]]: ...
|
401
|
+
|
402
|
+
@overload
|
403
|
+
def extract_path(
|
404
|
+
self,
|
405
|
+
path: str,
|
406
|
+
main: Literal['database'] = 'table'
|
407
|
+
) -> tuple[str, Optional[str], Optional[str]]: ...
|
408
|
+
|
409
|
+
def extract_path(
|
410
|
+
self,
|
411
|
+
path: str,
|
412
|
+
main: Literal['table', 'database'] = 'table'
|
413
|
+
) -> tuple[Optional[str], Optional[str], Optional[str]]:
|
414
|
+
"""
|
415
|
+
Extract table name and database name and column name from path.
|
416
|
+
|
417
|
+
Parameters
|
418
|
+
----------
|
419
|
+
path : Automatic extract.
|
420
|
+
```Not contain '.' or contain '`'```: Main name.
|
421
|
+
`Contain '.'`: Database name and table name, column name is optional. Example 'database.table[.column]'.
|
422
|
+
main : Priority main name, 'table' or 'database'.
|
423
|
+
|
424
|
+
Returns
|
425
|
+
-------
|
426
|
+
Database name and table name and column name.
|
427
|
+
"""
|
428
|
+
|
429
|
+
# Extract.
|
430
|
+
if (
|
431
|
+
'.' not in path
|
432
|
+
or '`' in path
|
433
|
+
):
|
434
|
+
name = path.replace('`', '')
|
435
|
+
match main:
|
436
|
+
case 'table':
|
437
|
+
names = (None, name, None)
|
438
|
+
case 'database':
|
439
|
+
names = (name, None, None)
|
440
|
+
case _:
|
441
|
+
throw(ValueError, main)
|
442
|
+
else:
|
443
|
+
names = path.split('.', 2)
|
444
|
+
if len(names) == 2:
|
445
|
+
names.append(None)
|
446
|
+
names = tuple(names)
|
447
|
+
|
448
|
+
return names
|
449
|
+
|
450
|
+
|
451
|
+
@property
|
452
|
+
def url(self) -> str:
|
453
|
+
"""
|
454
|
+
Generate server URL.
|
455
|
+
|
456
|
+
Returns
|
457
|
+
-------
|
458
|
+
Server URL.
|
459
|
+
"""
|
460
|
+
|
461
|
+
# Generate URL.
|
462
|
+
password = urllib_quote(self.password)
|
463
|
+
_url = f'{self.drivername}://{self.username}:{password}@{self.host}:{self.port}'
|
464
|
+
|
465
|
+
# Add database path.
|
466
|
+
if self.database is not None:
|
467
|
+
_url = f'{_url}/{self.database}'
|
468
|
+
|
469
|
+
# Add Server parameter.
|
470
|
+
if self.query != {}:
|
471
|
+
query = '&'.join(
|
472
|
+
[
|
473
|
+
f'{key}={value}'
|
474
|
+
for key, value in self.query.items()
|
475
|
+
]
|
476
|
+
)
|
477
|
+
_url = f'{_url}?{query}'
|
478
|
+
|
479
|
+
return _url
|
480
|
+
|
481
|
+
|
482
|
+
def create_engine(self, **kwargs) -> Engine:
|
483
|
+
"""
|
484
|
+
Create database `Engine` object.
|
485
|
+
|
486
|
+
Parameters
|
487
|
+
----------
|
488
|
+
kwargs : Keyword arguments of create engine method.
|
489
|
+
|
490
|
+
Returns
|
491
|
+
-------
|
492
|
+
Engine object.
|
493
|
+
"""
|
494
|
+
|
495
|
+
# Handle parameter.
|
496
|
+
if self.drivername is None:
|
497
|
+
drivernames = ('mysql+mysqldb', 'mysql+pymysql', 'mysql+mysqlconnector')
|
498
|
+
else:
|
499
|
+
drivernames = (self.drivername,)
|
500
|
+
|
501
|
+
# Create Engine object.
|
502
|
+
for drivername in drivernames:
|
503
|
+
|
504
|
+
## Set engine parameter.
|
505
|
+
self.drivername = drivername
|
506
|
+
engine_params = {
|
507
|
+
'url': self.url,
|
508
|
+
'pool_size': self.pool_size,
|
509
|
+
'max_overflow': self.max_overflow,
|
510
|
+
'pool_timeout': self.pool_timeout,
|
511
|
+
'pool_recycle': self.pool_recycle,
|
512
|
+
**kwargs
|
513
|
+
}
|
514
|
+
|
515
|
+
## Try create.
|
516
|
+
try:
|
517
|
+
engine = sqlalchemy_create_engine(**engine_params)
|
518
|
+
except ModuleNotFoundError:
|
519
|
+
pass
|
520
|
+
else:
|
521
|
+
return engine
|
522
|
+
|
523
|
+
# Throw exception.
|
524
|
+
drivernames_str = ' and '.join(
|
525
|
+
[
|
526
|
+
"'%s'" % dirvername.split('+', 1)[-1]
|
527
|
+
for dirvername in drivernames
|
528
|
+
]
|
529
|
+
)
|
530
|
+
raise ModuleNotFoundError(f'module {drivernames_str} not fund')
|
531
|
+
|
532
|
+
|
533
|
+
@property
|
534
|
+
def count(self) -> tuple[int, int]:
|
535
|
+
"""
|
536
|
+
Count number of keep open and allowed overflow connection.
|
537
|
+
|
538
|
+
Returns
|
539
|
+
-------
|
540
|
+
Number of keep open and allowed overflow connection.
|
541
|
+
"""
|
542
|
+
|
543
|
+
# Get parameter.
|
544
|
+
if hasattr(self, 'engine'):
|
545
|
+
rdatabase = self
|
546
|
+
else:
|
547
|
+
rdatabase: RDatabase = self.rdatabase
|
548
|
+
|
549
|
+
# Count.
|
550
|
+
_overflow = rdatabase.engine.pool._overflow
|
551
|
+
if _overflow < 0:
|
552
|
+
keep_n = rdatabase.pool_size + _overflow
|
553
|
+
overflow_n = 0
|
554
|
+
else:
|
555
|
+
keep_n = rdatabase.pool_size
|
556
|
+
overflow_n = _overflow
|
557
|
+
|
558
|
+
return keep_n, overflow_n
|
559
|
+
|
560
|
+
|
561
|
+
def handle_data(
|
562
|
+
self,
|
563
|
+
data: Table,
|
564
|
+
sql: Union[str, TextClause],
|
565
|
+
) -> list[dict]:
|
566
|
+
"""
|
567
|
+
Handle data based on the content of SQL.
|
568
|
+
|
569
|
+
Parameters
|
570
|
+
----------
|
571
|
+
data : Data set for filling.
|
572
|
+
sql : SQL in method sqlalchemy.text format, or TextClause object.
|
573
|
+
|
574
|
+
Returns
|
575
|
+
-------
|
576
|
+
Filled data.
|
577
|
+
"""
|
578
|
+
|
579
|
+
# Handle parameter.
|
580
|
+
match data:
|
581
|
+
case dict():
|
582
|
+
data = [data]
|
583
|
+
case list():
|
584
|
+
data = to_table(data)
|
585
|
+
if sql.__class__ == TextClause:
|
586
|
+
sql = sql.text
|
587
|
+
|
588
|
+
# Extract keys.
|
589
|
+
pattern = '(?<!\\\\):(\\w+)'
|
590
|
+
sql_keys = findall(pattern, sql)
|
591
|
+
|
592
|
+
# Extract keys of syntax "in".
|
593
|
+
pattern = '[iI][nN]\\s+(?<!\\\\):(\\w+)'
|
594
|
+
sql_keys_in = findall(pattern, sql)
|
595
|
+
|
596
|
+
# Loop.
|
597
|
+
for row in data:
|
598
|
+
if row == {}:
|
599
|
+
continue
|
600
|
+
for key in sql_keys:
|
601
|
+
value = row.get(key)
|
602
|
+
|
603
|
+
# Fill.
|
604
|
+
if (
|
605
|
+
value is None
|
606
|
+
or value in self.nulls
|
607
|
+
):
|
608
|
+
row[key] = None
|
609
|
+
|
610
|
+
# Convert.
|
611
|
+
elif (
|
612
|
+
value.__class__ in (list, dict)
|
613
|
+
and key not in sql_keys_in
|
614
|
+
):
|
615
|
+
value= to_json(value)
|
616
|
+
row[key] = value
|
617
|
+
|
618
|
+
return data
|
619
|
+
|
620
|
+
|
621
|
+
def get_syntax(self, sql: Union[str, TextClause]) -> list[str]:
|
622
|
+
"""
|
623
|
+
Extract SQL syntax type for each segment form SQL.
|
624
|
+
|
625
|
+
Parameters
|
626
|
+
----------
|
627
|
+
sql : SQL text or TextClause object.
|
628
|
+
|
629
|
+
Returns
|
630
|
+
-------
|
631
|
+
SQL syntax type for each segment.
|
632
|
+
"""
|
633
|
+
|
634
|
+
# Handle parameter.
|
635
|
+
if sql.__class__ == TextClause:
|
636
|
+
sql = sql.text
|
637
|
+
|
638
|
+
# Extract.
|
639
|
+
syntax = [
|
640
|
+
search('[a-zA-Z]+', sql_part).upper()
|
641
|
+
for sql_part in sql.split(';')
|
642
|
+
]
|
643
|
+
|
644
|
+
return syntax
|
645
|
+
|
646
|
+
|
647
|
+
def is_multi_sql(self, sql: Union[str, TextClause]) -> bool:
|
648
|
+
"""
|
649
|
+
Judge whether it is multi segment SQL.
|
650
|
+
|
651
|
+
Parameters
|
652
|
+
----------
|
653
|
+
sql : SQL text or TextClause object.
|
654
|
+
|
655
|
+
Returns
|
656
|
+
-------
|
657
|
+
Judgment result.
|
658
|
+
"""
|
659
|
+
|
660
|
+
# Handle parameter.
|
661
|
+
if sql.__class__ == TextClause:
|
662
|
+
sql = sql.text
|
663
|
+
|
664
|
+
# Judge.
|
665
|
+
if ';' in sql.rstrip()[:-1]:
|
666
|
+
return True
|
667
|
+
return False
|
668
|
+
|
669
|
+
|
670
|
+
def executor(
|
671
|
+
self,
|
672
|
+
connection: Connection,
|
673
|
+
sql: TextClause,
|
674
|
+
data: list[dict],
|
675
|
+
report: bool
|
676
|
+
) -> RResult:
|
677
|
+
"""
|
678
|
+
SQL executor.
|
679
|
+
|
680
|
+
Parameters
|
681
|
+
----------
|
682
|
+
connection : Connection object.
|
683
|
+
sql : TextClause object.
|
684
|
+
data : Data set for filling.
|
685
|
+
report : Whether report SQL execute information.
|
686
|
+
|
687
|
+
Returns
|
688
|
+
-------
|
689
|
+
Result object.
|
690
|
+
"""
|
691
|
+
|
692
|
+
# Create Transaction object.
|
693
|
+
with connection.begin():
|
694
|
+
|
695
|
+
# Execute.
|
696
|
+
|
697
|
+
## Report.
|
698
|
+
if report:
|
699
|
+
result, report_runtime = wrap_runtime(connection.execute, sql, data, _return_report=True)
|
700
|
+
report_info = (
|
701
|
+
f'{report_runtime}\n'
|
702
|
+
f'Row Count: {result.rowcount}'
|
703
|
+
)
|
704
|
+
sqls = [
|
705
|
+
sql_part.strip()
|
706
|
+
for sql_part in sql.text.split(';')
|
707
|
+
]
|
708
|
+
if data == []:
|
709
|
+
echo(report_info, *sqls, title='SQL')
|
710
|
+
else:
|
711
|
+
echo(report_info, *sqls, data, title='SQL')
|
712
|
+
|
713
|
+
## Not report.
|
714
|
+
else:
|
715
|
+
result = connection.execute(sql, data)
|
716
|
+
|
717
|
+
return result
|
718
|
+
|
719
|
+
|
720
|
+
def execute(
|
721
|
+
self,
|
722
|
+
sql: Union[str, TextClause],
|
723
|
+
data: Optional[Table] = None,
|
724
|
+
report: Optional[bool] = None,
|
725
|
+
**kwdata: Any
|
726
|
+
) -> RResult:
|
727
|
+
"""
|
728
|
+
Execute SQL.
|
729
|
+
|
730
|
+
Parameters
|
731
|
+
----------
|
732
|
+
sql : SQL in method `sqlalchemy.text` format, or `TextClause` object.
|
733
|
+
data : Data set for filling.
|
734
|
+
report : Whether report SQL execute information.
|
735
|
+
- `None`: Use attribute `default_report`.
|
736
|
+
- `bool`: Use this value.
|
737
|
+
kwdata : Keyword parameters for filling.
|
738
|
+
|
739
|
+
Returns
|
740
|
+
-------
|
741
|
+
Result object.
|
742
|
+
"""
|
743
|
+
|
744
|
+
# Get parameter by priority.
|
745
|
+
report = get_first_notnull(report, self.default_report, default='exception')
|
746
|
+
|
747
|
+
# Handle parameter.
|
748
|
+
if sql.__class__ == str:
|
749
|
+
sql = text(sql)
|
750
|
+
if data is None:
|
751
|
+
if kwdata == {}:
|
752
|
+
data = []
|
753
|
+
else:
|
754
|
+
data = [kwdata]
|
755
|
+
else:
|
756
|
+
match data:
|
757
|
+
case dict():
|
758
|
+
data = [data]
|
759
|
+
case CursorResult():
|
760
|
+
data = to_table(data)
|
761
|
+
case DataFrame():
|
762
|
+
data = to_table(data)
|
763
|
+
case _:
|
764
|
+
data = data.copy()
|
765
|
+
for param in data:
|
766
|
+
param.update(kwdata)
|
767
|
+
|
768
|
+
# Handle data.
|
769
|
+
data = self.handle_data(data, sql)
|
770
|
+
|
771
|
+
# Execute.
|
772
|
+
|
773
|
+
## Create Connection object.
|
774
|
+
with self.engine.connect() as connection:
|
775
|
+
|
776
|
+
## Can retry.
|
777
|
+
if (
|
778
|
+
self.retry
|
779
|
+
and not self.is_multi_sql(sql)
|
780
|
+
):
|
781
|
+
result = wrap_retry(
|
782
|
+
self.executor,
|
783
|
+
connection,
|
784
|
+
sql,
|
785
|
+
data,
|
786
|
+
report,
|
787
|
+
_report='Database Execute Operational Error',
|
788
|
+
_exception=OperationalError
|
789
|
+
)
|
790
|
+
|
791
|
+
## Cannot retry.
|
792
|
+
else:
|
793
|
+
result = self.executor(connection, sql, data, report)
|
794
|
+
|
795
|
+
return result
|
796
|
+
|
797
|
+
|
798
|
+
def execute_select(
|
799
|
+
self,
|
800
|
+
path: Union[str, tuple[str, str]],
|
801
|
+
fields: Optional[Union[str, Iterable[str]]] = None,
|
802
|
+
where: Optional[str] = None,
|
803
|
+
group: Optional[str] = None,
|
804
|
+
having: Optional[str] = None,
|
805
|
+
order: Optional[str] = None,
|
806
|
+
limit: Optional[Union[int, str, tuple[int, int]]] = None,
|
807
|
+
report: Optional[bool] = None,
|
808
|
+
**kwdata: Any
|
809
|
+
) -> RResult:
|
810
|
+
"""
|
811
|
+
Execute select SQL.
|
812
|
+
|
813
|
+
Parameters
|
814
|
+
----------
|
815
|
+
path : Table name, can contain database name, otherwise use `self.database`.
|
816
|
+
- `str`: Automatic extract database name and table name.
|
817
|
+
- `tuple[str, str]`: Database name and table name.
|
818
|
+
fields : Select clause content.
|
819
|
+
- `None`: Is `SELECT *`.
|
820
|
+
- `str`: Join as `SELECT str`.
|
821
|
+
- `Iterable[str]`, Join as `SELECT ``str``: ...`.
|
822
|
+
`str and first character is ':'`: Use this syntax.
|
823
|
+
`str`: Use this field.
|
824
|
+
where : Clause `WHERE` content, join as `WHERE str`.
|
825
|
+
group : Clause `GROUP BY` content, join as `GROUP BY str`.
|
826
|
+
having : Clause `HAVING` content, join as `HAVING str`.
|
827
|
+
order : Clause `ORDER BY` content, join as `ORDER BY str`.
|
828
|
+
limit : Clause `LIMIT` content.
|
829
|
+
- `Union[int, str]`: Join as `LIMIT int/str`.
|
830
|
+
- `tuple[int, int]`: Join as `LIMIT int, int`.
|
831
|
+
report : Whether report SQL execute information.
|
832
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
833
|
+
- `int`: Use this value.
|
834
|
+
kwdata : Keyword parameters for filling.
|
835
|
+
|
836
|
+
Returns
|
837
|
+
-------
|
838
|
+
Result object.
|
839
|
+
|
840
|
+
Examples
|
841
|
+
--------
|
842
|
+
Parameter `fields`.
|
843
|
+
>>> fields = ['id', ':`id` + 1 AS `id_`']
|
844
|
+
>>> result = RDatabase.execute_select('database.table', fields)
|
845
|
+
>>> print(result.to_table())
|
846
|
+
[{'id': 1, 'id_': 2}, ...]
|
847
|
+
|
848
|
+
Parameter `kwdata`.
|
849
|
+
>>> fields = '`id`, `id` + :value AS `id_`'
|
850
|
+
>>> result = RDatabase.execute_select('database.table', fields, value=1)
|
851
|
+
>>> print(result.to_table())
|
852
|
+
[{'id': 1, 'id_': 2}, ...]
|
853
|
+
"""
|
854
|
+
|
855
|
+
# Handle parameter.
|
856
|
+
if path.__class__ == str:
|
857
|
+
database, table, _ = self.extract_path(path)
|
858
|
+
else:
|
859
|
+
database, table = path
|
860
|
+
|
861
|
+
# Get parameter by priority.
|
862
|
+
database = get_first_notnull(database, self.database, default='exception')
|
863
|
+
|
864
|
+
# Generate SQL.
|
865
|
+
sql_list = []
|
866
|
+
|
867
|
+
## Part 'SELECT' syntax.
|
868
|
+
if fields is None:
|
869
|
+
fields = '*'
|
870
|
+
elif fields.__class__ != str:
|
871
|
+
fields = ', '.join(
|
872
|
+
[
|
873
|
+
field[1:]
|
874
|
+
if (
|
875
|
+
field.startswith(':')
|
876
|
+
and field != ':'
|
877
|
+
)
|
878
|
+
else f'`{field}`'
|
879
|
+
for field in fields
|
880
|
+
]
|
881
|
+
)
|
882
|
+
sql_select = f'SELECT {fields}'
|
883
|
+
sql_list.append(sql_select)
|
884
|
+
|
885
|
+
## Part 'FROM' syntax.
|
886
|
+
sql_from = f'FROM `{database}`.`{table}`'
|
887
|
+
sql_list.append(sql_from)
|
888
|
+
|
889
|
+
## Part 'WHERE' syntax.
|
890
|
+
if where is not None:
|
891
|
+
sql_where = f'WHERE {where}'
|
892
|
+
sql_list.append(sql_where)
|
893
|
+
|
894
|
+
## Part 'GROUP BY' syntax.
|
895
|
+
if group is not None:
|
896
|
+
sql_group = f'GROUP BY {group}'
|
897
|
+
sql_list.append(sql_group)
|
898
|
+
|
899
|
+
## Part 'GROUP BY' syntax.
|
900
|
+
if having is not None:
|
901
|
+
sql_having = f'HAVING {having}'
|
902
|
+
sql_list.append(sql_having)
|
903
|
+
|
904
|
+
## Part 'ORDER BY' syntax.
|
905
|
+
if order is not None:
|
906
|
+
sql_order = f'ORDER BY {order}'
|
907
|
+
sql_list.append(sql_order)
|
908
|
+
|
909
|
+
## Part 'LIMIT' syntax.
|
910
|
+
if limit is not None:
|
911
|
+
if limit.__class__ in (str, int):
|
912
|
+
sql_limit = f'LIMIT {limit}'
|
913
|
+
else:
|
914
|
+
if len(limit) == 2:
|
915
|
+
sql_limit = f'LIMIT {limit[0]}, {limit[1]}'
|
916
|
+
else:
|
917
|
+
throw(ValueError, limit)
|
918
|
+
sql_list.append(sql_limit)
|
919
|
+
|
920
|
+
## Join sql part.
|
921
|
+
sql = '\n'.join(sql_list)
|
922
|
+
|
923
|
+
# Execute SQL.
|
924
|
+
result = self.execute(sql, report=report, **kwdata)
|
925
|
+
|
926
|
+
return result
|
927
|
+
|
928
|
+
|
929
|
+
def execute_insert(
|
930
|
+
self,
|
931
|
+
path: Union[str, tuple[str, str]],
|
932
|
+
data: Table,
|
933
|
+
duplicate: Optional[Literal['ignore', 'update']] = None,
|
934
|
+
report: Optional[bool] = None,
|
935
|
+
**kwdata: Any
|
936
|
+
) -> RResult:
|
937
|
+
"""
|
938
|
+
Insert the data of table in the datebase.
|
939
|
+
|
940
|
+
Parameters
|
941
|
+
----------
|
942
|
+
path : Table name, can contain database name, otherwise use `self.database`.
|
943
|
+
- `str`: Automatic extract database name and table name.
|
944
|
+
- `tuple[str, str]`: Database name and table name.
|
945
|
+
data : Insert data.
|
946
|
+
duplicate : Handle method when constraint error.
|
947
|
+
- `None`: Not handled.
|
948
|
+
- `ignore`, Use `UPDATE IGNORE INTO`: clause.
|
949
|
+
- `update`, Use `ON DUPLICATE KEY UPDATE`: clause.
|
950
|
+
report : Whether report SQL execute information.
|
951
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
952
|
+
- `int`: Use this value.
|
953
|
+
kwdata : Keyword parameters for filling.
|
954
|
+
- `str and first character is ':'`: Use this syntax.
|
955
|
+
- `Any`: Use this value.
|
956
|
+
|
957
|
+
Returns
|
958
|
+
-------
|
959
|
+
Result object.
|
960
|
+
|
961
|
+
Examples
|
962
|
+
--------
|
963
|
+
Parameter `data` and `kwdata`.
|
964
|
+
>>> data = [{'key': 'a'}, {'key': 'b'}]
|
965
|
+
>>> kwdata = {'value1': 1, 'value2': ':(SELECT 2)'}
|
966
|
+
>>> result = RDatabase.execute_insert('database.table', data, **kwdata)
|
967
|
+
>>> print(result.rowcount)
|
968
|
+
2
|
969
|
+
>>> result = RDatabase.execute_select('database.table')
|
970
|
+
>>> print(result.to_table())
|
971
|
+
[{'key': 'a', 'value1': 1, 'value2': 2}, {'key': 'b', 'value1': 1, 'value2': 2}]
|
972
|
+
"""
|
973
|
+
|
974
|
+
# Handle parameter.
|
975
|
+
if path.__class__ == str:
|
976
|
+
database, table, _ = self.extract_path(path)
|
977
|
+
else:
|
978
|
+
database, table = path
|
979
|
+
|
980
|
+
# Get parameter by priority.
|
981
|
+
database = get_first_notnull(database, self.database, default='exception')
|
982
|
+
|
983
|
+
# Handle parameter.
|
984
|
+
|
985
|
+
## Data.
|
986
|
+
match data:
|
987
|
+
case dict():
|
988
|
+
data = [data]
|
989
|
+
case CursorResult():
|
990
|
+
data = to_table(data)
|
991
|
+
case DataFrame():
|
992
|
+
data = to_table(data)
|
993
|
+
|
994
|
+
## Check.
|
995
|
+
if data in ([], [{}]):
|
996
|
+
throw(ValueError, data)
|
997
|
+
|
998
|
+
## Keyword data.
|
999
|
+
kwdata_method = {}
|
1000
|
+
kwdata_replace = {}
|
1001
|
+
for key, value in kwdata.items():
|
1002
|
+
if (
|
1003
|
+
value.__class__ == str
|
1004
|
+
and value.startswith(':')
|
1005
|
+
and value != ':'
|
1006
|
+
):
|
1007
|
+
kwdata_method[key] = value[1:]
|
1008
|
+
else:
|
1009
|
+
kwdata_replace[key] = value
|
1010
|
+
|
1011
|
+
# Generate SQL.
|
1012
|
+
|
1013
|
+
## Part 'fields' syntax.
|
1014
|
+
fields_replace = {
|
1015
|
+
field
|
1016
|
+
for row in data
|
1017
|
+
for field in row
|
1018
|
+
}
|
1019
|
+
fields_replace = {
|
1020
|
+
field
|
1021
|
+
for field in fields_replace
|
1022
|
+
if field not in kwdata
|
1023
|
+
}
|
1024
|
+
sql_fields_list = (
|
1025
|
+
*kwdata_method,
|
1026
|
+
*kwdata_replace,
|
1027
|
+
*fields_replace
|
1028
|
+
)
|
1029
|
+
sql_fields = ', '.join(
|
1030
|
+
[
|
1031
|
+
f'`{field}`'
|
1032
|
+
for field in sql_fields_list
|
1033
|
+
]
|
1034
|
+
)
|
1035
|
+
|
1036
|
+
## Part 'values' syntax.
|
1037
|
+
sql_values_list = (
|
1038
|
+
*kwdata_method.values(),
|
1039
|
+
*[
|
1040
|
+
':' + field
|
1041
|
+
for field in (
|
1042
|
+
*kwdata_replace,
|
1043
|
+
*fields_replace
|
1044
|
+
)
|
1045
|
+
]
|
1046
|
+
)
|
1047
|
+
sql_values = ', '.join(sql_values_list)
|
1048
|
+
|
1049
|
+
## Join sql part.
|
1050
|
+
match duplicate:
|
1051
|
+
|
1052
|
+
### Ignore.
|
1053
|
+
case 'ignore':
|
1054
|
+
sql = (
|
1055
|
+
f'INSERT IGNORE INTO `{database}`.`{table}`({sql_fields})\n'
|
1056
|
+
f'VALUES({sql_values})'
|
1057
|
+
)
|
1058
|
+
|
1059
|
+
### Update.
|
1060
|
+
case 'update':
|
1061
|
+
update_content = ',\n '.join([f'`{field}` = VALUES(`{field}`)' for field in sql_fields_list])
|
1062
|
+
sql = (
|
1063
|
+
f'INSERT INTO `{database}`.`{table}`({sql_fields})\n'
|
1064
|
+
f'VALUES({sql_values})\n'
|
1065
|
+
'ON DUPLICATE KEY UPDATE\n'
|
1066
|
+
f' {update_content}'
|
1067
|
+
)
|
1068
|
+
|
1069
|
+
### Not handle.
|
1070
|
+
case _:
|
1071
|
+
sql = (
|
1072
|
+
f'INSERT INTO `{database}`.`{table}`({sql_fields})\n'
|
1073
|
+
f'VALUES({sql_values})'
|
1074
|
+
)
|
1075
|
+
|
1076
|
+
# Execute SQL.
|
1077
|
+
result = self.execute(sql, data, report, **kwdata_replace)
|
1078
|
+
|
1079
|
+
return result
|
1080
|
+
|
1081
|
+
|
1082
|
+
def execute_update(
|
1083
|
+
self,
|
1084
|
+
path: Union[str, tuple[str, str]],
|
1085
|
+
data: Table,
|
1086
|
+
where_fields: Optional[Union[str, Iterable[str]]] = None,
|
1087
|
+
report: Optional[bool] = None,
|
1088
|
+
**kwdata: Any
|
1089
|
+
) -> RResult:
|
1090
|
+
"""
|
1091
|
+
Update the data of table in the datebase.
|
1092
|
+
|
1093
|
+
Parameters
|
1094
|
+
----------
|
1095
|
+
path : Table name, can contain database name, otherwise use `self.database`.
|
1096
|
+
- `str`: Automatic extract database name and table name.
|
1097
|
+
- `tuple[str, str]`: Database name and table name.
|
1098
|
+
data : Update data, clause `SET` and `WHERE` and `ORDER BY` and `LIMIT` content.
|
1099
|
+
- `Key`: Table field.
|
1100
|
+
`literal['order']`: Clause `ORDER BY` content, join as `ORDER BY str`.
|
1101
|
+
`literal['limit']`: Clause `LIMIT` content, join as `LIMIT str`.
|
1102
|
+
`Other`: Clause `SET` and `WHERE` content.
|
1103
|
+
- `Value`: Table value.
|
1104
|
+
`Union[list, tuple]`: Join as `field IN :str`.
|
1105
|
+
`Any`: Join as `field = :str`.
|
1106
|
+
where_fields : Clause `WHERE` content fields.
|
1107
|
+
- `None`: The first key value pair of each item is judged.
|
1108
|
+
- `str`: This key value pair of each item is judged.
|
1109
|
+
- `Iterable[str]` Multiple judged, `and`: relationship.
|
1110
|
+
report : Whether report SQL execute information.
|
1111
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1112
|
+
- `int`: Use this value.
|
1113
|
+
kwdata : Keyword parameters for filling.
|
1114
|
+
- `str and first character is ':'`: Use this syntax.
|
1115
|
+
- `Any`: Use this value.
|
1116
|
+
|
1117
|
+
Returns
|
1118
|
+
-------
|
1119
|
+
Result object.
|
1120
|
+
|
1121
|
+
Examples
|
1122
|
+
--------
|
1123
|
+
Parameter `data` and `kwdata`.
|
1124
|
+
>>> data = [{'key': 'a'}, {'key': 'b'}]
|
1125
|
+
>>> kwdata = {'value': 1, 'name': ':`key`'}
|
1126
|
+
>>> result = RDatabase.execute_update('database.table', data, **kwdata)
|
1127
|
+
>>> print(result.rowcount)
|
1128
|
+
2
|
1129
|
+
>>> result = RDatabase.execute_select('database.table')
|
1130
|
+
>>> print(result.to_table())
|
1131
|
+
[{'key': 'a', 'value': 1, 'name': 'a'}, {'key': 'b', 'value': 1, 'name': 'b'}]
|
1132
|
+
"""
|
1133
|
+
|
1134
|
+
# Handle parameter.
|
1135
|
+
if path.__class__ == str:
|
1136
|
+
database, table, _ = self.extract_path(path)
|
1137
|
+
else:
|
1138
|
+
database, table = path
|
1139
|
+
|
1140
|
+
# Get parameter by priority.
|
1141
|
+
database = get_first_notnull(database, self.database, default='exception')
|
1142
|
+
|
1143
|
+
# Handle parameter.
|
1144
|
+
|
1145
|
+
## Data.
|
1146
|
+
match data:
|
1147
|
+
case dict():
|
1148
|
+
data = [data]
|
1149
|
+
case CursorResult():
|
1150
|
+
data = to_table(data)
|
1151
|
+
case DataFrame():
|
1152
|
+
data = to_table(data)
|
1153
|
+
|
1154
|
+
## Check.
|
1155
|
+
if data in ([], [{}]):
|
1156
|
+
throw(ValueError, data)
|
1157
|
+
|
1158
|
+
## Keyword data.
|
1159
|
+
kwdata_method = {}
|
1160
|
+
kwdata_replace = {}
|
1161
|
+
for key, value in kwdata.items():
|
1162
|
+
if (
|
1163
|
+
value.__class__ == str
|
1164
|
+
and value.startswith(':')
|
1165
|
+
and value != ':'
|
1166
|
+
):
|
1167
|
+
kwdata_method[key] = value[1:]
|
1168
|
+
else:
|
1169
|
+
kwdata_replace[key] = value
|
1170
|
+
sql_set_list_kwdata = [
|
1171
|
+
f'`{key}` = {value}'
|
1172
|
+
for key, value in kwdata_method.items()
|
1173
|
+
]
|
1174
|
+
sql_set_list_kwdata.extend(
|
1175
|
+
[
|
1176
|
+
f'`{key}` = :{key}'
|
1177
|
+
for key in kwdata_replace
|
1178
|
+
]
|
1179
|
+
)
|
1180
|
+
|
1181
|
+
# Generate SQL.
|
1182
|
+
data_flatten = kwdata_replace
|
1183
|
+
if where_fields is None:
|
1184
|
+
no_where = True
|
1185
|
+
else:
|
1186
|
+
no_where = False
|
1187
|
+
if where_fields.__class__ == str:
|
1188
|
+
where_fields = [where_fields]
|
1189
|
+
sqls_list = []
|
1190
|
+
sql_update = f'UPDATE `{database}`.`{table}`'
|
1191
|
+
for index, row in enumerate(data):
|
1192
|
+
sql_parts = [sql_update]
|
1193
|
+
for key, value in row.items():
|
1194
|
+
if key in ('order', 'limit'):
|
1195
|
+
continue
|
1196
|
+
index_key = f'{index}_{key}'
|
1197
|
+
data_flatten[index_key] = value
|
1198
|
+
if no_where:
|
1199
|
+
for key in row:
|
1200
|
+
where_fields = [key]
|
1201
|
+
break
|
1202
|
+
|
1203
|
+
## Part 'SET' syntax.
|
1204
|
+
sql_set_list = sql_set_list_kwdata.copy()
|
1205
|
+
sql_set_list.extend(
|
1206
|
+
[
|
1207
|
+
f'`{key}` = :{index}_{key}'
|
1208
|
+
for key in row
|
1209
|
+
if (
|
1210
|
+
key not in where_fields
|
1211
|
+
and key not in kwdata
|
1212
|
+
and key not in ('order', 'limit')
|
1213
|
+
)
|
1214
|
+
]
|
1215
|
+
)
|
1216
|
+
sql_set = 'SET ' + ',\n '.join(sql_set_list)
|
1217
|
+
sql_parts.append(sql_set)
|
1218
|
+
|
1219
|
+
## Part 'WHERE' syntax.
|
1220
|
+
sql_where_list = []
|
1221
|
+
for field in where_fields:
|
1222
|
+
index_field = f'{index}_{field}'
|
1223
|
+
index_value = data_flatten[index_field]
|
1224
|
+
if index_value.__class__ in (list, tuple):
|
1225
|
+
sql_where_part = f'`{field}` IN :{index_field}'
|
1226
|
+
else:
|
1227
|
+
sql_where_part = f'`{field}` = :{index_field}'
|
1228
|
+
sql_where_list.append(sql_where_part)
|
1229
|
+
sql_where = 'WHERE ' + '\n AND '.join(sql_where_list)
|
1230
|
+
sql_parts.append(sql_where)
|
1231
|
+
|
1232
|
+
## Part 'ORDER BY' syntax.
|
1233
|
+
order = row.get('order')
|
1234
|
+
if order is not None:
|
1235
|
+
sql_order = f'ORDER BY {order}'
|
1236
|
+
sql_parts.append(sql_order)
|
1237
|
+
|
1238
|
+
## Part 'LIMIT' syntax.
|
1239
|
+
limit = row.get('limit')
|
1240
|
+
if limit is not None:
|
1241
|
+
sql_limit = f'LIMIT {limit}'
|
1242
|
+
sql_parts.append(sql_limit)
|
1243
|
+
|
1244
|
+
## Join sql part.
|
1245
|
+
sql = '\n'.join(sql_parts)
|
1246
|
+
sqls_list.append(sql)
|
1247
|
+
|
1248
|
+
## Join sqls.
|
1249
|
+
sqls = ';\n'.join(sqls_list)
|
1250
|
+
|
1251
|
+
# Execute SQL.
|
1252
|
+
result = self.execute(sqls, data_flatten, report)
|
1253
|
+
|
1254
|
+
return result
|
1255
|
+
|
1256
|
+
|
1257
|
+
def execute_delete(
|
1258
|
+
self,
|
1259
|
+
path: Union[str, tuple[str, str]],
|
1260
|
+
where: Optional[str] = None,
|
1261
|
+
order: Optional[str] = None,
|
1262
|
+
limit: Optional[Union[int, str]] = None,
|
1263
|
+
report: Optional[bool] = None,
|
1264
|
+
**kwdata: Any
|
1265
|
+
) -> RResult:
|
1266
|
+
"""
|
1267
|
+
Delete the data of table in the datebase.
|
1268
|
+
|
1269
|
+
Parameters
|
1270
|
+
----------
|
1271
|
+
path : Table name, can contain database name, otherwise use `self.database`.
|
1272
|
+
- `str`: Automatic extract database name and table name.
|
1273
|
+
- `tuple[str, str]`: Database name and table name.
|
1274
|
+
where : Clause `WHERE` content, join as `WHERE str`.
|
1275
|
+
order : Clause `ORDER BY` content, join as `ORDER BY str`.
|
1276
|
+
limit : Clause `LIMIT` content, join as `LIMIT int/str`.
|
1277
|
+
report : Whether report SQL execute information.
|
1278
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1279
|
+
- `int`: Use this value.
|
1280
|
+
kwdata : Keyword parameters for filling.
|
1281
|
+
|
1282
|
+
Returns
|
1283
|
+
-------
|
1284
|
+
Result object.
|
1285
|
+
|
1286
|
+
Examples
|
1287
|
+
--------
|
1288
|
+
Parameter `where` and `kwdata`.
|
1289
|
+
>>> where = '`id` IN :ids'
|
1290
|
+
>>> ids = (1, 2)
|
1291
|
+
>>> result = RDatabase.execute_delete('database.table', where, ids=ids)
|
1292
|
+
>>> print(result.rowcount)
|
1293
|
+
2
|
1294
|
+
"""
|
1295
|
+
|
1296
|
+
# Handle parameter.
|
1297
|
+
if path.__class__ == str:
|
1298
|
+
database, table, _ = self.extract_path(path)
|
1299
|
+
else:
|
1300
|
+
database, table = path
|
1301
|
+
|
1302
|
+
# Get parameter by priority.
|
1303
|
+
database = get_first_notnull(database, self.database, default='exception')
|
1304
|
+
|
1305
|
+
# Generate SQL.
|
1306
|
+
sqls = []
|
1307
|
+
|
1308
|
+
## Part 'DELETE' syntax.
|
1309
|
+
sql_delete = f'DELETE FROM `{database}`.`{table}`'
|
1310
|
+
sqls.append(sql_delete)
|
1311
|
+
|
1312
|
+
## Part 'WHERE' syntax.
|
1313
|
+
if where is not None:
|
1314
|
+
sql_where = f'WHERE {where}'
|
1315
|
+
sqls.append(sql_where)
|
1316
|
+
|
1317
|
+
## Part 'ORDER BY' syntax.
|
1318
|
+
if order is not None:
|
1319
|
+
sql_order = f'ORDER BY {order}'
|
1320
|
+
sqls.append(sql_order)
|
1321
|
+
|
1322
|
+
## Part 'LIMIT' syntax.
|
1323
|
+
if limit is not None:
|
1324
|
+
sql_limit = f'LIMIT {limit}'
|
1325
|
+
sqls.append(sql_limit)
|
1326
|
+
|
1327
|
+
## Join sqls.
|
1328
|
+
sqls = '\n'.join(sqls)
|
1329
|
+
|
1330
|
+
# Execute SQL.
|
1331
|
+
result = self.execute(sqls, report=report, **kwdata)
|
1332
|
+
|
1333
|
+
return result
|
1334
|
+
|
1335
|
+
|
1336
|
+
def execute_copy(
|
1337
|
+
self,
|
1338
|
+
path: Union[str, tuple[str, str]],
|
1339
|
+
where: Optional[str] = None,
|
1340
|
+
limit: Optional[Union[int, str, tuple[int, int]]] = None,
|
1341
|
+
report: Optional[bool] = None,
|
1342
|
+
**kwdata: Any
|
1343
|
+
) -> RResult:
|
1344
|
+
"""
|
1345
|
+
Copy record of table in the datebase.
|
1346
|
+
|
1347
|
+
Parameters
|
1348
|
+
----------
|
1349
|
+
path : Table name, can contain database name, otherwise use `self.database`.
|
1350
|
+
- `str`: Automatic extract database name and table name.
|
1351
|
+
- `tuple[str, str]`: Database name and table name.
|
1352
|
+
where : Clause `WHERE` content, join as `WHERE str`.
|
1353
|
+
limit : Clause `LIMIT` content.
|
1354
|
+
- `Union[int, str]`: Join as `LIMIT int/str`.
|
1355
|
+
- `tuple[int, int]`: Join as `LIMIT int, int`.
|
1356
|
+
report : Whether report SQL execute information.
|
1357
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1358
|
+
- `int`: Use this value.
|
1359
|
+
kwdata : Keyword parameters for filling.
|
1360
|
+
- `In 'WHERE' syntax`: Fill 'WHERE' syntax.
|
1361
|
+
- `Not in 'WHERE' syntax`: Fill 'INSERT' and 'SELECT' syntax.
|
1362
|
+
`str and first character is ':'`: Use this syntax.
|
1363
|
+
`Any`: Use this value.
|
1364
|
+
|
1365
|
+
Returns
|
1366
|
+
-------
|
1367
|
+
Result object.
|
1368
|
+
|
1369
|
+
Examples
|
1370
|
+
--------
|
1371
|
+
Parameter `where` and `kwdata`.
|
1372
|
+
>>> where = '`id` IN :ids'
|
1373
|
+
>>> ids = (1, 2, 3)
|
1374
|
+
>>> result = RDatabase.execute_copy('database.table', where, 2, ids=ids, id=None, time=':NOW()')
|
1375
|
+
>>> print(result.rowcount)
|
1376
|
+
2
|
1377
|
+
"""
|
1378
|
+
|
1379
|
+
# Handle parameter.
|
1380
|
+
if path.__class__ == str:
|
1381
|
+
database, table, _ = self.extract_path(path)
|
1382
|
+
else:
|
1383
|
+
database, table = path
|
1384
|
+
|
1385
|
+
# Get parameter by priority.
|
1386
|
+
database = get_first_notnull(database, self.database, default='exception')
|
1387
|
+
|
1388
|
+
# Get parameter.
|
1389
|
+
table_info: list[dict] = self.info(database)(table)()
|
1390
|
+
fields = [
|
1391
|
+
row['COLUMN_NAME']
|
1392
|
+
for row in table_info
|
1393
|
+
]
|
1394
|
+
pattern = '(?<!\\\\):(\\w+)'
|
1395
|
+
if where.__class__ == str:
|
1396
|
+
where_keys = findall(pattern, where)
|
1397
|
+
else:
|
1398
|
+
where_keys = ()
|
1399
|
+
|
1400
|
+
# Generate SQL.
|
1401
|
+
sqls = []
|
1402
|
+
|
1403
|
+
## Part 'INSERT' syntax.
|
1404
|
+
sql_fields = ', '.join(
|
1405
|
+
f'`{field}`'
|
1406
|
+
for field in fields
|
1407
|
+
if field not in kwdata
|
1408
|
+
)
|
1409
|
+
if kwdata != {}:
|
1410
|
+
sql_fields_kwdata = ', '.join(
|
1411
|
+
f'`{field}`'
|
1412
|
+
for field in kwdata
|
1413
|
+
if field not in where_keys
|
1414
|
+
)
|
1415
|
+
sql_fields_filter = filter(
|
1416
|
+
lambda sql: sql != '',
|
1417
|
+
(
|
1418
|
+
sql_fields,
|
1419
|
+
sql_fields_kwdata
|
1420
|
+
)
|
1421
|
+
)
|
1422
|
+
sql_fields = ', '.join(sql_fields_filter)
|
1423
|
+
sql_insert = f'INSERT INTO `{database}`.`{table}`({sql_fields})'
|
1424
|
+
sqls.append(sql_insert)
|
1425
|
+
|
1426
|
+
## Part 'SELECT' syntax.
|
1427
|
+
sql_values = ', '.join(
|
1428
|
+
f'`{field}`'
|
1429
|
+
for field in fields
|
1430
|
+
if field not in kwdata
|
1431
|
+
)
|
1432
|
+
if kwdata != {}:
|
1433
|
+
sql_values_kwdata = ', '.join(
|
1434
|
+
value[1:]
|
1435
|
+
if (
|
1436
|
+
value.__class__ == str
|
1437
|
+
and value.startswith(':')
|
1438
|
+
and value != ':'
|
1439
|
+
)
|
1440
|
+
else f':{field}'
|
1441
|
+
for field, value in kwdata.items()
|
1442
|
+
if field not in where_keys
|
1443
|
+
)
|
1444
|
+
sql_values_filter = filter(
|
1445
|
+
lambda sql: sql != '',
|
1446
|
+
(
|
1447
|
+
sql_values,
|
1448
|
+
sql_values_kwdata
|
1449
|
+
)
|
1450
|
+
)
|
1451
|
+
sql_values = ', '.join(sql_values_filter)
|
1452
|
+
sql_select = (
|
1453
|
+
f'SELECT {sql_values}\n'
|
1454
|
+
f'FROM `{database}`.`{table}`'
|
1455
|
+
)
|
1456
|
+
sqls.append(sql_select)
|
1457
|
+
|
1458
|
+
## Part 'WHERE' syntax.
|
1459
|
+
if where is not None:
|
1460
|
+
sql_where = f'WHERE {where}'
|
1461
|
+
sqls.append(sql_where)
|
1462
|
+
|
1463
|
+
## Part 'LIMIT' syntax.
|
1464
|
+
if limit is not None:
|
1465
|
+
if limit.__class__ in (str, int):
|
1466
|
+
sql_limit = f'LIMIT {limit}'
|
1467
|
+
else:
|
1468
|
+
if len(limit) == 2:
|
1469
|
+
sql_limit = f'LIMIT {limit[0]}, {limit[1]}'
|
1470
|
+
else:
|
1471
|
+
throw(ValueError, limit)
|
1472
|
+
sqls.append(sql_limit)
|
1473
|
+
|
1474
|
+
## Join.
|
1475
|
+
sql = '\n'.join(sqls)
|
1476
|
+
|
1477
|
+
# Execute SQL.
|
1478
|
+
result = self.execute(sql, report=report, **kwdata)
|
1479
|
+
|
1480
|
+
return result
|
1481
|
+
|
1482
|
+
|
1483
|
+
def execute_exist(
|
1484
|
+
self,
|
1485
|
+
path: Union[str, tuple[str, str]],
|
1486
|
+
where: Optional[str] = None,
|
1487
|
+
report: Optional[bool] = None,
|
1488
|
+
**kwdata: Any
|
1489
|
+
) -> bool:
|
1490
|
+
"""
|
1491
|
+
Judge the exist of record.
|
1492
|
+
|
1493
|
+
Parameters
|
1494
|
+
----------
|
1495
|
+
path : Table name, can contain database name, otherwise use `self.database`.
|
1496
|
+
- `str`: Automatic extract database name and table name.
|
1497
|
+
- `tuple[str, str]`: Database name and table name.
|
1498
|
+
where : Match condition, `WHERE` clause content, join as `WHERE str`.
|
1499
|
+
- `None`: Match all.
|
1500
|
+
- `str`: Match condition.
|
1501
|
+
report : Whether report SQL execute information.
|
1502
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1503
|
+
- `int`: Use this value.
|
1504
|
+
kwdata : Keyword parameters for filling.
|
1505
|
+
|
1506
|
+
Returns
|
1507
|
+
-------
|
1508
|
+
Judged result.
|
1509
|
+
|
1510
|
+
Examples
|
1511
|
+
--------
|
1512
|
+
Parameter `where` and `kwdata`.
|
1513
|
+
>>> data = [{'id': 1}]
|
1514
|
+
>>> RDatabase.execute_insert('database.table', data)
|
1515
|
+
>>> where = '`id` = :id_'
|
1516
|
+
>>> id_ = 1
|
1517
|
+
>>> result = RDatabase.execute_exist('database.table', where, id_=id_)
|
1518
|
+
>>> print(result)
|
1519
|
+
True
|
1520
|
+
"""
|
1521
|
+
|
1522
|
+
# Handle parameter.
|
1523
|
+
if path.__class__ == str:
|
1524
|
+
database, table, _ = self.extract_path(path)
|
1525
|
+
else:
|
1526
|
+
database, table = path
|
1527
|
+
|
1528
|
+
# Execute.
|
1529
|
+
result = self.execute_select((database, table), '1', where=where, limit=1, report=report, **kwdata)
|
1530
|
+
|
1531
|
+
# Judge.
|
1532
|
+
judge = result.exist
|
1533
|
+
|
1534
|
+
return judge
|
1535
|
+
|
1536
|
+
|
1537
|
+
def execute_count(
|
1538
|
+
self,
|
1539
|
+
path: Union[str, tuple[str, str]],
|
1540
|
+
where: Optional[str] = None,
|
1541
|
+
report: Optional[bool] = None,
|
1542
|
+
**kwdata: Any
|
1543
|
+
) -> int:
|
1544
|
+
"""
|
1545
|
+
Count records.
|
1546
|
+
|
1547
|
+
Parameters
|
1548
|
+
----------
|
1549
|
+
path : Table name, can contain database name, otherwise use `self.database`.
|
1550
|
+
- `str`: Automatic extract database name and table name.
|
1551
|
+
- `tuple[str, str]`: Database name and table name.
|
1552
|
+
where : Match condition, `WHERE` clause content, join as `WHERE str`.
|
1553
|
+
- `None`: Match all.
|
1554
|
+
- `str`: Match condition.
|
1555
|
+
report : Whether report SQL execute information.
|
1556
|
+
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1557
|
+
- `int`: Use this value.
|
1558
|
+
kwdata : Keyword parameters for filling.
|
1559
|
+
|
1560
|
+
Returns
|
1561
|
+
-------
|
1562
|
+
Record count.
|
1563
|
+
|
1564
|
+
Examples
|
1565
|
+
--------
|
1566
|
+
Parameter `where` and `kwdata`.
|
1567
|
+
>>> where = '`id` IN :ids'
|
1568
|
+
>>> ids = (1, 2)
|
1569
|
+
>>> result = RDatabase.execute_count('database.table', where, ids=ids)
|
1570
|
+
>>> print(result)
|
1571
|
+
2
|
1572
|
+
"""
|
1573
|
+
|
1574
|
+
# Handle parameter.
|
1575
|
+
if path.__class__ == str:
|
1576
|
+
database, table, _ = self.extract_path(path)
|
1577
|
+
else:
|
1578
|
+
database, table = path
|
1579
|
+
|
1580
|
+
# Execute.
|
1581
|
+
result = self.execute_select((database, table), '1', where=where, report=report, **kwdata)
|
1582
|
+
count = result.rowcount
|
1583
|
+
|
1584
|
+
return count
|
1585
|
+
|
1586
|
+
|
1587
|
+
def execute_generator(
|
1588
|
+
self,
|
1589
|
+
sql: Union[str, TextClause],
|
1590
|
+
data: Table,
|
1591
|
+
report: Optional[bool] = None,
|
1592
|
+
**kwdata: Any
|
1593
|
+
) -> Generator[RResult, Any, None]:
|
1594
|
+
"""
|
1595
|
+
Return a generator that can execute SQL.
|
1596
|
+
|
1597
|
+
Parameters
|
1598
|
+
----------
|
1599
|
+
sql : SQL in method `sqlalchemy.text` format, or `TextClause` object.
|
1600
|
+
data : Data set for filling.
|
1601
|
+
report : Whether report SQL execute information.
|
1602
|
+
- `None`: Use attribute `default_report`.
|
1603
|
+
- `bool`: Use this value.
|
1604
|
+
kwdata : Keyword parameters for filling.
|
1605
|
+
|
1606
|
+
Returns
|
1607
|
+
-------
|
1608
|
+
Generator.
|
1609
|
+
"""
|
1610
|
+
|
1611
|
+
# Handle parameter.
|
1612
|
+
match data:
|
1613
|
+
case dict():
|
1614
|
+
data = [data]
|
1615
|
+
case CursorResult():
|
1616
|
+
data = to_table(data)
|
1617
|
+
case DataFrame():
|
1618
|
+
data = to_table(data)
|
1619
|
+
case _:
|
1620
|
+
data = data.copy()
|
1621
|
+
|
1622
|
+
# Instance.
|
1623
|
+
rgenerator = RGenerator(
|
1624
|
+
self.execute,
|
1625
|
+
sql=sql,
|
1626
|
+
report=report,
|
1627
|
+
**kwdata
|
1628
|
+
)
|
1629
|
+
|
1630
|
+
# Add.
|
1631
|
+
for row in data:
|
1632
|
+
rgenerator(**row)
|
1633
|
+
|
1634
|
+
return rgenerator.generator
|
1635
|
+
|
1636
|
+
|
1637
|
+
def connect(self) -> RDBConnection:
|
1638
|
+
"""
|
1639
|
+
Build `database connection` attributes.
|
1640
|
+
|
1641
|
+
Returns
|
1642
|
+
-------
|
1643
|
+
Database connection instance.
|
1644
|
+
"""
|
1645
|
+
|
1646
|
+
# Build.
|
1647
|
+
rdbconnection = RDBConnection(
|
1648
|
+
self.engine.connect(),
|
1649
|
+
self
|
1650
|
+
)
|
1651
|
+
|
1652
|
+
return rdbconnection
|
1653
|
+
|
1654
|
+
|
1655
|
+
@property
|
1656
|
+
def exe(self):
|
1657
|
+
"""
|
1658
|
+
Build `database execute` attributes.
|
1659
|
+
|
1660
|
+
Returns
|
1661
|
+
-------
|
1662
|
+
Database execute instance.
|
1663
|
+
|
1664
|
+
Examples
|
1665
|
+
--------
|
1666
|
+
Execute.
|
1667
|
+
>>> sql = 'select :value'
|
1668
|
+
>>> result = RDBExecute(sql, value=1)
|
1669
|
+
|
1670
|
+
Select.
|
1671
|
+
>>> field = ['id', 'value']
|
1672
|
+
>>> where = '`id` = ids'
|
1673
|
+
>>> ids = (1, 2)
|
1674
|
+
>>> result = RDBExecute.database.table(field, where, ids=ids)
|
1675
|
+
|
1676
|
+
Insert.
|
1677
|
+
>>> data = [{'id': 1}, {'id': 2}]
|
1678
|
+
>>> duplicate = 'ignore'
|
1679
|
+
>>> result = RDBExecute.database.table + data
|
1680
|
+
>>> result = RDBExecute.database.table + (data, duplicate)
|
1681
|
+
>>> result = RDBExecute.database.table + {'data': data, 'duplicate': duplicate}
|
1682
|
+
|
1683
|
+
Update.
|
1684
|
+
>>> data = [{'name': 'a', 'id': 1}, {'name': 'b', 'id': 2}]
|
1685
|
+
>>> where_fields = 'id'
|
1686
|
+
>>> result = RDBExecute.database.table & data
|
1687
|
+
>>> result = RDBExecute.database.table & (data, where_fields)
|
1688
|
+
>>> result = RDBExecute.database.table & {'data': data, 'where_fields': where_fields}
|
1689
|
+
|
1690
|
+
Delete.
|
1691
|
+
>>> where = '`id` IN (1, 2)'
|
1692
|
+
>>> report = True
|
1693
|
+
>>> result = RDBExecute.database.table - where
|
1694
|
+
>>> result = RDBExecute.database.table - (where, report)
|
1695
|
+
>>> result = RDBExecute.database.table - {'where': where, 'report': report}
|
1696
|
+
|
1697
|
+
Copy.
|
1698
|
+
>>> where = '`id` IN (1, 2)'
|
1699
|
+
>>> limit = 1
|
1700
|
+
>>> result = RDBExecute.database.table * where
|
1701
|
+
>>> result = RDBExecute.database.table * (where, limit)
|
1702
|
+
>>> result = RDBExecute.database.table * {'where': where, 'limit': limit}
|
1703
|
+
|
1704
|
+
Exist.
|
1705
|
+
>>> where = '`id` IN (1, 2)'
|
1706
|
+
>>> report = True
|
1707
|
+
>>> result = where in RDBExecute.database.table
|
1708
|
+
>>> result = (where, report) in RDBExecute.database.table
|
1709
|
+
>>> result = {'where': where, 'report': report} in RDBExecute.database.table
|
1710
|
+
|
1711
|
+
Count.
|
1712
|
+
>>> result = len(RDBExecute.database.table)
|
1713
|
+
|
1714
|
+
Default database.
|
1715
|
+
>>> field = ['id', 'value']
|
1716
|
+
>>> engine = RDatabase(**server, database)
|
1717
|
+
>>> result = engine.exe.table()
|
1718
|
+
"""
|
1719
|
+
|
1720
|
+
# Import.
|
1721
|
+
from .rexecute import RDBExecute
|
1722
|
+
|
1723
|
+
# Build.
|
1724
|
+
rdbexecute = RDBExecute(self)
|
1725
|
+
|
1726
|
+
return rdbexecute
|
1727
|
+
|
1728
|
+
|
1729
|
+
@property
|
1730
|
+
def schema(self) -> dict[str, dict[str, list]]:
|
1731
|
+
"""
|
1732
|
+
Get schemata of databases and tables and columns.
|
1733
|
+
|
1734
|
+
Returns
|
1735
|
+
-------
|
1736
|
+
Schemata of databases and tables and columns.
|
1737
|
+
"""
|
1738
|
+
|
1739
|
+
# Select.
|
1740
|
+
filter_db = (
|
1741
|
+
'information_schema',
|
1742
|
+
'mysql',
|
1743
|
+
'performance_schema',
|
1744
|
+
'sys'
|
1745
|
+
)
|
1746
|
+
result = self.execute_select(
|
1747
|
+
'information_schema.COLUMNS',
|
1748
|
+
['TABLE_SCHEMA', 'TABLE_NAME', 'COLUMN_NAME'],
|
1749
|
+
'`TABLE_SCHEMA` NOT IN :filter_db',
|
1750
|
+
order='`TABLE_SCHEMA`, `TABLE_NAME`, `ORDINAL_POSITION`',
|
1751
|
+
filter_db=filter_db
|
1752
|
+
)
|
1753
|
+
|
1754
|
+
# Convert.
|
1755
|
+
database_dict = {}
|
1756
|
+
for database, table, column in result:
|
1757
|
+
|
1758
|
+
## Index database.
|
1759
|
+
if database not in database_dict:
|
1760
|
+
database_dict[database] = {table: [column]}
|
1761
|
+
continue
|
1762
|
+
table_dict: dict = database_dict[database]
|
1763
|
+
|
1764
|
+
## Index table.
|
1765
|
+
if table not in table_dict:
|
1766
|
+
table_dict[table] = [column]
|
1767
|
+
continue
|
1768
|
+
column_list: list = table_dict[table]
|
1769
|
+
|
1770
|
+
## Add column.
|
1771
|
+
column_list.append(column)
|
1772
|
+
|
1773
|
+
return database_dict
|
1774
|
+
|
1775
|
+
|
1776
|
+
@property
|
1777
|
+
def info(self):
|
1778
|
+
"""
|
1779
|
+
Build `database schema information` attributes.
|
1780
|
+
|
1781
|
+
Returns
|
1782
|
+
-------
|
1783
|
+
Database schema information instance.
|
1784
|
+
|
1785
|
+
Examples
|
1786
|
+
--------
|
1787
|
+
Get databases information of server.
|
1788
|
+
>>> databases_info = RDBISchema()
|
1789
|
+
|
1790
|
+
Get tables information of database.
|
1791
|
+
>>> tables_info = RDBISchema.database()
|
1792
|
+
|
1793
|
+
Get columns information of table.
|
1794
|
+
>>> columns_info = RDBISchema.database.table()
|
1795
|
+
|
1796
|
+
Get database attribute.
|
1797
|
+
>>> database_attr = RDBISchema.database['attribute']
|
1798
|
+
|
1799
|
+
Get table attribute.
|
1800
|
+
>>> database_attr = RDBISchema.database.table['attribute']
|
1801
|
+
|
1802
|
+
Get column attribute.
|
1803
|
+
>>> database_attr = RDBISchema.database.table.column['attribute']
|
1804
|
+
"""
|
1805
|
+
|
1806
|
+
# Import.
|
1807
|
+
from .rinformation import RDBISchema
|
1808
|
+
|
1809
|
+
# Build.
|
1810
|
+
rdbischema = RDBISchema(self)
|
1811
|
+
|
1812
|
+
return rdbischema
|
1813
|
+
|
1814
|
+
|
1815
|
+
@property
|
1816
|
+
def build(self):
|
1817
|
+
"""
|
1818
|
+
Build `database build` attributes.
|
1819
|
+
|
1820
|
+
Returns
|
1821
|
+
-------
|
1822
|
+
Database build instance.
|
1823
|
+
"""
|
1824
|
+
|
1825
|
+
# Import.
|
1826
|
+
from .rbuild import RDBBuild
|
1827
|
+
|
1828
|
+
# Build.
|
1829
|
+
rdbbuild = RDBBuild(self)
|
1830
|
+
|
1831
|
+
return rdbbuild
|
1832
|
+
|
1833
|
+
|
1834
|
+
@property
|
1835
|
+
def file(self):
|
1836
|
+
"""
|
1837
|
+
Build `database file` attributes.
|
1838
|
+
|
1839
|
+
Returns
|
1840
|
+
-------
|
1841
|
+
Database file instance.
|
1842
|
+
"""
|
1843
|
+
|
1844
|
+
# Import.
|
1845
|
+
from .rfile import RDBFile
|
1846
|
+
|
1847
|
+
# Build.
|
1848
|
+
rdbfile = RDBFile(self)
|
1849
|
+
|
1850
|
+
return rdbfile
|
1851
|
+
|
1852
|
+
|
1853
|
+
@property
|
1854
|
+
def status(self):
|
1855
|
+
"""
|
1856
|
+
Build `database status parameters` attributes.
|
1857
|
+
|
1858
|
+
Returns
|
1859
|
+
-------
|
1860
|
+
Database status parameters instance.
|
1861
|
+
"""
|
1862
|
+
|
1863
|
+
# Import.
|
1864
|
+
from .rparameter import RDBPStatus
|
1865
|
+
|
1866
|
+
# Build.
|
1867
|
+
rdbpstatus = RDBPStatus(self, False)
|
1868
|
+
|
1869
|
+
return rdbpstatus
|
1870
|
+
|
1871
|
+
|
1872
|
+
@property
|
1873
|
+
def global_status(self):
|
1874
|
+
"""
|
1875
|
+
Build global `database status parameters` instance.
|
1876
|
+
|
1877
|
+
Returns
|
1878
|
+
-------
|
1879
|
+
Global database status parameters instance.
|
1880
|
+
"""
|
1881
|
+
|
1882
|
+
# Import.
|
1883
|
+
from .rparameter import RDBPStatus
|
1884
|
+
|
1885
|
+
# Build.
|
1886
|
+
rdbpstatus = RDBPStatus(self, True)
|
1887
|
+
|
1888
|
+
return rdbpstatus
|
1889
|
+
|
1890
|
+
|
1891
|
+
@property
|
1892
|
+
def variables(self):
|
1893
|
+
"""
|
1894
|
+
Build `database variable parameters` attributes.
|
1895
|
+
|
1896
|
+
Returns
|
1897
|
+
-------
|
1898
|
+
Database variable parameters instance.
|
1899
|
+
"""
|
1900
|
+
|
1901
|
+
# Import.
|
1902
|
+
from .rparameter import RDBPVariable
|
1903
|
+
|
1904
|
+
# Build.
|
1905
|
+
rdbpvariable = RDBPVariable(self, False)
|
1906
|
+
|
1907
|
+
return rdbpvariable
|
1908
|
+
|
1909
|
+
|
1910
|
+
@property
|
1911
|
+
def global_variables(self):
|
1912
|
+
"""
|
1913
|
+
Build global `database variable parameters` instance.
|
1914
|
+
|
1915
|
+
Returns
|
1916
|
+
-------
|
1917
|
+
Global database variable parameters instance.
|
1918
|
+
"""
|
1919
|
+
|
1920
|
+
# Import.
|
1921
|
+
from .rparameter import RDBPVariable
|
1922
|
+
|
1923
|
+
# Build.
|
1924
|
+
rdbpvariable = RDBPVariable(self, True)
|
1925
|
+
|
1926
|
+
return rdbpvariable
|
1927
|
+
|
1928
|
+
|
1929
|
+
@overload
|
1930
|
+
def __call__(
|
1931
|
+
self,
|
1932
|
+
sql: Union[str, TextClause],
|
1933
|
+
data: Optional[Table] = None,
|
1934
|
+
report: Optional[bool] = None,
|
1935
|
+
generator: Literal[False] = False,
|
1936
|
+
**kwdata: Any
|
1937
|
+
) -> RResult: ...
|
1938
|
+
|
1939
|
+
@overload
|
1940
|
+
def __call__(
|
1941
|
+
self,
|
1942
|
+
sql: Union[str, TextClause],
|
1943
|
+
data: Table = None,
|
1944
|
+
report: Optional[bool] = None,
|
1945
|
+
generator: Literal[True] = False,
|
1946
|
+
**kwdata: Any
|
1947
|
+
) -> Generator[RResult, Any, None]: ...
|
1948
|
+
|
1949
|
+
@overload
|
1950
|
+
def __call__(
|
1951
|
+
self,
|
1952
|
+
sql: Union[str, TextClause],
|
1953
|
+
data: None = None,
|
1954
|
+
report: Optional[bool] = None,
|
1955
|
+
generator: Literal[True] = False,
|
1956
|
+
**kwdata: Any
|
1957
|
+
) -> NoReturn: ...
|
1958
|
+
|
1959
|
+
def __call__(
|
1960
|
+
self,
|
1961
|
+
sql: Union[str, TextClause],
|
1962
|
+
data: Optional[Table] = None,
|
1963
|
+
report: Optional[bool] = None,
|
1964
|
+
generator: bool = False,
|
1965
|
+
**kwdata: Any
|
1966
|
+
) -> Union[RResult, Generator[RResult, Any, None]]:
|
1967
|
+
"""
|
1968
|
+
Execute SQL or return a generator that can execute SQL.
|
1969
|
+
|
1970
|
+
Parameters
|
1971
|
+
----------
|
1972
|
+
sql : SQL in method `sqlalchemy.text` format, or `TextClause` object.
|
1973
|
+
data : Data set for filling.
|
1974
|
+
report : Whether report SQL execute information.
|
1975
|
+
- `None`: Use attribute `default_report`.
|
1976
|
+
- `bool`: Use this value.
|
1977
|
+
generator : whether return a generator that can execute SQL, otherwise execute SQL.
|
1978
|
+
kwdata : Keyword parameters for filling.
|
1979
|
+
|
1980
|
+
Returns
|
1981
|
+
-------
|
1982
|
+
Result object or generator.
|
1983
|
+
"""
|
1984
|
+
|
1985
|
+
# Get parameter.
|
1986
|
+
if generator:
|
1987
|
+
func = self.execute_generator
|
1988
|
+
else:
|
1989
|
+
func = self.execute
|
1990
|
+
|
1991
|
+
# Execute.
|
1992
|
+
result = func(
|
1993
|
+
sql,
|
1994
|
+
data,
|
1995
|
+
report,
|
1996
|
+
**kwdata
|
1997
|
+
)
|
1998
|
+
|
1999
|
+
return result
|
2000
|
+
|
2001
|
+
|
2002
|
+
def __str__(self) -> str:
|
2003
|
+
"""
|
2004
|
+
Return connection information text.
|
2005
|
+
"""
|
2006
|
+
|
2007
|
+
# Get parameter.
|
2008
|
+
if hasattr(self, 'engine'):
|
2009
|
+
attr_dict = self.__dict__
|
2010
|
+
else:
|
2011
|
+
rdatabase: RDatabase = self.rdatabase
|
2012
|
+
attr_dict = {
|
2013
|
+
**self.__dict__,
|
2014
|
+
**rdatabase.__dict__
|
2015
|
+
}
|
2016
|
+
|
2017
|
+
# Generate.
|
2018
|
+
filter_key = (
|
2019
|
+
'engine',
|
2020
|
+
'connection',
|
2021
|
+
'rdatabase',
|
2022
|
+
'begin'
|
2023
|
+
)
|
2024
|
+
info = {
|
2025
|
+
key: value
|
2026
|
+
for key, value in attr_dict.items()
|
2027
|
+
if key not in filter_key
|
2028
|
+
}
|
2029
|
+
info['count'] = self.count
|
2030
|
+
text = join_data_text(info)
|
2031
|
+
|
2032
|
+
return text
|
2033
|
+
|
2034
|
+
|
2035
|
+
class RDBConnection(RDatabase):
|
2036
|
+
"""
|
2037
|
+
Rey's `database connection` type.
|
2038
|
+
"""
|
2039
|
+
|
2040
|
+
|
2041
|
+
def __init__(
|
2042
|
+
self,
|
2043
|
+
connection: Connection,
|
2044
|
+
rdatabase: RDatabase
|
2045
|
+
) -> None:
|
2046
|
+
"""
|
2047
|
+
Build `database connection` attributes.
|
2048
|
+
|
2049
|
+
Parameters
|
2050
|
+
----------
|
2051
|
+
connection : Connection object.
|
2052
|
+
rdatabase : RDatabase object.
|
2053
|
+
"""
|
2054
|
+
|
2055
|
+
# Set parameter.
|
2056
|
+
self.connection = connection
|
2057
|
+
self.rdatabase = rdatabase
|
2058
|
+
self.begin = None
|
2059
|
+
self.begin_count = 0
|
2060
|
+
self.drivername = rdatabase.drivername
|
2061
|
+
self.username = rdatabase.username
|
2062
|
+
self.password = rdatabase.password
|
2063
|
+
self.host = rdatabase.host
|
2064
|
+
self.port = rdatabase.port
|
2065
|
+
self.database = rdatabase.database
|
2066
|
+
self.query = rdatabase.query
|
2067
|
+
self.pool_recycle = rdatabase.pool_recycle
|
2068
|
+
self.retry = rdatabase.retry
|
2069
|
+
|
2070
|
+
|
2071
|
+
@override
|
2072
|
+
def executor(
|
2073
|
+
self,
|
2074
|
+
connection: Connection,
|
2075
|
+
sql: TextClause,
|
2076
|
+
data: list[dict],
|
2077
|
+
report: bool
|
2078
|
+
) -> RResult:
|
2079
|
+
"""
|
2080
|
+
SQL executor.
|
2081
|
+
|
2082
|
+
Parameters
|
2083
|
+
----------
|
2084
|
+
connection : Connection object.
|
2085
|
+
sql : TextClause object.
|
2086
|
+
data : Data set for filling.
|
2087
|
+
report : Whether report SQL execute information.
|
2088
|
+
|
2089
|
+
Returns
|
2090
|
+
-------
|
2091
|
+
Result object.
|
2092
|
+
"""
|
2093
|
+
|
2094
|
+
# Create Transaction object.
|
2095
|
+
if self.begin_count == 0:
|
2096
|
+
self.rollback()
|
2097
|
+
self.begin = connection.begin()
|
2098
|
+
|
2099
|
+
# Execute.
|
2100
|
+
|
2101
|
+
## Report.
|
2102
|
+
if report:
|
2103
|
+
result, report_runtime = wrap_runtime(connection.execute, sql, data, _return_report=True)
|
2104
|
+
report_info = (
|
2105
|
+
f'{report_runtime}\n'
|
2106
|
+
f'Row Count: {result.rowcount}'
|
2107
|
+
)
|
2108
|
+
sqls = [
|
2109
|
+
sql_part.strip()
|
2110
|
+
for sql_part in sql.text.split(';')
|
2111
|
+
]
|
2112
|
+
if data == []:
|
2113
|
+
echo(report_info, *sqls, title='SQL')
|
2114
|
+
else:
|
2115
|
+
echo(report_info, *sqls, data, title='SQL')
|
2116
|
+
|
2117
|
+
## Not report.
|
2118
|
+
else:
|
2119
|
+
result = connection.execute(sql, data)
|
2120
|
+
|
2121
|
+
# Count.
|
2122
|
+
syntaxes = self.get_syntax(sql)
|
2123
|
+
if objs_in(syntaxes, 'INSERT', 'UPDATE', 'DELETE'):
|
2124
|
+
self.begin_count += 1
|
2125
|
+
|
2126
|
+
return result
|
2127
|
+
|
2128
|
+
|
2129
|
+
@override
|
2130
|
+
def execute(
|
2131
|
+
self,
|
2132
|
+
sql: Union[str, TextClause],
|
2133
|
+
data: Optional[Table] = None,
|
2134
|
+
report: Optional[bool] = None,
|
2135
|
+
**kwdata: Any
|
2136
|
+
) -> RResult:
|
2137
|
+
"""
|
2138
|
+
Execute SQL.
|
2139
|
+
|
2140
|
+
Parameters
|
2141
|
+
----------
|
2142
|
+
sql : SQL in method `sqlalchemy.text` format, or `TextClause` object.
|
2143
|
+
data : Data set for filling.
|
2144
|
+
report : Whether report SQL execute information.
|
2145
|
+
- `None`: Use attribute `default_report`.
|
2146
|
+
- `bool`: Use this value.
|
2147
|
+
kwdata : Keyword parameters for filling.
|
2148
|
+
|
2149
|
+
Returns
|
2150
|
+
-------
|
2151
|
+
Result object.
|
2152
|
+
"""
|
2153
|
+
|
2154
|
+
# Get parameter by priority.
|
2155
|
+
report = get_first_notnull(report, self.default_report, default='exception')
|
2156
|
+
|
2157
|
+
# Handle parameter.
|
2158
|
+
if sql.__class__ == str:
|
2159
|
+
sql = text(sql)
|
2160
|
+
if data is None:
|
2161
|
+
if kwdata == {}:
|
2162
|
+
data = []
|
2163
|
+
else:
|
2164
|
+
data = [kwdata]
|
2165
|
+
else:
|
2166
|
+
match data:
|
2167
|
+
case dict():
|
2168
|
+
data = [data]
|
2169
|
+
case CursorResult():
|
2170
|
+
data = to_table(data)
|
2171
|
+
case DataFrame():
|
2172
|
+
data = to_table(data)
|
2173
|
+
case _:
|
2174
|
+
data = data.copy()
|
2175
|
+
for param in data:
|
2176
|
+
param.update(kwdata)
|
2177
|
+
|
2178
|
+
# Handle data.
|
2179
|
+
data = self.handle_data(data, sql)
|
2180
|
+
|
2181
|
+
# Execute.
|
2182
|
+
|
2183
|
+
## Can retry.
|
2184
|
+
if (
|
2185
|
+
self.retry
|
2186
|
+
and self.begin_count == 0
|
2187
|
+
and not self.is_multi_sql(sql)
|
2188
|
+
):
|
2189
|
+
result = wrap_retry(
|
2190
|
+
self.executor,
|
2191
|
+
self.connection,
|
2192
|
+
sql,
|
2193
|
+
data,
|
2194
|
+
report,
|
2195
|
+
_report='Database Execute Operational Error',
|
2196
|
+
_exception=OperationalError
|
2197
|
+
)
|
2198
|
+
|
2199
|
+
## Cannot retry.
|
2200
|
+
else:
|
2201
|
+
result = self.executor(self.connection, sql, data, report)
|
2202
|
+
|
2203
|
+
return result
|
2204
|
+
|
2205
|
+
|
2206
|
+
def commit(self) -> None:
|
2207
|
+
"""
|
2208
|
+
Commit cumulative executions.
|
2209
|
+
"""
|
2210
|
+
|
2211
|
+
# Commit.
|
2212
|
+
if self.begin is not None:
|
2213
|
+
self.begin.commit()
|
2214
|
+
self.begin = None
|
2215
|
+
self.begin_count = 0
|
2216
|
+
|
2217
|
+
|
2218
|
+
def rollback(self) -> None:
|
2219
|
+
"""
|
2220
|
+
Rollback cumulative executions.
|
2221
|
+
"""
|
2222
|
+
|
2223
|
+
# Rollback.
|
2224
|
+
if self.begin is not None:
|
2225
|
+
self.begin.rollback()
|
2226
|
+
self.begin = None
|
2227
|
+
self.begin_count = 0
|
2228
|
+
|
2229
|
+
|
2230
|
+
def close(self) -> None:
|
2231
|
+
"""
|
2232
|
+
Close database connection.
|
2233
|
+
"""
|
2234
|
+
|
2235
|
+
# Close.
|
2236
|
+
self.connection.close()
|
2237
|
+
|
2238
|
+
|
2239
|
+
def __enter__(self) -> Self:
|
2240
|
+
"""
|
2241
|
+
Enter syntax `with`.
|
2242
|
+
|
2243
|
+
Returns
|
2244
|
+
-------
|
2245
|
+
Self.
|
2246
|
+
"""
|
2247
|
+
|
2248
|
+
return self
|
2249
|
+
|
2250
|
+
|
2251
|
+
def __exit__(
|
2252
|
+
self,
|
2253
|
+
exc_type: Optional[type[BaseException]],
|
2254
|
+
exc_instance: Optional[BaseException],
|
2255
|
+
exc_traceback: Optional[TracebackType]
|
2256
|
+
) -> None:
|
2257
|
+
"""
|
2258
|
+
Exit syntax `with`.
|
2259
|
+
|
2260
|
+
Parameters
|
2261
|
+
----------
|
2262
|
+
exc_type : Exception type.
|
2263
|
+
exc_instance : Exception instance.
|
2264
|
+
exc_traceback : Exception traceback instance.
|
2265
|
+
"""
|
2266
|
+
|
2267
|
+
# Commit.
|
2268
|
+
if exc_type is None:
|
2269
|
+
self.commit()
|
2270
|
+
|
2271
|
+
# Close.
|
2272
|
+
else:
|
2273
|
+
self.close()
|
2274
|
+
|
2275
|
+
|
2276
|
+
__del__ = close
|