reykit 1.1.0__py3-none-any.whl → 1.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
reydb/rbuild.py ADDED
@@ -0,0 +1,1235 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2023-10-14 23:05:35
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Database build methods.
9
+ """
10
+
11
+
12
+ from typing import Any, Literal, NoReturn, overload
13
+ from reykit.rexception import throw
14
+ from reykit.rstdout import rinput
15
+ from reykit.rsystem import get_first_notnull
16
+
17
+ from .rconnection import RDatabase, RDBConnection
18
+
19
+
20
+ __all__ = (
21
+ 'RDBBuild',
22
+ )
23
+
24
+
25
+ class RDBBuild(object):
26
+ """
27
+ Rey's `database build` type.
28
+ """
29
+
30
+
31
+ def __init__(self, rdatabase: RDatabase | RDBConnection) -> None:
32
+ """
33
+ Build `database build` attributes.
34
+
35
+ Parameters
36
+ ----------
37
+ rdatabase : RDatabase or RDBConnection instance.
38
+ """
39
+
40
+ # Set attribute.
41
+ self.rdatabase = rdatabase
42
+
43
+
44
+ def create_database(
45
+ self,
46
+ database: str,
47
+ character: str = 'utf8mb3',
48
+ collate: str = 'utf8mb3_general_ci',
49
+ execute: bool = True
50
+ ) -> str:
51
+ """
52
+ Create database.
53
+
54
+ Parameters
55
+ ----------
56
+ database : Database name.
57
+ character : Character set.
58
+ collate : Collate rule.
59
+ execute : Whether directly execute.
60
+
61
+ Returns
62
+ -------
63
+ Execute SQL.
64
+ """
65
+
66
+ # Generate.
67
+ sql = f'CREATE DATABASE `{database}` CHARACTER SET {character} COLLATE {collate}'
68
+
69
+ # Execute.
70
+ if execute:
71
+ self.rdatabase(sql)
72
+
73
+ return sql
74
+
75
+
76
+ def _get_field_sql(
77
+ self,
78
+ name: str,
79
+ type_: str,
80
+ constraint: str = 'DEFAULT NULL',
81
+ comment: str | None = None,
82
+ position: str | None = None,
83
+ old_name: str | None = None
84
+ ) -> str:
85
+ """
86
+ Get a field set SQL.
87
+
88
+ Parameters
89
+ ----------
90
+ name : Field name.
91
+ type_ : Field type.
92
+ constraint : Field constraint.
93
+ comment : Field comment.
94
+ position : Field position.
95
+ old_name : Field old name.
96
+
97
+ Returns
98
+ -------
99
+ Field set SQL.
100
+ """
101
+
102
+ # Get parameter.
103
+
104
+ ## Constraint.
105
+ constraint = ' ' + constraint
106
+
107
+ ## Comment.
108
+ if comment is None:
109
+ comment = ''
110
+ else:
111
+ comment = f" COMMENT '{comment}'"
112
+
113
+ ## Position.
114
+ match position:
115
+ case None:
116
+ position = ''
117
+ case 'first':
118
+ position = ' FIRST'
119
+ case _:
120
+ position = f' AFTER `{position}`'
121
+
122
+ ## Old name.
123
+ if old_name is None:
124
+ old_name = ''
125
+ else:
126
+ old_name = f'`{old_name}` '
127
+
128
+ # Generate.
129
+ sql = f'{old_name}`{name}` {type_}{constraint}{comment}{position}'
130
+
131
+ return sql
132
+
133
+
134
+ @overload
135
+ def _get_index_sql(
136
+ self,
137
+ name: str,
138
+ fields: str | list[str],
139
+ type_: Literal['noraml', 'unique', 'fulltext', 'spatial'] = 'noraml',
140
+ comment: str | None = None
141
+ ) -> str: ...
142
+
143
+ @overload
144
+ def _get_index_sql(
145
+ self,
146
+ name: str,
147
+ fields: str | list[str],
148
+ type_: str = 'noraml',
149
+ comment: str | None = None
150
+ ) -> NoReturn: ...
151
+
152
+ def _get_index_sql(
153
+ self,
154
+ name: str,
155
+ fields: str | list[str],
156
+ type_: Literal['noraml', 'unique', 'fulltext', 'spatial'] = 'noraml',
157
+ comment: str | None = None
158
+ ) -> str:
159
+ """
160
+ Get a index set SQL.
161
+
162
+ Parameters
163
+ ----------
164
+ name : Index name.
165
+ fields : Index fileds.
166
+ type_ : Index type.
167
+ comment : Index comment.
168
+
169
+ Returns
170
+ -------
171
+ Index set SQL.
172
+ """
173
+
174
+ # Get parameter.
175
+ if fields.__class__ == str:
176
+ fields = [fields]
177
+ match type_:
178
+ case 'noraml':
179
+ type_ = 'KEY'
180
+ method = ' USING BTREE'
181
+ case 'unique':
182
+ type_ = 'UNIQUE KEY'
183
+ method = ' USING BTREE'
184
+ case 'fulltext':
185
+ type_ = 'FULLTEXT KEY'
186
+ method = ''
187
+ case 'spatial':
188
+ type_ = 'SPATIAL KEY'
189
+ method = ''
190
+ case _:
191
+ throw(ValueError, type_)
192
+ if comment in (None, ''):
193
+ comment = ''
194
+ else:
195
+ comment = f" COMMENT '{comment}'"
196
+
197
+ # Generate.
198
+
199
+ ## Fields.
200
+ sql_fields = ', '.join(
201
+ [
202
+ f'`{field}`'
203
+ for field in fields
204
+ ]
205
+ )
206
+
207
+ ## Join.
208
+ sql = f'{type_} `{name}` ({sql_fields}){method}{comment}'
209
+
210
+ return sql
211
+
212
+
213
+ def create_table(
214
+ self,
215
+ path: str | tuple[str, str],
216
+ fields: dict | list[dict],
217
+ primary: str | list[str] | None = None,
218
+ indexes: dict | list[dict] | None = None,
219
+ engine: str = 'InnoDB',
220
+ increment: int = 1,
221
+ charset: str = 'utf8mb3',
222
+ collate: str = 'utf8mb3_general_ci',
223
+ comment: str | None = None,
224
+ execute: bool = True
225
+ ) -> str:
226
+ """
227
+ Create table.
228
+
229
+ Parameters
230
+ ----------
231
+ path : Table name, can contain database name, otherwise use `self.rdatabase.database`.
232
+ - `str`: Automatic extract database name and table name.
233
+ - `tuple[str, str]`: Database name and table name.
234
+ fields : Fields set table.
235
+ - `Key 'name'`: Field name, required.
236
+ - `Key 'type' or 'type_'`: Field type, required.
237
+ - `Key 'constraint'`: Field constraint.
238
+ `Empty or None`: Use 'DEFAULT NULL'.
239
+ `str`: Use this value.
240
+ - `Key 'comment'`: Field comment.
241
+ `Empty or None`: Not comment.
242
+ `str`: Use this value.
243
+ primary : Primary key fields.
244
+ - `str`: One field.
245
+ - `list[str]`: Multiple fileds.
246
+ indexes : Index set table.
247
+ - `Key 'name'`: Index name, required.
248
+ - `Key 'fields'`: Index fields, required.
249
+ `str`: One field.
250
+ `list[str]`: Multiple fileds.
251
+ - `Key 'type' or 'type_'`: Index type.
252
+ `Literal['noraml']`: Noraml key.
253
+ `Literal['unique']`: Unique key.
254
+ `Literal['fulltext']`: Full text key.
255
+ `Literal['spatial']`: Spatial key.
256
+ - `Key 'comment'`: Field comment.
257
+ `Empty or None`: Not comment.
258
+ `str`: Use this value.
259
+ engine : Engine type.
260
+ increment : Automatic Increment start value.
261
+ charset : Charset type.
262
+ collate : Collate type.
263
+ comment : Table comment.
264
+ execute : Whether directly execute.
265
+
266
+ Returns
267
+ -------
268
+ Execute SQL.
269
+ """
270
+
271
+ # Handle parameter.
272
+ if path.__class__ == str:
273
+ database, table, _ = self.rdatabase.extract_path(path)
274
+ else:
275
+ database, table = path
276
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
277
+ if fields.__class__ == dict:
278
+ fields = [fields]
279
+ if primary.__class__ == str:
280
+ primary = [primary]
281
+ if primary in ([], ['']):
282
+ primary = None
283
+ if indexes.__class__ == dict:
284
+ indexes = [indexes]
285
+
286
+ ## Compatible dictionary key name.
287
+ fields = [
288
+ {
289
+ (
290
+ 'type_'
291
+ if key == 'type'
292
+ else key
293
+ ): value
294
+ for key, value in row.items()
295
+ }
296
+ for row in fields
297
+ ]
298
+ if indexes is not None:
299
+ indexes = [
300
+ {
301
+ (
302
+ 'type_'
303
+ if key == 'type'
304
+ else key
305
+ ): value
306
+ for key, value in row.items()
307
+ }
308
+ for row in indexes
309
+ ]
310
+
311
+ # Generate.
312
+
313
+ ## Fields.
314
+ sql_fields = [
315
+ self._get_field_sql(**field)
316
+ for field in fields
317
+ ]
318
+
319
+ ## Primary.
320
+ if primary is not None:
321
+ keys = ', '.join(
322
+ [
323
+ f'`{key}`'
324
+ for key in primary
325
+ ]
326
+ )
327
+ sql_primary = f'PRIMARY KEY ({keys}) USING BTREE'
328
+ sql_fields.append(sql_primary)
329
+
330
+ ## Indexes.
331
+ if indexes is not None:
332
+ sql_indexes = [
333
+ self._get_index_sql(**index)
334
+ for index in indexes
335
+ ]
336
+ sql_fields.extend(sql_indexes)
337
+
338
+ ## Comment.
339
+ if comment is None:
340
+ sql_comment = ''
341
+ else:
342
+ sql_comment = f" COMMENT='{comment}'"
343
+
344
+ ## Join.
345
+ sql_fields = ',\n '.join(sql_fields)
346
+ sql = (
347
+ f'CREATE TABLE `{database}`.`{table}`(\n'
348
+ f' {sql_fields}\n'
349
+ f') ENGINE={engine} AUTO_INCREMENT={increment} CHARSET={charset} COLLATE={collate}{sql_comment}'
350
+ )
351
+
352
+ # Execute.
353
+ if execute:
354
+ self.rdatabase(sql)
355
+
356
+ return sql
357
+
358
+
359
+ def create_view(
360
+ self,
361
+ path: str | tuple[str, str],
362
+ select: str,
363
+ execute: bool = True
364
+ ) -> str:
365
+ """
366
+ Create view.
367
+
368
+ Parameters
369
+ ----------
370
+ path : View name, can contain database name, otherwise use `self.rdatabase.database`.
371
+ - `str`: Automatic extract database name and view name.
372
+ - `tuple[str, str]`: Database name and view name.
373
+ select : View select SQL.
374
+ execute : Whether directly execute.
375
+
376
+ Returns
377
+ -------
378
+ Execute SQL.
379
+ """
380
+
381
+ # Handle parameter.
382
+ if path.__class__ == str:
383
+ database, view, _ = self.rdatabase.extract_path(path)
384
+ else:
385
+ database, view = path
386
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
387
+
388
+ # Generate SQL.
389
+ select = select.replace('\n', '\n ')
390
+ sql = 'CREATE VIEW `%s`.`%s` AS (\n %s\n)' % (database, view, select)
391
+
392
+ # Execute.
393
+ if execute:
394
+ self.rdatabase(sql)
395
+
396
+ return sql
397
+
398
+
399
+ def create_view_stats(
400
+ self,
401
+ path: str | tuple[str, str],
402
+ items: list[dict],
403
+ execute: bool = True
404
+ ) -> str:
405
+ """
406
+ Create stats view.
407
+
408
+ Parameters
409
+ ----------
410
+ path : View name, can contain database name, otherwise use `self.rdatabase.database`.
411
+ - `str`: Automatic extract database name and view name.
412
+ - `tuple[str, str]`: Database name and view name.
413
+ items : Items set table.
414
+ - `Key 'name'`: Item name, required.
415
+ - `Key 'select'`: Item select SQL, must only return one value, required.
416
+ - `Key 'comment'`: Item comment.
417
+ execute : Whether directly execute.
418
+
419
+ Returns
420
+ -------
421
+ Execute SQL.
422
+ """
423
+
424
+ # Check.
425
+ if items == []:
426
+ throw(ValueError, items)
427
+
428
+ # Generate select SQL.
429
+ item_first = items[0]
430
+ select_first = "SELECT '%s' AS `Item`,\n(\n %s\n) AS `Value`,\n%s AS `Comment`" % (
431
+ item_first['name'],
432
+ item_first['select'].replace('\n', '\n '),
433
+ (
434
+ 'NULL'
435
+ if 'comment' not in item_first
436
+ else "'%s'" % item_first['comment']
437
+ )
438
+ )
439
+ selects = [
440
+ "SELECT '%s',\n(\n %s\n),\n%s" % (
441
+ item['name'],
442
+ item['select'].replace('\n', '\n '),
443
+ (
444
+ 'NULL'
445
+ if 'comment' not in item
446
+ else "'%s'" % item['comment']
447
+ )
448
+ )
449
+ for item in items[1:]
450
+ ]
451
+ selects[0:0] = [select_first]
452
+ select = '\nUNION\n'.join(selects)
453
+
454
+ # Create.
455
+ sql = self.create_view(
456
+ path,
457
+ select,
458
+ execute
459
+ )
460
+
461
+ return sql
462
+
463
+
464
+ def drop_database(
465
+ self,
466
+ database: str,
467
+ execute: bool = True
468
+ ) -> str:
469
+ """
470
+ Drop database.
471
+
472
+ Parameters
473
+ ----------
474
+ database : Database name.
475
+ execute : Whether directly execute.
476
+
477
+ Returns
478
+ -------
479
+ Execute SQL.
480
+ """
481
+
482
+ # Generate.
483
+ sql = f'DROP DATABASE `{database}`'
484
+
485
+ # Execute.
486
+ if execute:
487
+ self.rdatabase(sql)
488
+
489
+ return sql
490
+
491
+
492
+ def drop_table(
493
+ self,
494
+ path: str | tuple[str, str],
495
+ execute: bool = True
496
+ ) -> str:
497
+ """
498
+ Drop table.
499
+
500
+ Parameters
501
+ ----------
502
+ path : Table name, can contain database name, otherwise use `self.rdatabase.database`.
503
+ - `str`: Automatic extract database name and table name.
504
+ - `tuple[str, str]`: Database name and table name.
505
+ execute : Whether directly execute.
506
+
507
+ Returns
508
+ -------
509
+ Execute SQL.
510
+ """
511
+
512
+ # Handle parameter.
513
+ if path.__class__ == str:
514
+ database, table, _ = self.rdatabase.extract_path(path)
515
+ else:
516
+ database, table = path
517
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
518
+
519
+ # Generate.
520
+ sql = f'DROP TABLE `{database}`.`{table}`'
521
+
522
+ # Execute.
523
+ if execute:
524
+ self.rdatabase(sql)
525
+
526
+ return sql
527
+
528
+
529
+ def drop_view(
530
+ self,
531
+ path: str | tuple[str, str],
532
+ execute: bool = True
533
+ ) -> str:
534
+ """
535
+ Drop view.
536
+
537
+ Parameters
538
+ ----------
539
+ path : View name, can contain database name, otherwise use `self.rdatabase.database`.
540
+ - `str`: Automatic extract database name and view name.
541
+ - `tuple[str, str]`: Database name and view name.
542
+ execute : Whether directly execute.
543
+
544
+ Returns
545
+ -------
546
+ Execute SQL.
547
+ """
548
+
549
+ # Handle parameter.
550
+ if path.__class__ == str:
551
+ database, view, _ = self.rdatabase.extract_path(path)
552
+ else:
553
+ database, view = path
554
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
555
+
556
+ # Generate SQL.
557
+ sql = 'DROP VIEW `%s`.`%s`' % (database, view)
558
+
559
+ # Execute.
560
+ if execute:
561
+ self.rdatabase(sql)
562
+
563
+ return sql
564
+
565
+
566
+ def alter_database(
567
+ self,
568
+ database: str,
569
+ character: str | None = None,
570
+ collate: str | None = None,
571
+ execute: bool = True
572
+ ) -> str:
573
+ """
574
+ Alter database.
575
+
576
+ Parameters
577
+ ----------
578
+ database : Database name.
579
+ character : Character set.
580
+ - `None`: Not alter.
581
+ - `str`: Alter to this value.
582
+ collate : Collate rule.
583
+ - `None`: Not alter.
584
+ - `str`: Alter to this value.
585
+ execute : Whether directly execute.
586
+
587
+ Returns
588
+ -------
589
+ Execute SQL.
590
+ """
591
+
592
+ # Generate.
593
+
594
+ ## Character.
595
+ if character is None:
596
+ sql_character = ''
597
+ else:
598
+ sql_character = f' CHARACTER SET {character}'
599
+
600
+ ## Collate.
601
+ if collate is None:
602
+ sql_collate = ''
603
+ else:
604
+ sql_collate = f' COLLATE {collate}'
605
+
606
+ ## Join.
607
+ sql = f'ALTER DATABASE `{database}`{sql_character}{sql_collate}'
608
+
609
+ # Execute.
610
+ if execute:
611
+ self.rdatabase(sql)
612
+
613
+ return sql
614
+
615
+
616
+ def alter_table_add(
617
+ self,
618
+ path: str | tuple[str, str],
619
+ fields: dict | list[dict] | None = None,
620
+ primary: str | list[str] | None = None,
621
+ indexes: dict | list[dict] | None = None,
622
+ execute: bool = True
623
+ ) -> str:
624
+ """
625
+ Alter table add filed.
626
+
627
+ Parameters
628
+ ----------
629
+ path : Table name, can contain database name, otherwise use `self.rdatabase.database`.
630
+ - `str`: Automatic extract database name and table name.
631
+ - `tuple[str, str]`: Database name and table name.
632
+ fields : Fields set table.
633
+ - `Key 'name'`: Field name, required.
634
+ - `Key 'type' or 'type_'`: Field type, required.
635
+ - `Key 'constraint'`: Field constraint.
636
+ `Empty or None`: Use 'DEFAULT NULL'.
637
+ `str`: Use this value.
638
+ - `Key 'comment'`: Field comment.
639
+ `Empty or None`: Not comment.
640
+ `str`: Use this value.
641
+ - `Key 'position'`: Field position.
642
+ `None`: Last.
643
+ `Literal['first']`: First.
644
+ `str`: After this field.
645
+ primary : Primary key fields.
646
+ - `str`: One field.
647
+ - `list[str]`: Multiple fileds.
648
+ indexes : Index set table.
649
+ - `Key 'name'`: Index name, required.
650
+ - `Key 'fields'`: Index fields, required.
651
+ `str`: One field.
652
+ `list[str]`: Multiple fileds.
653
+ - `Key 'type' or 'type_'`: Index type.
654
+ `Literal['noraml']`: Noraml key.
655
+ `Literal['unique']`: Unique key.
656
+ `Literal['fulltext']`: Full text key.
657
+ `Literal['spatial']`: Spatial key.
658
+ - `Key 'comment'`: Field comment.
659
+ `Empty or None`: Not comment.
660
+ `str`: Use this value.
661
+
662
+ Returns
663
+ -------
664
+ Execute SQL.
665
+ """
666
+
667
+ # Handle parameter.
668
+ if path.__class__ == str:
669
+ database, table, _ = self.rdatabase.extract_path(path)
670
+ else:
671
+ database, table = path
672
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
673
+ if fields.__class__ == dict:
674
+ fields = [fields]
675
+ if primary.__class__ == str:
676
+ primary = [primary]
677
+ if primary in ([], ['']):
678
+ primary = None
679
+ if indexes.__class__ == dict:
680
+ indexes = [indexes]
681
+
682
+ ## Compatible dictionary key name.
683
+ fields = [
684
+ {
685
+ (
686
+ 'type_'
687
+ if key == 'type'
688
+ else key
689
+ ): value
690
+ for key, value in row.items()
691
+ }
692
+ for row in fields
693
+ ]
694
+ if indexes is not None:
695
+ indexes = [
696
+ {
697
+ (
698
+ 'type_'
699
+ if key == 'type'
700
+ else key
701
+ ): value
702
+ for key, value in row.items()
703
+ }
704
+ for row in indexes
705
+ ]
706
+
707
+ # Generate.
708
+ sql_content = []
709
+
710
+ ## Fields.
711
+ if fields is not None:
712
+ sql_fields = [
713
+ 'COLUMN ' + self._get_field_sql(**field)
714
+ for field in fields
715
+ ]
716
+ sql_content.extend(sql_fields)
717
+
718
+ ## Primary.
719
+ if primary is not None:
720
+ keys = ', '.join(
721
+ [
722
+ f'`{key}`'
723
+ for key in primary
724
+ ]
725
+ )
726
+ sql_primary = f'PRIMARY KEY ({keys}) USING BTREE'
727
+ sql_content.append(sql_primary)
728
+
729
+ ## Indexes.
730
+ if indexes is not None:
731
+ sql_indexes = [
732
+ self._get_index_sql(**index)
733
+ for index in indexes
734
+ ]
735
+ sql_content.extend(sql_indexes)
736
+
737
+ ## Join.
738
+ sql_content = ',\n ADD '.join(sql_content)
739
+ sql = (
740
+ f'ALTER TABLE `{database}`.`{table}`\n'
741
+ f' ADD {sql_content}'
742
+ )
743
+
744
+ # Execute.
745
+ if execute:
746
+ self.rdatabase(sql)
747
+
748
+ return sql
749
+
750
+
751
+ def alter_table_drop(
752
+ self,
753
+ path: str | tuple[str, str],
754
+ fields: list[str] | None = None,
755
+ primary: bool = False,
756
+ indexes: list[str] | None = None,
757
+ execute: bool = True
758
+ ) -> str:
759
+ """
760
+ Alter table drop field.
761
+
762
+ Parameters
763
+ ----------
764
+ path : Table name, can contain database name, otherwise use `self.rdatabase.database`.
765
+ - `str`: Automatic extract database name and table name.
766
+ - `tuple[str, str]`: Database name and table name.
767
+ fields : Delete fields name.
768
+ primary : Whether delete primary key.
769
+ indexes : Delete indexes name.
770
+ execute : Whether directly execute.
771
+
772
+ Returns
773
+ -------
774
+ Execute SQL.
775
+ """
776
+
777
+ # Handle parameter.
778
+ if path.__class__ == str:
779
+ database, table, _ = self.rdatabase.extract_path(path)
780
+ else:
781
+ database, table = path
782
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
783
+
784
+ # Generate.
785
+ sql_content = []
786
+
787
+ ## Fields.
788
+ if fields is not None:
789
+ sql_fields = [
790
+ 'COLUMN ' + field
791
+ for field in fields
792
+ ]
793
+ sql_content.extend(sql_fields)
794
+
795
+ ## Primary.
796
+ if primary:
797
+ sql_primary = 'PRIMARY KEY'
798
+ sql_content.append(sql_primary)
799
+
800
+ ## Indexes.
801
+ if indexes is not None:
802
+ sql_indexes = [
803
+ 'INDEX ' + index
804
+ for index in indexes
805
+ ]
806
+ sql_content.extend(sql_indexes)
807
+
808
+ ## Join.
809
+ sql_content = ',\n DROP '.join(sql_content)
810
+ sql = (
811
+ f'ALTER TABLE `{database}`.`{table}`\n'
812
+ f' DROP {sql_content}'
813
+ )
814
+
815
+ # Execute.
816
+ if execute:
817
+ self.rdatabase(sql)
818
+
819
+ return sql
820
+
821
+
822
+ def alter_table_change(
823
+ self,
824
+ path: str | tuple[str, str],
825
+ fields: dict | list[dict] | None = None,
826
+ rename: str | None = None,
827
+ engine: str | None = None,
828
+ increment: int | None = None,
829
+ charset: str | None = None,
830
+ collate: str | None = None,
831
+ execute: bool = True
832
+ ) -> str:
833
+ """
834
+ Alter database.
835
+
836
+ Parameters
837
+ ----------
838
+ path : Table name, can contain database name, otherwise use `self.rdatabase.database`.
839
+ - `str`: Automatic extract database name and table name.
840
+ - `tuple[str, str]`: Database name and table name.
841
+ fields : Fields set table.
842
+ - `Key 'name'`: Field name, required.
843
+ - `Key 'type' or 'type_'`: Field type, required.
844
+ - `Key 'constraint'`: Field constraint.
845
+ `Empty or None`: Use 'DEFAULT NULL'.
846
+ `str`: Use this value.
847
+ - `Key 'comment'`: Field comment.
848
+ `Empty or None`: Not comment.
849
+ `str`: Use this value.
850
+ - `Key 'position'`: Field position.
851
+ `None`: Last.
852
+ `Literal['first']`: First.
853
+ `str`: After this field.
854
+ - `Key 'old_name'`: Field old name.
855
+ rename : Table new name.
856
+ engine : Engine type.
857
+ increment : Automatic Increment start value.
858
+ charset : Charset type.
859
+ collate : Collate type.
860
+ execute : Whether directly execute.
861
+
862
+ Returns
863
+ -------
864
+ Execute SQL.
865
+ """
866
+
867
+ # Handle parameter.
868
+ if path.__class__ == str:
869
+ database, table, _ = self.rdatabase.extract_path(path)
870
+ else:
871
+ database, table = path
872
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
873
+ if fields.__class__ == dict:
874
+ fields = [fields]
875
+
876
+ ## Compatible dictionary key name.
877
+ fields = [
878
+ {
879
+ (
880
+ 'type_'
881
+ if key == 'type'
882
+ else key
883
+ ): value
884
+ for key, value in row.items()
885
+ }
886
+ for row in fields
887
+ ]
888
+
889
+ # Generate.
890
+ sql_content = []
891
+
892
+ ## Rename.
893
+ if rename is not None:
894
+ sql_rename = f'RENAME `{database}`.`{rename}`'
895
+ sql_content.append(sql_rename)
896
+
897
+ ## Fields.
898
+ if fields is not None:
899
+ sql_fields = [
900
+ '%s %s' % (
901
+ (
902
+ 'MODIFY'
903
+ if 'old_name' not in field
904
+ else 'CHANGE'
905
+ ),
906
+ self._get_field_sql(**field)
907
+ )
908
+ for field in fields
909
+ ]
910
+ sql_content.extend(sql_fields)
911
+
912
+ ## Attribute.
913
+ sql_attr = []
914
+
915
+ ### Engine.
916
+ if engine is not None:
917
+ sql_engine = f'ENGINE={engine}'
918
+ sql_attr.append(sql_engine)
919
+
920
+ ### Increment.
921
+ if increment is not None:
922
+ sql_increment = f'AUTO_INCREMENT={increment}'
923
+ sql_attr.append(sql_increment)
924
+
925
+ ### Charset.
926
+ if charset is not None:
927
+ sql_charset = f'CHARSET={charset}'
928
+ sql_attr.append(sql_charset)
929
+
930
+ ### Collate.
931
+ if collate is not None:
932
+ sql_collate = f'COLLATE={collate}'
933
+ sql_attr.append(sql_collate)
934
+
935
+ if sql_attr != []:
936
+ sql_attr = ' '.join(sql_attr)
937
+ sql_content.append(sql_attr)
938
+
939
+ ## Join.
940
+ sql_content = ',\n '.join(sql_content)
941
+ sql = (
942
+ f'ALTER TABLE `{database}`.`{table}`\n'
943
+ f' {sql_content}'
944
+ )
945
+
946
+ # Execute.
947
+ if execute:
948
+ self.rdatabase(sql)
949
+
950
+ return sql
951
+
952
+
953
+ def alter_view(
954
+ self,
955
+ path: str | tuple[str, str],
956
+ select: str,
957
+ execute: bool = True
958
+ ) -> str:
959
+ """
960
+ Alter view.
961
+
962
+ Parameters
963
+ ----------
964
+ path : View name, can contain database name, otherwise use `self.rdatabase.database`.
965
+ - `str`: Automatic extract database name and view name.
966
+ - `tuple[str, str]`: Database name and view name.
967
+ select : View select SQL.
968
+ execute : Whether directly execute.
969
+
970
+ Returns
971
+ -------
972
+ Execute SQL.
973
+ """
974
+
975
+ # Handle parameter.
976
+ if path.__class__ == str:
977
+ database, view, _ = self.rdatabase.extract_path(path)
978
+ else:
979
+ database, view = path
980
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
981
+
982
+ # Generate SQL.
983
+ sql = 'ALTER VIEW `%s`.`%s` AS\n%s' % (database, view, select)
984
+
985
+ # Execute.
986
+ if execute:
987
+ self.rdatabase(sql)
988
+
989
+ return sql
990
+
991
+
992
+ def truncate_table(
993
+ self,
994
+ path: str | tuple[str, str],
995
+ execute: bool = True
996
+ ) -> str:
997
+ """
998
+ Truncate table.
999
+
1000
+ Parameters
1001
+ ----------
1002
+ path : Table name, can contain database name, otherwise use `self.rdatabase.database`.
1003
+ - `str`: Automatic extract database name and table name.
1004
+ - `tuple[str, str]`: Database name and table name.
1005
+ execute : Whether directly execute.
1006
+
1007
+ Returns
1008
+ -------
1009
+ Execute SQL.
1010
+ """
1011
+
1012
+ # Handle parameter.
1013
+ if path.__class__ == str:
1014
+ database, table, _ = self.rdatabase.extract_path(path)
1015
+ else:
1016
+ database, table = path
1017
+ database = get_first_notnull(database, self.rdatabase.database, default='exception')
1018
+
1019
+ # Generate.
1020
+ sql = f'TRUNCATE TABLE `{database}`.`{table}`'
1021
+
1022
+ # Execute.
1023
+ if execute:
1024
+ self.rdatabase(sql)
1025
+
1026
+ return sql
1027
+
1028
+
1029
+ def exist(
1030
+ self,
1031
+ path: str | tuple[str, str | None, str | None]
1032
+ ) -> bool:
1033
+ """
1034
+ Judge database or table or column exists.
1035
+
1036
+ Parameters
1037
+ ----------
1038
+ path : Database name and table name and column name.
1039
+ - `str`: Automatic extract.
1040
+ - `tuple[str, str | None, str | None]`: Database name, table name and column name is optional.
1041
+
1042
+ Returns
1043
+ -------
1044
+ Judge result.
1045
+ """
1046
+
1047
+ # Handle parameter.
1048
+ if path.__class__ == str:
1049
+ database, table, column = self.rdatabase.extract_path(path, 'database')
1050
+ else:
1051
+ database, table, column = path
1052
+
1053
+ # Judge.
1054
+ if table is None:
1055
+ rinfo = self.rdatabase.info(database)
1056
+ elif column is None:
1057
+ rinfo = self.rdatabase.info(database)(table)
1058
+ else:
1059
+ rinfo = self.rdatabase.info(database)(table)(column)
1060
+ try:
1061
+ rinfo['*']
1062
+ except AssertionError:
1063
+ judge = False
1064
+ else:
1065
+ judge = True
1066
+
1067
+ return judge
1068
+
1069
+
1070
+ def input_confirm_build(
1071
+ self,
1072
+ sql: str
1073
+ ) -> None:
1074
+ """
1075
+ Print tip text, and confirm execute SQL. If reject, throw exception.
1076
+
1077
+ Parameters
1078
+ ----------
1079
+ sql : SQL.
1080
+ """
1081
+
1082
+ # Confirm.
1083
+ text = 'Do you want to execute SQL to build the database? Otherwise stop program. (y/n) '
1084
+ command = rinput(
1085
+ sql,
1086
+ text,
1087
+ frame='top',
1088
+ title='SQL'
1089
+ )
1090
+
1091
+ # Check.
1092
+ while True:
1093
+ command = command.lower()
1094
+ match command:
1095
+
1096
+ ## Confirm.
1097
+ case 'y':
1098
+ break
1099
+
1100
+ ## Stop.
1101
+ case 'n':
1102
+ raise AssertionError('program stop')
1103
+
1104
+ ## Reenter.
1105
+ case _:
1106
+ text = 'Incorrect input, reenter. (y/n) '
1107
+ command = input(text)
1108
+
1109
+
1110
+ def build(
1111
+ self,
1112
+ databases: list[dict] | None = None,
1113
+ tables: list[dict] | None = None,
1114
+ views: list[dict] | None = None,
1115
+ views_stats: list[dict] | None = None
1116
+ ) -> None:
1117
+ """
1118
+ Build databases or tables.
1119
+
1120
+ Parameters
1121
+ ----------
1122
+ databases : Database build parameters, equivalent to the parameters of method `self.create_database`.
1123
+ tables : Tables build parameters, equivalent to the parameters of method `self.create_table`.
1124
+ views : Views build parameters, equivalent to the parameters of method `self.create_view`.
1125
+ views_stats : Views stats build parameters, equivalent to the parameters of method `self.create_view_stats`.
1126
+ """
1127
+
1128
+ # Handle parameter.
1129
+ if databases is None:
1130
+ databases = []
1131
+ if tables is None:
1132
+ tables = []
1133
+ if views is None:
1134
+ views = []
1135
+ if views_stats is None:
1136
+ views_stats = []
1137
+
1138
+ # Database.
1139
+ for params in databases:
1140
+ database = params['database']
1141
+
1142
+ # Exist.
1143
+ exist = self.rdatabase.build.exist((database, None, None))
1144
+ if exist: continue
1145
+
1146
+ # Create.
1147
+ sql = self.create_database(**params, execute=False)
1148
+
1149
+ # Confirm.
1150
+ self.input_confirm_build(sql)
1151
+
1152
+ # Execute.
1153
+ self.rdatabase(sql)
1154
+
1155
+ # Report.
1156
+ text = f"Database '{database}' build completed."
1157
+ print(text)
1158
+
1159
+ # Table.
1160
+ for params in tables:
1161
+ path = params['path']
1162
+ if path.__class__ == str:
1163
+ database, table, _ = self.rdatabase.extract_path(path)
1164
+ else:
1165
+ database, table = path
1166
+
1167
+ # Exist.
1168
+ exist = self.rdatabase.build.exist((database, table, None))
1169
+ if exist: continue
1170
+
1171
+ # Create.
1172
+ sql = self.create_table(**params, execute=False)
1173
+
1174
+ # Confirm.
1175
+ self.input_confirm_build(sql)
1176
+
1177
+ # Execute.
1178
+ self.rdatabase(sql)
1179
+
1180
+ # Report.
1181
+ text = f"Table '{table}' of database '{database}' build completed."
1182
+ print(text)
1183
+
1184
+ # View.
1185
+ for params in views:
1186
+ path = params['path']
1187
+ if path.__class__ == str:
1188
+ database, view, _ = self.rdatabase.extract_path(path)
1189
+ else:
1190
+ database, view = path
1191
+
1192
+ # Exist.
1193
+ exist = self.rdatabase.build.exist((database, view, None))
1194
+ if exist: continue
1195
+
1196
+ # Create.
1197
+ sql = self.create_view(**params, execute=False)
1198
+
1199
+ # Confirm.
1200
+ self.input_confirm_build(sql)
1201
+
1202
+ # Execute.
1203
+ self.rdatabase(sql)
1204
+
1205
+ # Report.
1206
+ text = f"View '{view}' of database '{database}' build completed."
1207
+ print(text)
1208
+
1209
+ # View stats.
1210
+ for params in views_stats:
1211
+ path = params['path']
1212
+ if path.__class__ == str:
1213
+ database, view, _ = self.rdatabase.extract_path(path)
1214
+ else:
1215
+ database, view = path
1216
+
1217
+ # Exist.
1218
+ exist = self.rdatabase.build.exist((database, view, None))
1219
+ if exist: continue
1220
+
1221
+ # Create.
1222
+ sql = self.create_view_stats(**params, execute=False)
1223
+
1224
+ # Confirm.
1225
+ self.input_confirm_build(sql)
1226
+
1227
+ # Execute.
1228
+ self.rdatabase(sql)
1229
+
1230
+ # Report.
1231
+ text = f"View '{view}' of database '{database}' build completed."
1232
+ print(text)
1233
+
1234
+
1235
+ __call__ = build