reykit 1.0.1__py3-none-any.whl → 1.1.0__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/rconnection.py DELETED
@@ -1,2276 +0,0 @@
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