reydb 1.1.48__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 +3 -8
- reydb/rinfo.py +37 -95
- {reydb-1.1.48.dist-info → reydb-1.1.49.dist-info}/METADATA +1 -1
- reydb-1.1.49.dist-info/RECORD +16 -0
- reydb-1.1.48.dist-info/RECORD +0 -16
- {reydb-1.1.48.dist-info → reydb-1.1.49.dist-info}/WHEEL +0 -0
- {reydb-1.1.48.dist-info → reydb-1.1.49.dist-info}/licenses/LICENSE +0 -0
reydb/rdb.py
CHANGED
@@ -9,107 +9,42 @@
|
|
9
9
|
"""
|
10
10
|
|
11
11
|
|
12
|
-
from typing import Any, Literal, overload
|
13
|
-
from collections.abc import Iterable, Generator, Container
|
14
|
-
from enum import EnumType
|
15
12
|
from urllib.parse import quote as urllib_quote
|
16
13
|
from pymysql.constants.CLIENT import MULTI_STATEMENTS
|
17
|
-
from sqlalchemy import create_engine as sqlalchemy_create_engine
|
18
|
-
from sqlalchemy.engine.base import Engine
|
19
|
-
from sqlalchemy.sql.elements import TextClause
|
20
|
-
from reykit.rbase import throw, get_first_notnone
|
21
|
-
from reykit.rdata import Generator, to_json
|
22
|
-
from reykit.rmonkey import monkey_sqlalchemy_result_more_fetch, monkey_sqlalchemy_row_index_field
|
23
|
-
from reykit.rre import search, findall
|
24
|
-
from reykit.rstdout import echo
|
25
|
-
from reykit.rtable import TableData, Table
|
14
|
+
from sqlalchemy import create_engine as sqlalchemy_create_engine
|
15
|
+
from sqlalchemy.engine.base import Engine
|
26
16
|
from reykit.rtext import join_data_text
|
27
|
-
from reykit.rwrap import wrap_runtime
|
28
17
|
|
29
18
|
from .rbase import DatabaseBase, extract_url
|
30
19
|
|
31
20
|
|
32
21
|
__all__ = (
|
33
|
-
'
|
34
|
-
'Database'
|
22
|
+
'Database',
|
35
23
|
)
|
36
24
|
|
37
25
|
|
38
|
-
# Monkey path.
|
39
|
-
Result_ = monkey_sqlalchemy_result_more_fetch()
|
40
|
-
Result = Result_
|
41
|
-
monkey_sqlalchemy_row_index_field()
|
42
|
-
|
43
|
-
|
44
26
|
class Database(DatabaseBase):
|
45
27
|
"""
|
46
28
|
Database type.
|
47
|
-
Based `MySQL
|
48
|
-
|
49
|
-
Examples
|
50
|
-
--------
|
51
|
-
>>> rdb = Database()
|
52
|
-
>>> result = rdb.execute('SELECT 1 as `a`')
|
53
|
-
>>> result.to_table()
|
54
|
-
[{'a': 1}]
|
29
|
+
Based `MySQL`.
|
55
30
|
"""
|
56
31
|
|
57
|
-
#
|
32
|
+
# Whether default to report execution.
|
58
33
|
default_report: bool = False
|
59
34
|
|
60
35
|
|
61
|
-
@overload
|
62
36
|
def __init__(
|
63
37
|
self,
|
64
38
|
host: str,
|
65
39
|
port: int | str,
|
66
40
|
username: str,
|
67
41
|
password: str,
|
68
|
-
database: str | None = None,
|
69
|
-
*,
|
70
|
-
pool_size: int = 5,
|
71
|
-
max_overflow: int = 10,
|
72
|
-
pool_timeout: float = 30.0,
|
73
|
-
pool_recycle: int | None = None,
|
74
|
-
**query: str
|
75
|
-
) -> None: ...
|
76
|
-
|
77
|
-
@overload
|
78
|
-
def __init__(
|
79
|
-
self,
|
80
|
-
*,
|
81
42
|
database: str,
|
82
43
|
pool_size: int = 5,
|
83
44
|
max_overflow: int = 10,
|
84
45
|
pool_timeout: float = 30.0,
|
85
46
|
pool_recycle: int | None = None,
|
86
47
|
**query: str
|
87
|
-
) -> None: ...
|
88
|
-
|
89
|
-
@overload
|
90
|
-
def __init__(
|
91
|
-
self,
|
92
|
-
*,
|
93
|
-
pool_size: int = 5,
|
94
|
-
max_overflow: int = 10,
|
95
|
-
pool_timeout: float = 30.0,
|
96
|
-
pool_recycle: int | None = None,
|
97
|
-
**query: str
|
98
|
-
) -> None: ...
|
99
|
-
|
100
|
-
|
101
|
-
def __init__(
|
102
|
-
self,
|
103
|
-
host: str | None = None,
|
104
|
-
port: int | str | None = None,
|
105
|
-
username: str | None = None,
|
106
|
-
password: str | None = None,
|
107
|
-
database: str | None = None,
|
108
|
-
pool_size: int = 5,
|
109
|
-
max_overflow: int = 10,
|
110
|
-
pool_timeout: float = 30.0,
|
111
|
-
pool_recycle: int | None = None,
|
112
|
-
**query: str
|
113
48
|
) -> None:
|
114
49
|
"""
|
115
50
|
Build instance attributes.
|
@@ -120,8 +55,7 @@ class Database(DatabaseBase):
|
|
120
55
|
port : Remote server database port.
|
121
56
|
username : Remote server database username.
|
122
57
|
password : Remote server database password.
|
123
|
-
database : Remote server database name
|
124
|
-
- `None`: When parameters `host`, `port`, `username`, `password`, `database` are all `None`, then using memory store.
|
58
|
+
database : Remote server database name.
|
125
59
|
pool_size : Number of connections `keep open`.
|
126
60
|
max_overflow : Number of connections `allowed overflow`.
|
127
61
|
pool_timeout : Number of seconds `wait create` connection.
|
@@ -158,94 +92,12 @@ class Database(DatabaseBase):
|
|
158
92
|
|
159
93
|
# Server recycle time.
|
160
94
|
if pool_recycle is None:
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
self.pool_recycle = int(wait_timeout)
|
95
|
+
wait_timeout = self.variables['wait_timeout']
|
96
|
+
if wait_timeout is not None:
|
97
|
+
self.pool_recycle = int(wait_timeout)
|
165
98
|
self.engine.pool._recycle = self.pool_recycle
|
166
99
|
|
167
100
|
|
168
|
-
@overload
|
169
|
-
def extract_path(
|
170
|
-
self,
|
171
|
-
path: str,
|
172
|
-
main: Literal['table', 'database'] = 'table'
|
173
|
-
) -> tuple[str, str, str | None]: ...
|
174
|
-
|
175
|
-
@overload
|
176
|
-
def extract_path(
|
177
|
-
self,
|
178
|
-
path: tuple[str | None, str | None] | tuple[str | None, str | None, str | None],
|
179
|
-
main: Literal['table', 'database'] = 'table'
|
180
|
-
) -> tuple[str, str | None, str | None]: ...
|
181
|
-
|
182
|
-
def extract_path(
|
183
|
-
self,
|
184
|
-
path: str | tuple[str | None, str | None] | tuple[str | None, str | None, str | None],
|
185
|
-
main: Literal['table', 'database'] = 'table'
|
186
|
-
) -> tuple[str, str | None, str | None]:
|
187
|
-
"""
|
188
|
-
Extract table name and database name and column name from path.
|
189
|
-
|
190
|
-
Parameters
|
191
|
-
----------
|
192
|
-
path : Table name, can contain database name, otherwise use `self.rdatabase.database`.
|
193
|
-
- `str`: Automatic extract database name and table name.
|
194
|
-
Not contain '.' or contain '`': Main name.
|
195
|
-
Contain '.': Database name and table name, column name is optional. Example 'database.table[.column]'.
|
196
|
-
- `tuple[str, str]`: Database name and table name.
|
197
|
-
- `tuple[str, str | None, str | None]`: Database name and table name and column name.
|
198
|
-
path : Automatic extract.
|
199
|
-
main : Priority main name, 'table' or 'database'.
|
200
|
-
|
201
|
-
Returns
|
202
|
-
-------
|
203
|
-
Database name and table name and column name.
|
204
|
-
"""
|
205
|
-
|
206
|
-
# Type str.
|
207
|
-
if type(path) == str:
|
208
|
-
|
209
|
-
## Single.
|
210
|
-
if (
|
211
|
-
'.' not in path
|
212
|
-
or '`' in path
|
213
|
-
):
|
214
|
-
name = path.replace('`', '')
|
215
|
-
match main:
|
216
|
-
case 'table':
|
217
|
-
names = (self.database, name, None)
|
218
|
-
case 'database':
|
219
|
-
names = (name, None, None)
|
220
|
-
case _:
|
221
|
-
throw(ValueError, main)
|
222
|
-
|
223
|
-
## Multiple.
|
224
|
-
else:
|
225
|
-
names = path.split('.', 2)
|
226
|
-
if len(names) == 2:
|
227
|
-
names.append(None)
|
228
|
-
names = tuple(names)
|
229
|
-
|
230
|
-
# Type tuple.
|
231
|
-
else:
|
232
|
-
if len(path) == 2:
|
233
|
-
path += (None,)
|
234
|
-
if path[0] is None:
|
235
|
-
path = (self.database,) + names[1:]
|
236
|
-
names = path
|
237
|
-
|
238
|
-
# SQLite.
|
239
|
-
if self.backend == 'sqlite':
|
240
|
-
names = ('main',) + names[1:]
|
241
|
-
|
242
|
-
# Check.
|
243
|
-
if names[0] is None:
|
244
|
-
throw(ValueError, names)
|
245
|
-
|
246
|
-
return names
|
247
|
-
|
248
|
-
|
249
101
|
@property
|
250
102
|
def backend(self) -> str:
|
251
103
|
"""
|
@@ -280,32 +132,6 @@ class Database(DatabaseBase):
|
|
280
132
|
return driver
|
281
133
|
|
282
134
|
|
283
|
-
@property
|
284
|
-
def mode(self) -> Literal['server', 'file', 'memory']:
|
285
|
-
"""
|
286
|
-
Database store mode.
|
287
|
-
|
288
|
-
Returns
|
289
|
-
-------
|
290
|
-
Mode.
|
291
|
-
"""
|
292
|
-
|
293
|
-
# Judge.
|
294
|
-
if (
|
295
|
-
self.username is not None
|
296
|
-
and self.password is not None
|
297
|
-
and self.host is not None
|
298
|
-
and self.port is not None
|
299
|
-
):
|
300
|
-
value = 'server'
|
301
|
-
elif self.database not in (None, ':memory:'):
|
302
|
-
value = 'file'
|
303
|
-
else:
|
304
|
-
value = 'memory'
|
305
|
-
|
306
|
-
return value
|
307
|
-
|
308
|
-
|
309
135
|
@property
|
310
136
|
def url(self) -> str:
|
311
137
|
"""
|
@@ -317,21 +143,8 @@ class Database(DatabaseBase):
|
|
317
143
|
"""
|
318
144
|
|
319
145
|
# Generate URL.
|
320
|
-
|
321
|
-
|
322
|
-
if self.mode == 'server':
|
323
|
-
password = urllib_quote(self.password)
|
324
|
-
url_ = f'mysql+pymysql://{self.username}:{password}@{self.host}:{self.port}'
|
325
|
-
if self.database is not None:
|
326
|
-
url_ = f'{url_}/{self.database}'
|
327
|
-
|
328
|
-
## File.
|
329
|
-
elif self.mode == 'file':
|
330
|
-
url_ = f'sqlite:///{self.database}'
|
331
|
-
|
332
|
-
## Memory.
|
333
|
-
else:
|
334
|
-
url_ = f'sqlite:///:memory:'
|
146
|
+
password = urllib_quote(self.password)
|
147
|
+
url_ = f'mysql+pymysql://{self.username}:{password}@{self.host}:{self.port}/{self.database}'
|
335
148
|
|
336
149
|
# Add Server parameter.
|
337
150
|
if self.query != {}:
|
@@ -356,20 +169,14 @@ class Database(DatabaseBase):
|
|
356
169
|
"""
|
357
170
|
|
358
171
|
# Handle parameter.
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
'pool_size': self.pool_size,
|
368
|
-
'max_overflow': self.max_overflow,
|
369
|
-
'pool_timeout': self.pool_timeout,
|
370
|
-
'pool_recycle': self.pool_recycle,
|
371
|
-
'connect_args': {'client_flag': MULTI_STATEMENTS}
|
372
|
-
}
|
172
|
+
engine_params = {
|
173
|
+
'url': self.url,
|
174
|
+
'pool_size': self.pool_size,
|
175
|
+
'max_overflow': self.max_overflow,
|
176
|
+
'pool_timeout': self.pool_timeout,
|
177
|
+
'pool_recycle': self.pool_recycle,
|
178
|
+
'connect_args': {'client_flag': MULTI_STATEMENTS}
|
179
|
+
}
|
373
180
|
|
374
181
|
# Create Engine.
|
375
182
|
engine = sqlalchemy_create_engine(**engine_params)
|
@@ -378,7 +185,7 @@ class Database(DatabaseBase):
|
|
378
185
|
|
379
186
|
|
380
187
|
@property
|
381
|
-
def
|
188
|
+
def count_conn(self) -> tuple[int, int]:
|
382
189
|
"""
|
383
190
|
Count number of keep open and allowed overflow connection.
|
384
191
|
|
@@ -387,1090 +194,25 @@ class Database(DatabaseBase):
|
|
387
194
|
Number of keep open and allowed overflow connection.
|
388
195
|
"""
|
389
196
|
|
390
|
-
# Handle parameter.
|
391
|
-
if hasattr(self, 'engine'):
|
392
|
-
rdatabase = self
|
393
|
-
else:
|
394
|
-
rdatabase: Database = self.rdatabase
|
395
|
-
|
396
197
|
# Count.
|
397
|
-
_overflow =
|
198
|
+
_overflow = self.engine.pool._overflow
|
398
199
|
if _overflow < 0:
|
399
|
-
keep_n =
|
200
|
+
keep_n = self.pool_size + _overflow
|
400
201
|
overflow_n = 0
|
401
202
|
else:
|
402
|
-
keep_n =
|
203
|
+
keep_n = self.pool_size
|
403
204
|
overflow_n = _overflow
|
404
205
|
|
405
206
|
return keep_n, overflow_n
|
406
207
|
|
407
208
|
|
408
|
-
def
|
409
|
-
"""
|
410
|
-
Handle SQL.
|
411
|
-
|
412
|
-
Parameters
|
413
|
-
----------
|
414
|
-
sql : SQL in method `sqlalchemy.text` format, or TextClause object.
|
415
|
-
|
416
|
-
Returns
|
417
|
-
-------
|
418
|
-
TextClause instance.
|
419
|
-
"""
|
420
|
-
|
421
|
-
# Handle parameter.
|
422
|
-
if type(sql) == TextClause:
|
423
|
-
sql = sql.text
|
424
|
-
|
425
|
-
# Handle.
|
426
|
-
sql = sql.strip()
|
427
|
-
if sql[-1] != ';':
|
428
|
-
sql += ';'
|
429
|
-
sql = sqlalchemy_text(sql)
|
430
|
-
|
431
|
-
return sql
|
432
|
-
|
433
|
-
|
434
|
-
def handle_data(
|
435
|
-
self,
|
436
|
-
data: list[dict],
|
437
|
-
sql: str | TextClause,
|
438
|
-
) -> list[dict]:
|
209
|
+
def connect(self, autocommit: bool = False):
|
439
210
|
"""
|
440
|
-
|
441
|
-
|
442
|
-
Parameters
|
443
|
-
----------
|
444
|
-
data : Data set for filling.
|
445
|
-
sql : SQL in method `sqlalchemy.text` format, or TextClause object.
|
446
|
-
|
447
|
-
Returns
|
448
|
-
-------
|
449
|
-
Filled data.
|
450
|
-
"""
|
451
|
-
|
452
|
-
# Handle parameter.
|
453
|
-
if type(sql) == TextClause:
|
454
|
-
sql = sql.text
|
455
|
-
|
456
|
-
# Extract keys.
|
457
|
-
pattern = '(?<!\\\\):(\\w+)'
|
458
|
-
sql_keys = findall(pattern, sql)
|
459
|
-
|
460
|
-
# Extract keys of syntax "in".
|
461
|
-
pattern = '[iI][nN]\\s+(?<!\\\\):(\\w+)'
|
462
|
-
sql_keys_in = findall(pattern, sql)
|
463
|
-
|
464
|
-
# Loop.
|
465
|
-
for row in data:
|
466
|
-
if row == {}:
|
467
|
-
continue
|
468
|
-
for key in sql_keys:
|
469
|
-
value = row.get(key)
|
470
|
-
|
471
|
-
# Empty string.
|
472
|
-
if value == '':
|
473
|
-
value = None
|
474
|
-
|
475
|
-
# Convert.
|
476
|
-
elif (
|
477
|
-
type(value) in (list, dict)
|
478
|
-
and key not in sql_keys_in
|
479
|
-
):
|
480
|
-
value = to_json(value)
|
481
|
-
|
482
|
-
# Enum.
|
483
|
-
elif isinstance(type(value), EnumType):
|
484
|
-
value = value.value
|
485
|
-
|
486
|
-
row[key] = value
|
487
|
-
|
488
|
-
return data
|
489
|
-
|
490
|
-
|
491
|
-
def get_syntax(self, sql: str | TextClause) -> list[str]:
|
492
|
-
"""
|
493
|
-
Extract SQL syntax type for each segment form SQL.
|
494
|
-
|
495
|
-
Parameters
|
496
|
-
----------
|
497
|
-
sql : SQL text or TextClause object.
|
498
|
-
|
499
|
-
Returns
|
500
|
-
-------
|
501
|
-
SQL syntax type for each segment.
|
502
|
-
"""
|
503
|
-
|
504
|
-
# Handle parameter.
|
505
|
-
if type(sql) == TextClause:
|
506
|
-
sql = sql.text
|
507
|
-
|
508
|
-
# Extract.
|
509
|
-
syntax = [
|
510
|
-
search('[a-zA-Z]+', sql_part).upper()
|
511
|
-
for sql_part in sql.split(';')
|
512
|
-
if sql_part != ''
|
513
|
-
]
|
514
|
-
|
515
|
-
return syntax
|
516
|
-
|
517
|
-
|
518
|
-
def is_multi_sql(self, sql: str | TextClause) -> bool:
|
519
|
-
"""
|
520
|
-
Judge whether it is multi segment SQL.
|
521
|
-
|
522
|
-
Parameters
|
523
|
-
----------
|
524
|
-
sql : SQL text or TextClause object.
|
525
|
-
|
526
|
-
Returns
|
527
|
-
-------
|
528
|
-
Judgment result.
|
529
|
-
"""
|
530
|
-
|
531
|
-
# Handle parameter.
|
532
|
-
if type(sql) == TextClause:
|
533
|
-
sql = sql.text
|
534
|
-
|
535
|
-
# Judge.
|
536
|
-
if ';' in sql.rstrip()[:-1]:
|
537
|
-
return True
|
538
|
-
return False
|
539
|
-
|
540
|
-
|
541
|
-
def executor_report(
|
542
|
-
self,
|
543
|
-
connection: Connection,
|
544
|
-
sql: TextClause,
|
545
|
-
data: list[dict]
|
546
|
-
) -> Result:
|
547
|
-
"""
|
548
|
-
SQL executor and report SQL execute information
|
549
|
-
|
550
|
-
Parameters
|
551
|
-
----------
|
552
|
-
connection : Connection object.
|
553
|
-
sql : TextClause object.
|
554
|
-
data : Data set for filling.
|
555
|
-
|
556
|
-
Returns
|
557
|
-
-------
|
558
|
-
Result object.
|
559
|
-
"""
|
560
|
-
|
561
|
-
# Execute.
|
562
|
-
execute = wrap_runtime(connection.execute, to_return=True)
|
563
|
-
result, report_runtime, *_ = execute(sql, data)
|
564
|
-
|
565
|
-
# Report.
|
566
|
-
report_info = (
|
567
|
-
f'{report_runtime}\n'
|
568
|
-
f'Row Count: {result.rowcount}'
|
569
|
-
)
|
570
|
-
sqls = [
|
571
|
-
sql_part.strip()
|
572
|
-
for sql_part in sql.text.split(';')
|
573
|
-
if sql_part != ''
|
574
|
-
]
|
575
|
-
if data == []:
|
576
|
-
echo(report_info, *sqls, title='SQL')
|
577
|
-
else:
|
578
|
-
echo(report_info, *sqls, data, title='SQL')
|
579
|
-
|
580
|
-
return result
|
581
|
-
|
582
|
-
|
583
|
-
def executor(
|
584
|
-
self,
|
585
|
-
sql: TextClause,
|
586
|
-
data: list[dict],
|
587
|
-
report: bool
|
588
|
-
) -> Result:
|
589
|
-
"""
|
590
|
-
SQL executor.
|
591
|
-
|
592
|
-
Parameters
|
593
|
-
----------
|
594
|
-
sql : TextClause object.
|
595
|
-
data : Data set for filling.
|
596
|
-
report : Whether report SQL execute information.
|
597
|
-
|
598
|
-
Returns
|
599
|
-
-------
|
600
|
-
Result object.
|
601
|
-
"""
|
602
|
-
|
603
|
-
# Create connection.
|
604
|
-
with self.engine.connect() as connection:
|
605
|
-
|
606
|
-
# Create transaction.
|
607
|
-
with connection.begin():
|
608
|
-
|
609
|
-
# Execute.
|
610
|
-
|
611
|
-
## Report.
|
612
|
-
if report:
|
613
|
-
result = self.executor_report(connection, sql, data)
|
614
|
-
|
615
|
-
## Not report.
|
616
|
-
else:
|
617
|
-
result = connection.execute(sql, data)
|
618
|
-
|
619
|
-
return result
|
620
|
-
|
621
|
-
|
622
|
-
def execute(
|
623
|
-
self,
|
624
|
-
sql: str | TextClause,
|
625
|
-
data: TableData | None = None,
|
626
|
-
report: bool | None = None,
|
627
|
-
**kwdata: Any
|
628
|
-
) -> Result:
|
629
|
-
"""
|
630
|
-
Execute SQL.
|
631
|
-
|
632
|
-
Parameters
|
633
|
-
----------
|
634
|
-
sql : SQL in method `sqlalchemy.text` format, or `TextClause` object.
|
635
|
-
data : Data set for filling.
|
636
|
-
report : Whether report SQL execute information.
|
637
|
-
- `None`: Use attribute `default_report`.
|
638
|
-
- `bool`: Use this value.
|
639
|
-
kwdata : Keyword parameters for filling.
|
640
|
-
|
641
|
-
Returns
|
642
|
-
-------
|
643
|
-
Result object.
|
644
|
-
"""
|
645
|
-
|
646
|
-
# Handle parameter by priority.
|
647
|
-
report = get_first_notnone(report, self.default_report)
|
648
|
-
|
649
|
-
# Handle parameter.
|
650
|
-
sql = self.handle_sql(sql)
|
651
|
-
if data is None:
|
652
|
-
if kwdata == {}:
|
653
|
-
data = []
|
654
|
-
else:
|
655
|
-
data = [kwdata]
|
656
|
-
else:
|
657
|
-
data_table = Table(data)
|
658
|
-
data = data_table.to_table()
|
659
|
-
for row in data:
|
660
|
-
row.update(kwdata)
|
661
|
-
data = self.handle_data(data, sql)
|
662
|
-
|
663
|
-
# Execute.
|
664
|
-
result = self.executor(sql, data, report)
|
665
|
-
|
666
|
-
return result
|
667
|
-
|
668
|
-
|
669
|
-
def execute_select(
|
670
|
-
self,
|
671
|
-
path: str | tuple[str, str],
|
672
|
-
fields: str | Iterable[str] | None = None,
|
673
|
-
where: str | None = None,
|
674
|
-
group: str | None = None,
|
675
|
-
having: str | None = None,
|
676
|
-
order: str | None = None,
|
677
|
-
limit: int | str | tuple[int, int] | None = None,
|
678
|
-
report: bool | None = None,
|
679
|
-
**kwdata: Any
|
680
|
-
) -> Result:
|
681
|
-
"""
|
682
|
-
Execute select SQL.
|
683
|
-
|
684
|
-
Parameters
|
685
|
-
----------
|
686
|
-
path : Table name, can contain database name, otherwise use `self.database`.
|
687
|
-
- `str`: Automatic extract database name and table name.
|
688
|
-
- `tuple[str, str]`: Database name and table name.
|
689
|
-
fields : Select clause content.
|
690
|
-
- `None`: Is `SELECT *`.
|
691
|
-
- `str`: Join as `SELECT str`.
|
692
|
-
- `Iterable[str]`, Join as `SELECT ``str``: ...`.
|
693
|
-
`str and first character is ':'`: Use this syntax.
|
694
|
-
`str`: Use this field.
|
695
|
-
where : Clause `WHERE` content, join as `WHERE str`.
|
696
|
-
group : Clause `GROUP BY` content, join as `GROUP BY str`.
|
697
|
-
having : Clause `HAVING` content, join as `HAVING str`.
|
698
|
-
order : Clause `ORDER BY` content, join as `ORDER BY str`.
|
699
|
-
limit : Clause `LIMIT` content.
|
700
|
-
- `int | str`: Join as `LIMIT int/str`.
|
701
|
-
- `tuple[int, int]`: Join as `LIMIT int, int`.
|
702
|
-
report : Whether report SQL execute information.
|
703
|
-
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
704
|
-
- `int`: Use this value.
|
705
|
-
kwdata : Keyword parameters for filling.
|
706
|
-
|
707
|
-
Returns
|
708
|
-
-------
|
709
|
-
Result object.
|
710
|
-
|
711
|
-
Examples
|
712
|
-
--------
|
713
|
-
Parameter `fields`.
|
714
|
-
>>> fields = ['id', ':`id` + 1 AS `id_`']
|
715
|
-
>>> result = Database.execute_select('database.table', fields)
|
716
|
-
>>> print(result.to_table())
|
717
|
-
[{'id': 1, 'id_': 2}, ...]
|
718
|
-
|
719
|
-
Parameter `kwdata`.
|
720
|
-
>>> fields = '`id`, `id` + :value AS `id_`'
|
721
|
-
>>> result = Database.execute_select('database.table', fields, value=1)
|
722
|
-
>>> print(result.to_table())
|
723
|
-
[{'id': 1, 'id_': 2}, ...]
|
724
|
-
"""
|
725
|
-
|
726
|
-
# Handle parameter.
|
727
|
-
database, table, _ = self.extract_path(path)
|
728
|
-
|
729
|
-
# Generate SQL.
|
730
|
-
sql_list = []
|
731
|
-
|
732
|
-
## Part 'SELECT' syntax.
|
733
|
-
if fields is None:
|
734
|
-
fields = '*'
|
735
|
-
elif type(fields) != str:
|
736
|
-
fields = ', '.join(
|
737
|
-
[
|
738
|
-
field[1:]
|
739
|
-
if (
|
740
|
-
field.startswith(':')
|
741
|
-
and field != ':'
|
742
|
-
)
|
743
|
-
else f'`{field}`'
|
744
|
-
for field in fields
|
745
|
-
]
|
746
|
-
)
|
747
|
-
sql_select = f'SELECT {fields}'
|
748
|
-
sql_list.append(sql_select)
|
749
|
-
|
750
|
-
## Part 'FROM' syntax.
|
751
|
-
sql_from = f'FROM `{database}`.`{table}`'
|
752
|
-
sql_list.append(sql_from)
|
753
|
-
|
754
|
-
## Part 'WHERE' syntax.
|
755
|
-
if where is not None:
|
756
|
-
sql_where = f'WHERE {where}'
|
757
|
-
sql_list.append(sql_where)
|
758
|
-
|
759
|
-
## Part 'GROUP BY' syntax.
|
760
|
-
if group is not None:
|
761
|
-
sql_group = f'GROUP BY {group}'
|
762
|
-
sql_list.append(sql_group)
|
763
|
-
|
764
|
-
## Part 'GROUP BY' syntax.
|
765
|
-
if having is not None:
|
766
|
-
sql_having = f'HAVING {having}'
|
767
|
-
sql_list.append(sql_having)
|
768
|
-
|
769
|
-
## Part 'ORDER BY' syntax.
|
770
|
-
if order is not None:
|
771
|
-
sql_order = f'ORDER BY {order}'
|
772
|
-
sql_list.append(sql_order)
|
773
|
-
|
774
|
-
## Part 'LIMIT' syntax.
|
775
|
-
if limit is not None:
|
776
|
-
if type(limit) in (str, int):
|
777
|
-
sql_limit = f'LIMIT {limit}'
|
778
|
-
else:
|
779
|
-
if len(limit) == 2:
|
780
|
-
sql_limit = f'LIMIT {limit[0]}, {limit[1]}'
|
781
|
-
else:
|
782
|
-
throw(ValueError, limit)
|
783
|
-
sql_list.append(sql_limit)
|
784
|
-
|
785
|
-
## Join sql part.
|
786
|
-
sql = '\n'.join(sql_list)
|
787
|
-
|
788
|
-
# Execute SQL.
|
789
|
-
result = self.execute(sql, report=report, **kwdata)
|
790
|
-
|
791
|
-
return result
|
792
|
-
|
793
|
-
|
794
|
-
def execute_insert(
|
795
|
-
self,
|
796
|
-
path: str | tuple[str, str],
|
797
|
-
data: TableData,
|
798
|
-
duplicate: Literal['ignore', 'update'] | Container[str] | None = None,
|
799
|
-
report: bool | None = None,
|
800
|
-
**kwdata: Any
|
801
|
-
) -> Result:
|
802
|
-
"""
|
803
|
-
Insert the data of table in the datebase.
|
804
|
-
|
805
|
-
Parameters
|
806
|
-
----------
|
807
|
-
path : Table name, can contain database name, otherwise use `self.database`.
|
808
|
-
- `str`: Automatic extract database name and table name.
|
809
|
-
- `tuple[str, str]`: Database name and table name.
|
810
|
-
data : Insert data.
|
811
|
-
duplicate : Handle method when constraint error.
|
812
|
-
- `None`: Not handled.
|
813
|
-
- `ignore`: Use `UPDATE IGNORE INTO` clause.
|
814
|
-
- `update`: Use `ON DUPLICATE KEY UPDATE` clause and update all fields.
|
815
|
-
- `Container[str]`: Use `ON DUPLICATE KEY UPDATE` clause and update this fields.
|
816
|
-
report : Whether report SQL execute information.
|
817
|
-
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
818
|
-
- `int`: Use this value.
|
819
|
-
kwdata : Keyword parameters for filling.
|
820
|
-
- `str and first character is ':'`: Use this syntax.
|
821
|
-
- `Any`: Use this value.
|
822
|
-
|
823
|
-
Returns
|
824
|
-
-------
|
825
|
-
Result object.
|
826
|
-
|
827
|
-
Examples
|
828
|
-
--------
|
829
|
-
Parameter `data` and `kwdata`.
|
830
|
-
>>> data = [{'key': 'a'}, {'key': 'b'}]
|
831
|
-
>>> kwdata = {'value1': 1, 'value2': ':(SELECT 2)'}
|
832
|
-
>>> result = Database.execute_insert('database.table', data, **kwdata)
|
833
|
-
>>> print(result.rowcount)
|
834
|
-
2
|
835
|
-
>>> result = Database.execute_select('database.table')
|
836
|
-
>>> print(result.to_table())
|
837
|
-
[{'key': 'a', 'value1': 1, 'value2': 2}, {'key': 'b', 'value1': 1, 'value2': 2}]
|
838
|
-
"""
|
839
|
-
|
840
|
-
# Handle parameter.
|
841
|
-
database, table, _ = self.extract_path(path)
|
842
|
-
|
843
|
-
# Handle parameter.
|
844
|
-
|
845
|
-
## Data.
|
846
|
-
data_table = Table(data)
|
847
|
-
data = data_table.to_table()
|
848
|
-
|
849
|
-
## Check.
|
850
|
-
if data in ([], [{}]):
|
851
|
-
throw(ValueError, data)
|
852
|
-
|
853
|
-
## Keyword data.
|
854
|
-
kwdata_method = {}
|
855
|
-
kwdata_replace = {}
|
856
|
-
for key, value in kwdata.items():
|
857
|
-
if (
|
858
|
-
type(value) == str
|
859
|
-
and value.startswith(':')
|
860
|
-
and value != ':'
|
861
|
-
):
|
862
|
-
kwdata_method[key] = value[1:]
|
863
|
-
else:
|
864
|
-
kwdata_replace[key] = value
|
865
|
-
|
866
|
-
# Generate SQL.
|
867
|
-
|
868
|
-
## Part 'fields' syntax.
|
869
|
-
fields_replace = {
|
870
|
-
field
|
871
|
-
for row in data
|
872
|
-
for field in row
|
873
|
-
}
|
874
|
-
fields_replace = {
|
875
|
-
field
|
876
|
-
for field in fields_replace
|
877
|
-
if field not in kwdata
|
878
|
-
}
|
879
|
-
sql_fields_list = (
|
880
|
-
*kwdata_method,
|
881
|
-
*kwdata_replace,
|
882
|
-
*fields_replace
|
883
|
-
)
|
884
|
-
sql_fields = ', '.join(
|
885
|
-
[
|
886
|
-
f'`{field}`'
|
887
|
-
for field in sql_fields_list
|
888
|
-
]
|
889
|
-
)
|
890
|
-
|
891
|
-
## Part 'values' syntax.
|
892
|
-
sql_values_list = (
|
893
|
-
*kwdata_method.values(),
|
894
|
-
*[
|
895
|
-
':' + field
|
896
|
-
for field in (
|
897
|
-
*kwdata_replace,
|
898
|
-
*fields_replace
|
899
|
-
)
|
900
|
-
]
|
901
|
-
)
|
902
|
-
sql_values = ', '.join(sql_values_list)
|
903
|
-
|
904
|
-
## Join sql part.
|
905
|
-
match duplicate:
|
906
|
-
|
907
|
-
### Not handle.
|
908
|
-
case None:
|
909
|
-
sql = (
|
910
|
-
f'INSERT INTO `{database}`.`{table}`({sql_fields})\n'
|
911
|
-
f'VALUES({sql_values})'
|
912
|
-
)
|
913
|
-
|
914
|
-
### Ignore.
|
915
|
-
case 'ignore':
|
916
|
-
sql = (
|
917
|
-
f'INSERT IGNORE INTO `{database}`.`{table}`({sql_fields})\n'
|
918
|
-
f'VALUES({sql_values})'
|
919
|
-
)
|
920
|
-
|
921
|
-
### Update.
|
922
|
-
case _:
|
923
|
-
sql_fields_list_update = sql_fields_list
|
924
|
-
if duplicate != 'update':
|
925
|
-
sql_fields_list_update = [
|
926
|
-
field
|
927
|
-
for field in sql_fields_list
|
928
|
-
if field in duplicate
|
929
|
-
]
|
930
|
-
update_content = ',\n '.join(
|
931
|
-
[
|
932
|
-
f'`{field}` = VALUES(`{field}`)'
|
933
|
-
for field in sql_fields_list_update
|
934
|
-
]
|
935
|
-
)
|
936
|
-
sql = (
|
937
|
-
f'INSERT INTO `{database}`.`{table}`({sql_fields})\n'
|
938
|
-
f'VALUES({sql_values})\n'
|
939
|
-
'ON DUPLICATE KEY UPDATE\n'
|
940
|
-
f' {update_content}'
|
941
|
-
)
|
942
|
-
|
943
|
-
# Execute SQL.
|
944
|
-
result = self.execute(sql, data, report, **kwdata_replace)
|
945
|
-
|
946
|
-
return result
|
947
|
-
|
948
|
-
|
949
|
-
def execute_update(
|
950
|
-
self,
|
951
|
-
path: str | tuple[str, str],
|
952
|
-
data: TableData,
|
953
|
-
where_fields: str | Iterable[str] | None = None,
|
954
|
-
report: bool | None = None,
|
955
|
-
**kwdata: Any
|
956
|
-
) -> Result:
|
957
|
-
"""
|
958
|
-
Update the data of table in the datebase.
|
959
|
-
|
960
|
-
Parameters
|
961
|
-
----------
|
962
|
-
path : Table name, can contain database name, otherwise use `self.database`.
|
963
|
-
- `str`: Automatic extract database name and table name.
|
964
|
-
- `tuple[str, str]`: Database name and table name.
|
965
|
-
data : Update data, clause `SET` and `WHERE` and `ORDER BY` and `LIMIT` content.
|
966
|
-
- `Key`: Table field.
|
967
|
-
`literal['order']`: Clause `ORDER BY` content, join as `ORDER BY str`.
|
968
|
-
`literal['limit']`: Clause `LIMIT` content, join as `LIMIT str`.
|
969
|
-
`Other`: Clause `SET` and `WHERE` content.
|
970
|
-
- `Value`: Table value.
|
971
|
-
`list | tuple`: Join as `field IN :str`.
|
972
|
-
`Any`: Join as `field = :str`.
|
973
|
-
where_fields : Clause `WHERE` content fields.
|
974
|
-
- `None`: The first key value pair of each item is judged.
|
975
|
-
- `str`: This key value pair of each item is judged.
|
976
|
-
- `Iterable[str]`: Multiple judged, `and`: relationship.
|
977
|
-
report : Whether report SQL execute information.
|
978
|
-
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
979
|
-
- `int`: Use this value.
|
980
|
-
kwdata : Keyword parameters for filling.
|
981
|
-
- `str and first character is ':'`: Use this syntax.
|
982
|
-
- `Any`: Use this value.
|
983
|
-
|
984
|
-
Returns
|
985
|
-
-------
|
986
|
-
Result object.
|
987
|
-
|
988
|
-
Examples
|
989
|
-
--------
|
990
|
-
Parameter `data` and `kwdata`.
|
991
|
-
>>> data = [{'key': 'a'}, {'key': 'b'}]
|
992
|
-
>>> kwdata = {'value': 1, 'name': ':`key`'}
|
993
|
-
>>> result = Database.execute_update('database.table', data, **kwdata)
|
994
|
-
>>> print(result.rowcount)
|
995
|
-
2
|
996
|
-
>>> result = Database.execute_select('database.table')
|
997
|
-
>>> print(result.to_table())
|
998
|
-
[{'key': 'a', 'value': 1, 'name': 'a'}, {'key': 'b', 'value': 1, 'name': 'b'}]
|
999
|
-
"""
|
1000
|
-
|
1001
|
-
# Handle parameter.
|
1002
|
-
database, table, _ = self.extract_path(path)
|
1003
|
-
|
1004
|
-
# Handle parameter.
|
1005
|
-
|
1006
|
-
## Data.
|
1007
|
-
data_table = Table(data)
|
1008
|
-
data = data_table.to_table()
|
1009
|
-
|
1010
|
-
## Check.
|
1011
|
-
if data in ([], [{}]):
|
1012
|
-
throw(ValueError, data)
|
1013
|
-
|
1014
|
-
## Keyword data.
|
1015
|
-
kwdata_method = {}
|
1016
|
-
kwdata_replace = {}
|
1017
|
-
for key, value in kwdata.items():
|
1018
|
-
if (
|
1019
|
-
type(value) == str
|
1020
|
-
and value.startswith(':')
|
1021
|
-
and value != ':'
|
1022
|
-
):
|
1023
|
-
kwdata_method[key] = value[1:]
|
1024
|
-
else:
|
1025
|
-
kwdata_replace[key] = value
|
1026
|
-
sql_set_list_kwdata = [
|
1027
|
-
f'`{key}` = {value}'
|
1028
|
-
for key, value in kwdata_method.items()
|
1029
|
-
]
|
1030
|
-
sql_set_list_kwdata.extend(
|
1031
|
-
[
|
1032
|
-
f'`{key}` = :{key}'
|
1033
|
-
for key in kwdata_replace
|
1034
|
-
]
|
1035
|
-
)
|
1036
|
-
|
1037
|
-
# Generate SQL.
|
1038
|
-
data_flatten = kwdata_replace
|
1039
|
-
if where_fields is None:
|
1040
|
-
no_where = True
|
1041
|
-
else:
|
1042
|
-
no_where = False
|
1043
|
-
if type(where_fields) == str:
|
1044
|
-
where_fields = [where_fields]
|
1045
|
-
sqls_list = []
|
1046
|
-
sql_update = f'UPDATE `{database}`.`{table}`'
|
1047
|
-
for index, row in enumerate(data):
|
1048
|
-
sql_parts = [sql_update]
|
1049
|
-
for key, value in row.items():
|
1050
|
-
if key in ('order', 'limit'):
|
1051
|
-
continue
|
1052
|
-
index_key = f'{index}_{key}'
|
1053
|
-
data_flatten[index_key] = value
|
1054
|
-
if no_where:
|
1055
|
-
for key in row:
|
1056
|
-
where_fields = [key]
|
1057
|
-
break
|
1058
|
-
|
1059
|
-
## Part 'SET' syntax.
|
1060
|
-
sql_set_list = sql_set_list_kwdata.copy()
|
1061
|
-
sql_set_list.extend(
|
1062
|
-
[
|
1063
|
-
f'`{key}` = :{index}_{key}'
|
1064
|
-
for key in row
|
1065
|
-
if (
|
1066
|
-
key not in where_fields
|
1067
|
-
and key not in kwdata
|
1068
|
-
and key not in ('order', 'limit')
|
1069
|
-
)
|
1070
|
-
]
|
1071
|
-
)
|
1072
|
-
sql_set = 'SET ' + ',\n '.join(sql_set_list)
|
1073
|
-
sql_parts.append(sql_set)
|
1074
|
-
|
1075
|
-
## Part 'WHERE' syntax.
|
1076
|
-
sql_where_list = []
|
1077
|
-
for field in where_fields:
|
1078
|
-
index_field = f'{index}_{field}'
|
1079
|
-
index_value = data_flatten[index_field]
|
1080
|
-
if type(index_value) in (list, tuple):
|
1081
|
-
sql_where_part = f'`{field}` IN :{index_field}'
|
1082
|
-
else:
|
1083
|
-
sql_where_part = f'`{field}` = :{index_field}'
|
1084
|
-
sql_where_list.append(sql_where_part)
|
1085
|
-
sql_where = 'WHERE ' + '\n AND '.join(sql_where_list)
|
1086
|
-
sql_parts.append(sql_where)
|
1087
|
-
|
1088
|
-
## Part 'ORDER BY' syntax.
|
1089
|
-
order = row.get('order')
|
1090
|
-
if order is not None:
|
1091
|
-
sql_order = f'ORDER BY {order}'
|
1092
|
-
sql_parts.append(sql_order)
|
1093
|
-
|
1094
|
-
## Part 'LIMIT' syntax.
|
1095
|
-
limit = row.get('limit')
|
1096
|
-
if limit is not None:
|
1097
|
-
sql_limit = f'LIMIT {limit}'
|
1098
|
-
sql_parts.append(sql_limit)
|
1099
|
-
|
1100
|
-
## Join sql part.
|
1101
|
-
sql = '\n'.join(sql_parts)
|
1102
|
-
sqls_list.append(sql)
|
1103
|
-
|
1104
|
-
## Join sqls.
|
1105
|
-
sqls = ';\n'.join(sqls_list)
|
1106
|
-
|
1107
|
-
# Execute SQL.
|
1108
|
-
result = self.execute(sqls, data_flatten, report)
|
1109
|
-
|
1110
|
-
return result
|
1111
|
-
|
1112
|
-
|
1113
|
-
def execute_delete(
|
1114
|
-
self,
|
1115
|
-
path: str | tuple[str, str],
|
1116
|
-
where: str | None = None,
|
1117
|
-
order: str | None = None,
|
1118
|
-
limit: int | str | None = None,
|
1119
|
-
report: bool | None = None,
|
1120
|
-
**kwdata: Any
|
1121
|
-
) -> Result:
|
1122
|
-
"""
|
1123
|
-
Delete the data of table in the datebase.
|
1124
|
-
|
1125
|
-
Parameters
|
1126
|
-
----------
|
1127
|
-
path : Table name, can contain database name, otherwise use `self.database`.
|
1128
|
-
- `str`: Automatic extract database name and table name.
|
1129
|
-
- `tuple[str, str]`: Database name and table name.
|
1130
|
-
where : Clause `WHERE` content, join as `WHERE str`.
|
1131
|
-
order : Clause `ORDER BY` content, join as `ORDER BY str`.
|
1132
|
-
limit : Clause `LIMIT` content, join as `LIMIT int/str`.
|
1133
|
-
report : Whether report SQL execute information.
|
1134
|
-
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1135
|
-
- `int`: Use this value.
|
1136
|
-
kwdata : Keyword parameters for filling.
|
1137
|
-
|
1138
|
-
Returns
|
1139
|
-
-------
|
1140
|
-
Result object.
|
1141
|
-
|
1142
|
-
Examples
|
1143
|
-
--------
|
1144
|
-
Parameter `where` and `kwdata`.
|
1145
|
-
>>> where = '`id` IN :ids'
|
1146
|
-
>>> ids = (1, 2)
|
1147
|
-
>>> result = Database.execute_delete('database.table', where, ids=ids)
|
1148
|
-
>>> print(result.rowcount)
|
1149
|
-
2
|
1150
|
-
"""
|
1151
|
-
|
1152
|
-
# Handle parameter.
|
1153
|
-
database, table, _ = self.extract_path(path)
|
1154
|
-
|
1155
|
-
# Generate SQL.
|
1156
|
-
sqls = []
|
1157
|
-
|
1158
|
-
## Part 'DELETE' syntax.
|
1159
|
-
sql_delete = f'DELETE FROM `{database}`.`{table}`'
|
1160
|
-
sqls.append(sql_delete)
|
1161
|
-
|
1162
|
-
## Part 'WHERE' syntax.
|
1163
|
-
if where is not None:
|
1164
|
-
sql_where = f'WHERE {where}'
|
1165
|
-
sqls.append(sql_where)
|
1166
|
-
|
1167
|
-
## Part 'ORDER BY' syntax.
|
1168
|
-
if order is not None:
|
1169
|
-
sql_order = f'ORDER BY {order}'
|
1170
|
-
sqls.append(sql_order)
|
1171
|
-
|
1172
|
-
## Part 'LIMIT' syntax.
|
1173
|
-
if limit is not None:
|
1174
|
-
sql_limit = f'LIMIT {limit}'
|
1175
|
-
sqls.append(sql_limit)
|
1176
|
-
|
1177
|
-
## Join sqls.
|
1178
|
-
sqls = '\n'.join(sqls)
|
1179
|
-
|
1180
|
-
# Execute SQL.
|
1181
|
-
result = self.execute(sqls, report=report, **kwdata)
|
1182
|
-
|
1183
|
-
return result
|
1184
|
-
|
1185
|
-
|
1186
|
-
def execute_copy(
|
1187
|
-
self,
|
1188
|
-
path: str | tuple[str, str],
|
1189
|
-
where: str | None = None,
|
1190
|
-
limit: int | str | tuple[int, int] | None = None,
|
1191
|
-
report: bool | None = None,
|
1192
|
-
**kwdata: Any
|
1193
|
-
) -> Result:
|
1194
|
-
"""
|
1195
|
-
Copy record of table in the datebase.
|
1196
|
-
|
1197
|
-
Parameters
|
1198
|
-
----------
|
1199
|
-
path : Table name, can contain database name, otherwise use `self.database`.
|
1200
|
-
- `str`: Automatic extract database name and table name.
|
1201
|
-
- `tuple[str, str]`: Database name and table name.
|
1202
|
-
where : Clause `WHERE` content, join as `WHERE str`.
|
1203
|
-
limit : Clause `LIMIT` content.
|
1204
|
-
- `int | str`: Join as `LIMIT int/str`.
|
1205
|
-
- `tuple[int, int]`: Join as `LIMIT int, int`.
|
1206
|
-
report : Whether report SQL execute information.
|
1207
|
-
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1208
|
-
- `int`: Use this value.
|
1209
|
-
kwdata : Keyword parameters for filling.
|
1210
|
-
- `In 'WHERE' syntax`: Fill 'WHERE' syntax.
|
1211
|
-
- `Not in 'WHERE' syntax`: Fill 'INSERT' and 'SELECT' syntax.
|
1212
|
-
`str and first character is ':'`: Use this syntax.
|
1213
|
-
`Any`: Use this value.
|
1214
|
-
|
1215
|
-
Returns
|
1216
|
-
-------
|
1217
|
-
Result object.
|
1218
|
-
|
1219
|
-
Examples
|
1220
|
-
--------
|
1221
|
-
Parameter `where` and `kwdata`.
|
1222
|
-
>>> where = '`id` IN :ids'
|
1223
|
-
>>> ids = (1, 2, 3)
|
1224
|
-
>>> result = Database.execute_copy('database.table', where, 2, ids=ids, id=None, time=':NOW()')
|
1225
|
-
>>> print(result.rowcount)
|
1226
|
-
2
|
1227
|
-
"""
|
1228
|
-
|
1229
|
-
# Handle parameter.
|
1230
|
-
database, table, _ = self.extract_path(path)
|
1231
|
-
table_info: list[dict] = self.info(database)(table)()
|
1232
|
-
|
1233
|
-
## SQLite.
|
1234
|
-
if self.backend == 'sqlite':
|
1235
|
-
field_key = 'name'
|
1236
|
-
|
1237
|
-
## Other.
|
1238
|
-
else:
|
1239
|
-
field_key = 'COLUMN_NAME'
|
1240
|
-
|
1241
|
-
fields = [
|
1242
|
-
row[field_key]
|
1243
|
-
for row in table_info
|
1244
|
-
]
|
1245
|
-
pattern = '(?<!\\\\):(\\w+)'
|
1246
|
-
if type(where) == str:
|
1247
|
-
where_keys = findall(pattern, where)
|
1248
|
-
else:
|
1249
|
-
where_keys = ()
|
1250
|
-
|
1251
|
-
# Generate SQL.
|
1252
|
-
sqls = []
|
1253
|
-
|
1254
|
-
## Part 'INSERT' syntax.
|
1255
|
-
sql_fields = ', '.join(
|
1256
|
-
f'`{field}`'
|
1257
|
-
for field in fields
|
1258
|
-
if field not in kwdata
|
1259
|
-
)
|
1260
|
-
if kwdata != {}:
|
1261
|
-
sql_fields_kwdata = ', '.join(
|
1262
|
-
f'`{field}`'
|
1263
|
-
for field in kwdata
|
1264
|
-
if field not in where_keys
|
1265
|
-
)
|
1266
|
-
sql_fields_filter = filter(
|
1267
|
-
lambda sql: sql != '',
|
1268
|
-
(
|
1269
|
-
sql_fields,
|
1270
|
-
sql_fields_kwdata
|
1271
|
-
)
|
1272
|
-
)
|
1273
|
-
sql_fields = ', '.join(sql_fields_filter)
|
1274
|
-
sql_insert = f'INSERT INTO `{database}`.`{table}`({sql_fields})'
|
1275
|
-
sqls.append(sql_insert)
|
1276
|
-
|
1277
|
-
## Part 'SELECT' syntax.
|
1278
|
-
sql_values = ', '.join(
|
1279
|
-
f'`{field}`'
|
1280
|
-
for field in fields
|
1281
|
-
if field not in kwdata
|
1282
|
-
)
|
1283
|
-
if kwdata != {}:
|
1284
|
-
sql_values_kwdata = ', '.join(
|
1285
|
-
value[1:]
|
1286
|
-
if (
|
1287
|
-
type(value) == str
|
1288
|
-
and value.startswith(':')
|
1289
|
-
and value != ':'
|
1290
|
-
)
|
1291
|
-
else f':{field}'
|
1292
|
-
for field, value in kwdata.items()
|
1293
|
-
if field not in where_keys
|
1294
|
-
)
|
1295
|
-
sql_values_filter = filter(
|
1296
|
-
lambda sql: sql != '',
|
1297
|
-
(
|
1298
|
-
sql_values,
|
1299
|
-
sql_values_kwdata
|
1300
|
-
)
|
1301
|
-
)
|
1302
|
-
sql_values = ', '.join(sql_values_filter)
|
1303
|
-
sql_select = (
|
1304
|
-
f'SELECT {sql_values}\n'
|
1305
|
-
f'FROM `{database}`.`{table}`'
|
1306
|
-
)
|
1307
|
-
sqls.append(sql_select)
|
1308
|
-
|
1309
|
-
## Part 'WHERE' syntax.
|
1310
|
-
if where is not None:
|
1311
|
-
sql_where = f'WHERE {where}'
|
1312
|
-
sqls.append(sql_where)
|
1313
|
-
|
1314
|
-
## Part 'LIMIT' syntax.
|
1315
|
-
if limit is not None:
|
1316
|
-
if type(limit) in (str, int):
|
1317
|
-
sql_limit = f'LIMIT {limit}'
|
1318
|
-
else:
|
1319
|
-
if len(limit) == 2:
|
1320
|
-
sql_limit = f'LIMIT {limit[0]}, {limit[1]}'
|
1321
|
-
else:
|
1322
|
-
throw(ValueError, limit)
|
1323
|
-
sqls.append(sql_limit)
|
1324
|
-
|
1325
|
-
## Join.
|
1326
|
-
sql = '\n'.join(sqls)
|
1327
|
-
|
1328
|
-
# Execute SQL.
|
1329
|
-
result = self.execute(sql, report=report, **kwdata)
|
1330
|
-
|
1331
|
-
return result
|
1332
|
-
|
1333
|
-
|
1334
|
-
def execute_count(
|
1335
|
-
self,
|
1336
|
-
path: str | tuple[str, str],
|
1337
|
-
where: str | None = None,
|
1338
|
-
report: bool | None = None,
|
1339
|
-
**kwdata: Any
|
1340
|
-
) -> int:
|
1341
|
-
"""
|
1342
|
-
Count records.
|
1343
|
-
|
1344
|
-
Parameters
|
1345
|
-
----------
|
1346
|
-
path : Table name, can contain database name, otherwise use `self.database`.
|
1347
|
-
- `str`: Automatic extract database name and table name.
|
1348
|
-
- `tuple[str, str]`: Database name and table name.
|
1349
|
-
where : Match condition, `WHERE` clause content, join as `WHERE str`.
|
1350
|
-
- `None`: Match all.
|
1351
|
-
- `str`: Match condition.
|
1352
|
-
report : Whether report SQL execute information.
|
1353
|
-
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1354
|
-
- `int`: Use this value.
|
1355
|
-
kwdata : Keyword parameters for filling.
|
1356
|
-
|
1357
|
-
Returns
|
1358
|
-
-------
|
1359
|
-
Record count.
|
1360
|
-
|
1361
|
-
Examples
|
1362
|
-
--------
|
1363
|
-
Parameter `where` and `kwdata`.
|
1364
|
-
>>> where = '`id` IN :ids'
|
1365
|
-
>>> ids = (1, 2)
|
1366
|
-
>>> result = Database.execute_count('database.table', where, ids=ids)
|
1367
|
-
>>> print(result)
|
1368
|
-
2
|
1369
|
-
"""
|
1370
|
-
|
1371
|
-
# Handle parameter.
|
1372
|
-
database, table, _ = self.extract_path(path)
|
1373
|
-
|
1374
|
-
# Execute.
|
1375
|
-
result = self.execute_select((database, table), '1', where=where, report=report, **kwdata)
|
1376
|
-
count = len(tuple(result))
|
1377
|
-
|
1378
|
-
return count
|
1379
|
-
|
1380
|
-
|
1381
|
-
def execute_exist(
|
1382
|
-
self,
|
1383
|
-
path: str | tuple[str, str],
|
1384
|
-
where: str | None = None,
|
1385
|
-
report: bool | None = None,
|
1386
|
-
**kwdata: Any
|
1387
|
-
) -> bool:
|
1388
|
-
"""
|
1389
|
-
Judge the exist of record.
|
1390
|
-
|
1391
|
-
Parameters
|
1392
|
-
----------
|
1393
|
-
path : Table name, can contain database name, otherwise use `self.database`.
|
1394
|
-
- `str`: Automatic extract database name and table name.
|
1395
|
-
- `tuple[str, str]`: Database name and table name.
|
1396
|
-
where : Match condition, `WHERE` clause content, join as `WHERE str`.
|
1397
|
-
- `None`: Match all.
|
1398
|
-
- `str`: Match condition.
|
1399
|
-
report : Whether report SQL execute information.
|
1400
|
-
- `None`, Use attribute `report_execute_info`: of object `ROption`.
|
1401
|
-
- `int`: Use this value.
|
1402
|
-
kwdata : Keyword parameters for filling.
|
1403
|
-
|
1404
|
-
Returns
|
1405
|
-
-------
|
1406
|
-
Judged result.
|
1407
|
-
|
1408
|
-
Examples
|
1409
|
-
--------
|
1410
|
-
Parameter `where` and `kwdata`.
|
1411
|
-
>>> data = [{'id': 1}]
|
1412
|
-
>>> Database.execute_insert('database.table', data)
|
1413
|
-
>>> where = '`id` = :id_'
|
1414
|
-
>>> id_ = 1
|
1415
|
-
>>> result = Database.execute_exist('database.table', where, id_=id_)
|
1416
|
-
>>> print(result)
|
1417
|
-
True
|
1418
|
-
"""
|
1419
|
-
|
1420
|
-
# Handle parameter.
|
1421
|
-
database, table, _ = self.extract_path(path)
|
1422
|
-
|
1423
|
-
# Execute.
|
1424
|
-
result = self.execute_count(path, where, report, **kwdata)
|
1425
|
-
|
1426
|
-
# Judge.
|
1427
|
-
judge = result != 0
|
1428
|
-
|
1429
|
-
return judge
|
1430
|
-
|
1431
|
-
|
1432
|
-
def execute_generator(
|
1433
|
-
self,
|
1434
|
-
sql: str | TextClause,
|
1435
|
-
data: TableData,
|
1436
|
-
report: bool | None = None,
|
1437
|
-
**kwdata: Any
|
1438
|
-
) -> Generator[Result, Any, None]:
|
1439
|
-
"""
|
1440
|
-
Return a generator that can execute SQL.
|
211
|
+
Build `DatabaseConnection` instance.
|
1441
212
|
|
1442
213
|
Parameters
|
1443
214
|
----------
|
1444
|
-
|
1445
|
-
data : Data set for filling.
|
1446
|
-
report : Whether report SQL execute information.
|
1447
|
-
- `None`: Use attribute `default_report`.
|
1448
|
-
- `bool`: Use this value.
|
1449
|
-
kwdata : Keyword parameters for filling.
|
1450
|
-
|
1451
|
-
Returns
|
1452
|
-
-------
|
1453
|
-
Generator.
|
1454
|
-
"""
|
1455
|
-
|
1456
|
-
# Instance.
|
1457
|
-
generator = Generator(
|
1458
|
-
self.execute,
|
1459
|
-
sql=sql,
|
1460
|
-
report=report,
|
1461
|
-
**kwdata
|
1462
|
-
)
|
1463
|
-
|
1464
|
-
# Add.
|
1465
|
-
for row in data:
|
1466
|
-
generator(**row)
|
1467
|
-
|
1468
|
-
return generator.generator
|
1469
|
-
|
1470
|
-
|
1471
|
-
def connect(self):
|
1472
|
-
"""
|
1473
|
-
Build `DatabaseConnection` instance.
|
215
|
+
autocommit: Whether automatic commit connection.
|
1474
216
|
|
1475
217
|
Returns
|
1476
218
|
-------
|
@@ -1481,86 +223,26 @@ class Database(DatabaseBase):
|
|
1481
223
|
from .rconn import DatabaseConnection
|
1482
224
|
|
1483
225
|
# Build.
|
1484
|
-
|
1485
|
-
self.engine.connect(),
|
1486
|
-
self
|
1487
|
-
)
|
226
|
+
conn = DatabaseConnection(self, autocommit)
|
1488
227
|
|
1489
|
-
return
|
228
|
+
return conn
|
1490
229
|
|
1491
230
|
|
1492
231
|
@property
|
1493
|
-
def
|
232
|
+
def execute(self):
|
1494
233
|
"""
|
1495
|
-
Build `database
|
234
|
+
Build `database execute` instance.
|
1496
235
|
|
1497
236
|
Returns
|
1498
237
|
-------
|
1499
238
|
Instance.
|
1500
|
-
|
1501
|
-
Examples
|
1502
|
-
--------
|
1503
|
-
Execute.
|
1504
|
-
>>> sql = 'select :value'
|
1505
|
-
>>> result = DatabaseExecute(sql, value=1)
|
1506
|
-
|
1507
|
-
Select.
|
1508
|
-
>>> field = ['id', 'value']
|
1509
|
-
>>> where = '`id` = ids'
|
1510
|
-
>>> ids = (1, 2)
|
1511
|
-
>>> result = DatabaseExecute.database.table(field, where, ids=ids)
|
1512
|
-
|
1513
|
-
Insert.
|
1514
|
-
>>> data = [{'id': 1}, {'id': 2}]
|
1515
|
-
>>> duplicate = 'ignore'
|
1516
|
-
>>> result = DatabaseExecute.database.table + data
|
1517
|
-
>>> result = DatabaseExecute.database.table + (data, duplicate)
|
1518
|
-
>>> result = DatabaseExecute.database.table + {'data': data, 'duplicate': duplicate}
|
1519
|
-
|
1520
|
-
Update.
|
1521
|
-
>>> data = [{'name': 'a', 'id': 1}, {'name': 'b', 'id': 2}]
|
1522
|
-
>>> where_fields = 'id'
|
1523
|
-
>>> result = DatabaseExecute.database.table & data
|
1524
|
-
>>> result = DatabaseExecute.database.table & (data, where_fields)
|
1525
|
-
>>> result = DatabaseExecute.database.table & {'data': data, 'where_fields': where_fields}
|
1526
|
-
|
1527
|
-
Delete.
|
1528
|
-
>>> where = '`id` IN (1, 2)'
|
1529
|
-
>>> report = True
|
1530
|
-
>>> result = DatabaseExecute.database.table - where
|
1531
|
-
>>> result = DatabaseExecute.database.table - (where, report)
|
1532
|
-
>>> result = DatabaseExecute.database.table - {'where': where, 'report': report}
|
1533
|
-
|
1534
|
-
Copy.
|
1535
|
-
>>> where = '`id` IN (1, 2)'
|
1536
|
-
>>> limit = 1
|
1537
|
-
>>> result = DatabaseExecute.database.table * where
|
1538
|
-
>>> result = DatabaseExecute.database.table * (where, limit)
|
1539
|
-
>>> result = DatabaseExecute.database.table * {'where': where, 'limit': limit}
|
1540
|
-
|
1541
|
-
Exist.
|
1542
|
-
>>> where = '`id` IN (1, 2)'
|
1543
|
-
>>> report = True
|
1544
|
-
>>> result = where in DatabaseExecute.database.table
|
1545
|
-
>>> result = (where, report) in DatabaseExecute.database.table
|
1546
|
-
>>> result = {'where': where, 'report': report} in DatabaseExecute.database.table
|
1547
|
-
|
1548
|
-
Count.
|
1549
|
-
>>> result = len(DatabaseExecute.database.table)
|
1550
|
-
|
1551
|
-
Default database.
|
1552
|
-
>>> field = ['id', 'value']
|
1553
|
-
>>> engine = Database(**server, database)
|
1554
|
-
>>> result = engine.exe.table()
|
1555
239
|
"""
|
1556
240
|
|
1557
|
-
# Import.
|
1558
|
-
from .rexec import DatabaseExecute
|
1559
|
-
|
1560
241
|
# Build.
|
1561
|
-
|
242
|
+
dbconn = self.connect(True)
|
243
|
+
exec = dbconn.execute
|
1562
244
|
|
1563
|
-
return
|
245
|
+
return exec
|
1564
246
|
|
1565
247
|
|
1566
248
|
def schema(self, filter_default: bool = True) -> dict[str, dict[str, list[str]]]:
|
@@ -1576,11 +258,6 @@ class Database(DatabaseBase):
|
|
1576
258
|
Schemata of databases and tables and columns.
|
1577
259
|
"""
|
1578
260
|
|
1579
|
-
# Check.
|
1580
|
-
if self.backend == 'sqlite':
|
1581
|
-
text = 'not suitable for SQLite databases'
|
1582
|
-
throw(AssertionError, text=text)
|
1583
|
-
|
1584
261
|
# Handle parameter.
|
1585
262
|
filter_db = (
|
1586
263
|
'information_schema',
|
@@ -1645,7 +322,7 @@ class Database(DatabaseBase):
|
|
1645
322
|
|
1646
323
|
Returns
|
1647
324
|
-------
|
1648
|
-
|
325
|
+
Instance.
|
1649
326
|
|
1650
327
|
Examples
|
1651
328
|
--------
|
@@ -1684,7 +361,7 @@ class Database(DatabaseBase):
|
|
1684
361
|
|
1685
362
|
Returns
|
1686
363
|
-------
|
1687
|
-
|
364
|
+
Instance.
|
1688
365
|
"""
|
1689
366
|
|
1690
367
|
# Import.
|
@@ -1703,7 +380,7 @@ class Database(DatabaseBase):
|
|
1703
380
|
|
1704
381
|
Returns
|
1705
382
|
-------
|
1706
|
-
|
383
|
+
Instance.
|
1707
384
|
"""
|
1708
385
|
|
1709
386
|
# Import.
|
@@ -1722,7 +399,7 @@ class Database(DatabaseBase):
|
|
1722
399
|
|
1723
400
|
Returns
|
1724
401
|
-------
|
1725
|
-
|
402
|
+
Instance.
|
1726
403
|
"""
|
1727
404
|
|
1728
405
|
# Import.
|
@@ -1741,7 +418,7 @@ class Database(DatabaseBase):
|
|
1741
418
|
|
1742
419
|
Returns
|
1743
420
|
-------
|
1744
|
-
|
421
|
+
Instance.
|
1745
422
|
"""
|
1746
423
|
|
1747
424
|
# Import.
|
@@ -1756,108 +433,79 @@ class Database(DatabaseBase):
|
|
1756
433
|
@property
|
1757
434
|
def status(self):
|
1758
435
|
"""
|
1759
|
-
Build `DatabaseParametersStatus`
|
436
|
+
Build `DatabaseParametersStatus` instance.
|
1760
437
|
|
1761
438
|
Returns
|
1762
439
|
-------
|
1763
|
-
|
440
|
+
Instance.
|
1764
441
|
"""
|
1765
442
|
|
1766
443
|
# Import.
|
1767
|
-
from .rparam import DatabaseParametersStatus
|
444
|
+
from .rparam import DatabaseParametersStatus
|
1768
445
|
|
1769
446
|
# Build.
|
447
|
+
dbps = DatabaseParametersStatus(self, False)
|
1770
448
|
|
1771
|
-
|
1772
|
-
if self.backend == 'sqlite':
|
1773
|
-
dbp = DatabaseParametersPragma(self)
|
1774
|
-
|
1775
|
-
## Other.
|
1776
|
-
else:
|
1777
|
-
dbp = DatabaseParametersStatus(self, False)
|
1778
|
-
|
1779
|
-
return dbp
|
449
|
+
return dbps
|
1780
450
|
|
1781
451
|
|
1782
452
|
@property
|
1783
453
|
def global_status(self):
|
1784
454
|
"""
|
1785
|
-
Build `DatabaseParametersStatus`
|
455
|
+
Build global `DatabaseParametersStatus` instance.
|
1786
456
|
|
1787
457
|
Returns
|
1788
458
|
-------
|
1789
|
-
|
459
|
+
Instance.
|
1790
460
|
"""
|
1791
461
|
|
1792
462
|
# Import.
|
1793
|
-
from .rparam import DatabaseParametersStatus
|
463
|
+
from .rparam import DatabaseParametersStatus
|
1794
464
|
|
1795
465
|
# Build.
|
466
|
+
dbps = DatabaseParametersStatus(self, True)
|
1796
467
|
|
1797
|
-
|
1798
|
-
if self.backend == 'sqlite':
|
1799
|
-
dbp = DatabaseParametersPragma(self)
|
1800
|
-
|
1801
|
-
## Other.
|
1802
|
-
else:
|
1803
|
-
dbp = DatabaseParametersStatus(self, True)
|
1804
|
-
|
1805
|
-
return dbp
|
468
|
+
return dbps
|
1806
469
|
|
1807
470
|
|
1808
471
|
@property
|
1809
472
|
def variables(self):
|
1810
473
|
"""
|
1811
|
-
Build `DatabaseParametersVariable`
|
474
|
+
Build `DatabaseParametersVariable` instance.
|
1812
475
|
|
1813
476
|
Returns
|
1814
477
|
-------
|
1815
|
-
|
478
|
+
Instance.
|
1816
479
|
"""
|
1817
480
|
|
1818
481
|
# Import.
|
1819
|
-
from .rparam import DatabaseParametersVariable
|
482
|
+
from .rparam import DatabaseParametersVariable
|
1820
483
|
|
1821
484
|
# Build.
|
485
|
+
dbpv = DatabaseParametersVariable(self, False)
|
1822
486
|
|
1823
|
-
|
1824
|
-
if self.backend == 'sqlite':
|
1825
|
-
dbp = DatabaseParametersPragma(self)
|
1826
|
-
|
1827
|
-
## Other.
|
1828
|
-
else:
|
1829
|
-
dbp = DatabaseParametersVariable(self, False)
|
1830
|
-
|
1831
|
-
return dbp
|
487
|
+
return dbpv
|
1832
488
|
|
1833
489
|
|
1834
490
|
@property
|
1835
491
|
def global_variables(self):
|
1836
492
|
"""
|
1837
|
-
Build global `
|
493
|
+
Build global `DatabaseParametersVariable` instance.
|
1838
494
|
|
1839
495
|
Returns
|
1840
496
|
-------
|
1841
|
-
|
497
|
+
Instance.
|
1842
498
|
"""
|
1843
499
|
|
1844
500
|
# Import.
|
1845
|
-
from .rparam import DatabaseParametersVariable
|
501
|
+
from .rparam import DatabaseParametersVariable
|
1846
502
|
|
1847
503
|
# Build.
|
1848
504
|
|
1849
505
|
## SQLite.
|
1850
|
-
|
1851
|
-
dbp = DatabaseParametersPragma(self)
|
1852
|
-
|
1853
|
-
## Other.
|
1854
|
-
else:
|
1855
|
-
dbp = DatabaseParametersVariable(self, True)
|
1856
|
-
|
1857
|
-
return dbp
|
1858
|
-
|
506
|
+
dbpv = DatabaseParametersVariable(self, True)
|
1859
507
|
|
1860
|
-
|
508
|
+
return dbpv
|
1861
509
|
|
1862
510
|
|
1863
511
|
def __str__(self) -> str:
|
@@ -1865,16 +513,6 @@ class Database(DatabaseBase):
|
|
1865
513
|
Return connection information text.
|
1866
514
|
"""
|
1867
515
|
|
1868
|
-
# Handle parameter.
|
1869
|
-
if hasattr(self, 'engine'):
|
1870
|
-
attr_dict = self.__dict__
|
1871
|
-
else:
|
1872
|
-
rdatabase: Database = self.rdatabase
|
1873
|
-
attr_dict = {
|
1874
|
-
**self.__dict__,
|
1875
|
-
**rdatabase.__dict__
|
1876
|
-
}
|
1877
|
-
|
1878
516
|
# Generate.
|
1879
517
|
filter_key = (
|
1880
518
|
'engine',
|
@@ -1884,10 +522,10 @@ class Database(DatabaseBase):
|
|
1884
522
|
)
|
1885
523
|
info = {
|
1886
524
|
key: value
|
1887
|
-
for key, value in
|
525
|
+
for key, value in self.__dict__.items()
|
1888
526
|
if key not in filter_key
|
1889
527
|
}
|
1890
|
-
info['count'] = self.
|
528
|
+
info['count'] = self.count_conn
|
1891
529
|
text = join_data_text(info)
|
1892
530
|
|
1893
531
|
return text
|