reydb 1.2.2__py3-none-any.whl → 1.2.4__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 CHANGED
@@ -983,7 +983,7 @@ class DatabaseBuildSuper(DatabaseBase, Generic[DatabaseT]):
983
983
  """
984
984
 
985
985
  # Get.
986
- table = model._table()
986
+ table = model._get_table()
987
987
  text = f'TABLE `{self.db.database}`.`{table}`'
988
988
  if 'mysql_charset' in table.kwargs:
989
989
  text += f" | CHARSET '{table.kwargs['mysql_charset']}'"
@@ -1146,7 +1146,7 @@ class DatabaseBuild(DatabaseBuildSuper['rdb.Database']):
1146
1146
  or issubclass(params, DatabaseORMModel)
1147
1147
  ):
1148
1148
  database = self.db.database
1149
- table = params._table().name
1149
+ table = params._get_table().name
1150
1150
 
1151
1151
  ## Exist.
1152
1152
  if (
@@ -1187,6 +1187,227 @@ class DatabaseBuild(DatabaseBuildSuper['rdb.Database']):
1187
1187
 
1188
1188
  ### Execute.
1189
1189
  self.db.execute(sql)
1190
+
1191
+ ## Report.
1192
+ text = f"Table '{table}' of database '{database}' build completed."
1193
+ print(text)
1194
+ refresh_schema = True
1195
+
1196
+ # Refresh schema.
1197
+ if refresh_schema:
1198
+ self.db.schema()
1199
+
1200
+ # View.
1201
+ for params in views:
1202
+ path = params['path']
1203
+ if type(path) == str:
1204
+ database, table = self.db.database, path
1205
+ else:
1206
+ database, table = path
1207
+
1208
+ ## Exist.
1209
+ if (
1210
+ skip
1211
+ and self.db.schema.exist(database, table)
1212
+ ):
1213
+ continue
1214
+
1215
+ ## SQL.
1216
+ sql = self.get_sql_create_view(**params)
1217
+
1218
+ ## Confirm.
1219
+ if ask:
1220
+ self.input_confirm_build(sql)
1221
+
1222
+ ## Execute.
1223
+ self.db.execute(sql)
1224
+
1225
+ ## Report.
1226
+ text = f"View '{table}' of database '{database}' build completed."
1227
+ print(text)
1228
+
1229
+ # View stats.
1230
+ for params in views_stats:
1231
+ path = params['path']
1232
+ if type(path) == str:
1233
+ database, table = self.db.database, path
1234
+ else:
1235
+ database, table = path
1236
+
1237
+ ## Exist.
1238
+ if (
1239
+ skip
1240
+ and self.db.schema.exist(database, table)
1241
+ ):
1242
+ continue
1243
+
1244
+ ## SQL.
1245
+ sql = self.get_sql_create_view_stats(**params)
1246
+
1247
+ ## Confirm.
1248
+ if ask:
1249
+ self.input_confirm_build(sql)
1250
+
1251
+ ## Execute.
1252
+ self.db.execute(sql)
1253
+
1254
+ ## Report.
1255
+ text = f"View '{table}' of database '{database}' build completed."
1256
+ print(text)
1257
+
1258
+
1259
+ __call__ = build
1260
+
1261
+
1262
+ class DatabaseBuildAsync(DatabaseBuildSuper['rdb.DatabaseAsync']):
1263
+ """
1264
+ Asynchronous database build type.
1265
+ """
1266
+
1267
+
1268
+ async def create_orm_table(
1269
+ self,
1270
+ *models: Type[DatabaseORMModel] | DatabaseORMModel,
1271
+ skip: bool = False
1272
+ ) -> None:
1273
+ """
1274
+ Asynchronous create tables by ORM model.
1275
+
1276
+ Parameters
1277
+ ----------
1278
+ models : ORM model instances.
1279
+ skip : Whether skip existing table.
1280
+ """
1281
+
1282
+ # Create.
1283
+ await self.db.orm.create(*models, skip=skip)
1284
+
1285
+
1286
+ async def drop_orm_table(
1287
+ self,
1288
+ *models: Type[DatabaseORMModel] | DatabaseORMModel,
1289
+ skip: bool = False
1290
+ ) -> None:
1291
+ """
1292
+ Asynchronous delete tables by model.
1293
+
1294
+ Parameters
1295
+ ----------
1296
+ models : ORM model instances.
1297
+ skip : Skip not exist table.
1298
+ """
1299
+
1300
+ # Drop.
1301
+ await self.db.orm.drop(*models, skip=skip)
1302
+
1303
+
1304
+ async def build(
1305
+ self,
1306
+ databases: list[dict] | None = None,
1307
+ tables: list[dict] | None = None,
1308
+ tables_orm: list[Type[DatabaseORMModel]] | None = None,
1309
+ views: list[dict] | None = None,
1310
+ views_stats: list[dict] | None = None,
1311
+ ask: bool = True,
1312
+ skip: bool = False
1313
+ ) -> None:
1314
+ """
1315
+ Asynchronous build databases or tables.
1316
+
1317
+ Parameters
1318
+ ----------
1319
+ databases : Database build parameters, equivalent to the parameters of method `self.create_database`.
1320
+ tables : Tables build parameters, equivalent to the parameters of method `self.create_table`.
1321
+ views : Views build parameters, equivalent to the parameters of method `self.create_view`.
1322
+ views_stats : Views stats build parameters, equivalent to the parameters of method `self.create_view_stats`.
1323
+ ask : Whether ask confirm execute.
1324
+ skip : Whether skip existing table.
1325
+ """
1326
+
1327
+ # Set parameter.
1328
+ databases = databases or []
1329
+ tables = tables or []
1330
+ tables_orm = tables_orm or []
1331
+ views = views or []
1332
+ views_stats = views_stats or []
1333
+ refresh_schema = False
1334
+
1335
+ # Database.
1336
+ for params in databases:
1337
+ database = params['name']
1338
+
1339
+ ## Exist.
1340
+ if (
1341
+ skip
1342
+ and await self.db.schema.exist(database)
1343
+ ):
1344
+ continue
1345
+
1346
+ ## SQL.
1347
+ sql = self.get_sql_create_database(**params)
1348
+
1349
+ ## Confirm.
1350
+ if ask:
1351
+ self.input_confirm_build(sql)
1352
+
1353
+ ## Execute.
1354
+ await self.db.execute(sql)
1355
+
1356
+ ## Report.
1357
+ text = f"Database '{database}' build completed."
1358
+ print(text)
1359
+
1360
+ # Table.
1361
+ for params in tables:
1362
+
1363
+ ## ORM.
1364
+ if (
1365
+ is_instance(params)
1366
+ and isinstance(params, DatabaseORMModel)
1367
+ or issubclass(params, DatabaseORMModel)
1368
+ ):
1369
+ database = self.db.database
1370
+ table = params._get_table().name
1371
+
1372
+ ## Exist.
1373
+ if (
1374
+ skip
1375
+ and await self.db.schema.exist(self.db.database, table)
1376
+ ):
1377
+ continue
1378
+
1379
+ ## Confirm.
1380
+ if ask:
1381
+ text = self.get_orm_table_text(params)
1382
+ self.input_confirm_build(text)
1383
+
1384
+ ## Execute.
1385
+ await self.create_orm_table(params)
1386
+
1387
+ ## Parameter.
1388
+ else:
1389
+ path: str | tuple[str, str] = params['path']
1390
+ if type(path) == str:
1391
+ database, table = self.db.database, path
1392
+ else:
1393
+ database, table = path
1394
+
1395
+ ### Exist.
1396
+ if (
1397
+ skip
1398
+ and await self.db.schema.exist(database, table)
1399
+ ):
1400
+ continue
1401
+
1402
+ ### SQL.
1403
+ sql = self.get_sql_create_table(**params)
1404
+
1405
+ ### Confirm.
1406
+ if ask:
1407
+ self.input_confirm_build(sql)
1408
+
1409
+ ### Execute.
1410
+ await self.db.execute(sql)
1190
1411
  refresh_schema = True
1191
1412
 
1192
1413
  ## Report.
@@ -1197,6 +1418,1261 @@ class DatabaseBuild(DatabaseBuildSuper['rdb.Database']):
1197
1418
  if refresh_schema:
1198
1419
  self.db.schema()
1199
1420
 
1421
+ # View.
1422
+ for params in views:
1423
+ path = params['path']
1424
+ if type(path) == str:
1425
+ database, table = self.db.database, path
1426
+ else:
1427
+ database, table = path
1428
+
1429
+ ## Exist.
1430
+ if (
1431
+ skip
1432
+ and await self.db.schema.exist(database, table)
1433
+ ):
1434
+ continue
1435
+
1436
+ ## SQL.
1437
+ sql = self.get_sql_create_view(**params)
1438
+
1439
+ ## Confirm.
1440
+ if ask:
1441
+ self.input_confirm_build(sql)
1442
+
1443
+ ## Execute.
1444
+ await self.db.execute(sql)
1445
+
1446
+ ## Report.
1447
+ text = f"View '{table}' of database '{database}' build completed."
1448
+ print(text)
1449
+
1450
+ # View stats.
1451
+ for params in views_stats:
1452
+ path = params['path']
1453
+ if type(path) == str:
1454
+ database, table = self.db.database, path
1455
+ else:
1456
+ database, table = path
1457
+
1458
+ ## Exist.
1459
+ if (
1460
+ skip
1461
+ and await self.db.schema.exist(database, table)
1462
+ ):
1463
+ continue
1464
+
1465
+ ## SQL.
1466
+ sql = self.get_sql_create_view_stats(**params)
1467
+
1468
+ ## Confirm.
1469
+ if ask:
1470
+ self.input_confirm_build(sql)
1471
+
1472
+ ## Execute.
1473
+ await self.db.execute(sql)
1474
+
1475
+ ## Report.
1476
+ text = f"View '{table}' of database '{database}' build completed."
1477
+ print(text)
1478
+
1479
+
1480
+ __call__ = build
1481
+ # !/usr/bin/env python
1482
+ # -*- coding: utf-8 -*-
1483
+
1484
+ """
1485
+ @Time : 2023-10-14 23:05:35
1486
+ @Author : Rey
1487
+ @Contact : reyxbo@163.com
1488
+ @Explain : Database build methods.
1489
+ """
1490
+
1491
+
1492
+ from typing import TypedDict, NotRequired, Literal, Type, TypeVar, Generic
1493
+ from copy import deepcopy
1494
+ from reykit.rbase import throw, is_instance
1495
+ from reykit.rstdout import ask
1496
+
1497
+ from . import rdb
1498
+ from .rbase import DatabaseBase
1499
+ from .rorm import DatabaseORMModel
1500
+
1501
+
1502
+ __all__ = (
1503
+ 'DatabaseBuildSuper',
1504
+ 'DatabaseBuild',
1505
+ 'DatabaseBuildAsync'
1506
+ )
1507
+
1508
+
1509
+ FieldSet = TypedDict(
1510
+ 'FieldSet',
1511
+ {
1512
+ 'name': str,
1513
+ 'type': str,
1514
+ 'constraint': NotRequired[str | None],
1515
+ 'comment': NotRequired[str | None],
1516
+ 'position': NotRequired[Literal['first'] | str | None]
1517
+ }
1518
+ )
1519
+ type IndexType = Literal['noraml', 'unique', 'fulltext', 'spatial']
1520
+ IndexSet = TypedDict(
1521
+ 'IndexSet',
1522
+ {
1523
+ 'name': str,
1524
+ 'fields' : str | list[str],
1525
+ 'type': IndexType,
1526
+ 'comment': NotRequired[str | None]
1527
+ }
1528
+ )
1529
+ DatabaseT = TypeVar('DatabaseT', 'rdb.Database', 'rdb.DatabaseAsync')
1530
+
1531
+
1532
+ class DatabaseBuildSuper(DatabaseBase, Generic[DatabaseT]):
1533
+ """
1534
+ Database build super type.
1535
+ """
1536
+
1537
+
1538
+ def __init__(self, db: DatabaseT) -> None:
1539
+ """
1540
+ Build instance attributes.
1541
+
1542
+ Parameters
1543
+ ----------
1544
+ db: Database instance.
1545
+ """
1546
+
1547
+ # Set attribute.
1548
+ self.db = db
1549
+
1550
+
1551
+ def get_sql_create_database(
1552
+ self,
1553
+ name: str,
1554
+ character: str = 'utf8mb4',
1555
+ collate: str = 'utf8mb4_0900_ai_ci'
1556
+ ) -> str:
1557
+ """
1558
+ Get SQL of create database.
1559
+
1560
+ Parameters
1561
+ ----------
1562
+ name : Database name.
1563
+ character : Character set.
1564
+ collate : Collate rule.
1565
+ execute : Whether directly execute.
1566
+
1567
+ Returns
1568
+ -------
1569
+ SQL.
1570
+ """
1571
+
1572
+ # Generate.
1573
+ sql = f'CREATE DATABASE `{name}` CHARACTER SET {character} COLLATE {collate}'
1574
+
1575
+ return sql
1576
+
1577
+
1578
+ def __get_field_sql(
1579
+ self,
1580
+ name: str,
1581
+ type_: str,
1582
+ constraint: str = 'DEFAULT NULL',
1583
+ comment: str | None = None,
1584
+ position: str | None = None,
1585
+ old_name: str | None = None
1586
+ ) -> str:
1587
+ """
1588
+ Get a field set SQL.
1589
+
1590
+ Parameters
1591
+ ----------
1592
+ name : Field name.
1593
+ type_ : Field type.
1594
+ constraint : Field constraint.
1595
+ comment : Field comment.
1596
+ position : Field position.
1597
+ old_name : Field old name.
1598
+
1599
+ Returns
1600
+ -------
1601
+ Field set SQL.
1602
+ """
1603
+
1604
+ # Set parameter.
1605
+
1606
+ ## Constraint.
1607
+ constraint = ' ' + constraint
1608
+
1609
+ ## Comment.
1610
+ if comment is None:
1611
+ comment = ''
1612
+ else:
1613
+ comment = f" COMMENT '{comment}'"
1614
+
1615
+ ## Position.
1616
+ match position:
1617
+ case None:
1618
+ position = ''
1619
+ case 'first':
1620
+ position = ' FIRST'
1621
+ case _:
1622
+ position = f' AFTER `{position}`'
1623
+
1624
+ ## Old name.
1625
+ if old_name is None:
1626
+ old_name = ''
1627
+ else:
1628
+ old_name = f'`{old_name}` '
1629
+
1630
+ # Generate.
1631
+ sql = f'{old_name}`{name}` {type_}{constraint}{comment}{position}'
1632
+
1633
+ return sql
1634
+
1635
+
1636
+ def __get_index_sql(
1637
+ self,
1638
+ name: str,
1639
+ fields: str | list[str],
1640
+ type_: IndexType,
1641
+ comment: str | None = None
1642
+ ) -> str:
1643
+ """
1644
+ Get a index set SQL.
1645
+
1646
+ Parameters
1647
+ ----------
1648
+ name : Index name.
1649
+ fields : Index fileds.
1650
+ type\\_ : Index type.
1651
+ comment : Index comment.
1652
+
1653
+ Returns
1654
+ -------
1655
+ Index set SQL.
1656
+ """
1657
+
1658
+ # Set parameter.
1659
+ if fields.__class__ == str:
1660
+ fields = [fields]
1661
+ match type_:
1662
+ case 'noraml':
1663
+ type_ = 'KEY'
1664
+ method = ' USING BTREE'
1665
+ case 'unique':
1666
+ type_ = 'UNIQUE KEY'
1667
+ method = ' USING BTREE'
1668
+ case 'fulltext':
1669
+ type_ = 'FULLTEXT KEY'
1670
+ method = ''
1671
+ case 'spatial':
1672
+ type_ = 'SPATIAL KEY'
1673
+ method = ''
1674
+ case _:
1675
+ throw(ValueError, type_)
1676
+ if comment in (None, ''):
1677
+ comment = ''
1678
+ else:
1679
+ comment = f" COMMENT '{comment}'"
1680
+
1681
+ # Generate.
1682
+
1683
+ ## Fields.
1684
+ sql_fields = ', '.join(
1685
+ [
1686
+ f'`{field}`'
1687
+ for field in fields
1688
+ ]
1689
+ )
1690
+
1691
+ ## Join.
1692
+ sql = f'{type_} `{name}` ({sql_fields}){method}{comment}'
1693
+
1694
+ return sql
1695
+
1696
+
1697
+ def get_sql_create_table(
1698
+ self,
1699
+ path: str | tuple[str, str],
1700
+ fields: FieldSet | list[FieldSet],
1701
+ primary: str | list[str] | None = None,
1702
+ indexes: IndexSet | list[IndexSet] | None = None,
1703
+ engine: str = 'InnoDB',
1704
+ increment: int = 1,
1705
+ charset: str = 'utf8mb4',
1706
+ collate: str = 'utf8mb4_0900_ai_ci',
1707
+ comment: str | None = None
1708
+ ) -> str:
1709
+ """
1710
+ Get SQL of create table.
1711
+
1712
+ Parameters
1713
+ ----------
1714
+ path : Path.
1715
+ - `str`: Table name.
1716
+ - `tuple[str, str]`: Database name and table name.
1717
+ fields : Fields set table.
1718
+ - `Key 'name'`: Field name, required.
1719
+ - `Key 'type'`: Field type, required.
1720
+ - `Key 'constraint'`: Field constraint.
1721
+ `Empty or None`: Use 'DEFAULT NULL'.
1722
+ `str`: Use this value.
1723
+ - `Key 'comment'`: Field comment.
1724
+ `Empty or None`: Not comment.
1725
+ `str`: Use this value.
1726
+ - `Key 'position'`: Field position.
1727
+ `None`: Last.
1728
+ `Literal['first']`: First.
1729
+ `str`: After this field.
1730
+ primary : Primary key fields.
1731
+ - `str`: One field.
1732
+ - `list[str]`: Multiple fileds.
1733
+ indexes : Index set table.
1734
+ - `Key 'name'`: Index name, required.
1735
+ - `Key 'fields'`: Index fields, required.
1736
+ `str`: One field.
1737
+ `list[str]`: Multiple fileds.
1738
+ - `Key 'type'`: Index type.
1739
+ `Literal['noraml']`: Noraml key.
1740
+ `Literal['unique']`: Unique key.
1741
+ `Literal['fulltext']`: Full text key.
1742
+ `Literal['spatial']`: Spatial key.
1743
+ - `Key 'comment'`: Field comment.
1744
+ `Empty or None`: Not comment.
1745
+ `str`: Use this value.
1746
+ engine : Engine type.
1747
+ increment : Automatic Increment start value.
1748
+ charset : Charset type.
1749
+ collate : Collate type.
1750
+ comment : Table comment.
1751
+ execute : Whether directly execute.
1752
+
1753
+ Returns
1754
+ -------
1755
+ SQL.
1756
+ """
1757
+
1758
+ # Set parameter.
1759
+ if type(path) == str:
1760
+ database, table = self.db.database, path
1761
+ else:
1762
+ database, table = path
1763
+ if fields.__class__ == dict:
1764
+ fields = [fields]
1765
+ if primary.__class__ == str:
1766
+ primary = [primary]
1767
+ if primary in ([], ['']):
1768
+ primary = None
1769
+ if indexes.__class__ == dict:
1770
+ indexes = [indexes]
1771
+
1772
+ ## Compatible dictionary key name.
1773
+ fields = deepcopy(fields)
1774
+ for row in fields:
1775
+ row['type_'] = row.pop('type')
1776
+ if indexes is not None:
1777
+ indexes = deepcopy(indexes)
1778
+ for row in indexes:
1779
+ row['type_'] = row.pop('type')
1780
+
1781
+ # Generate.
1782
+
1783
+ ## Fields.
1784
+ sql_fields = [
1785
+ self.__get_field_sql(**field)
1786
+ for field in fields
1787
+ ]
1788
+
1789
+ ## Primary.
1790
+ if primary is not None:
1791
+ keys = ', '.join(
1792
+ [
1793
+ f'`{key}`'
1794
+ for key in primary
1795
+ ]
1796
+ )
1797
+ sql_primary = f'PRIMARY KEY ({keys}) USING BTREE'
1798
+ sql_fields.append(sql_primary)
1799
+
1800
+ ## Indexes.
1801
+ if indexes is not None:
1802
+ sql_indexes = [
1803
+ self.__get_index_sql(**index)
1804
+ for index in indexes
1805
+ ]
1806
+ sql_fields.extend(sql_indexes)
1807
+
1808
+ ## Comment.
1809
+ if comment is None:
1810
+ sql_comment = ''
1811
+ else:
1812
+ sql_comment = f" COMMENT='{comment}'"
1813
+
1814
+ ## Join.
1815
+ sql_fields = ',\n '.join(sql_fields)
1816
+ sql = (
1817
+ f'CREATE TABLE `{database}`.`{table}`(\n'
1818
+ f' {sql_fields}\n'
1819
+ f') ENGINE={engine} AUTO_INCREMENT={increment} CHARSET={charset} COLLATE={collate}{sql_comment}'
1820
+ )
1821
+
1822
+ return sql
1823
+
1824
+
1825
+ def get_sql_create_view(
1826
+ self,
1827
+ path: str | tuple[str, str],
1828
+ select: str
1829
+ ) -> str:
1830
+ """
1831
+ Get SQL of create view.
1832
+
1833
+ Parameters
1834
+ ----------
1835
+ path : Path.
1836
+ - `str`: Table name.
1837
+ - `tuple[str, str]`: Database name and table name.
1838
+ select : View select SQL.
1839
+ execute : Whether directly execute.
1840
+
1841
+ Returns
1842
+ -------
1843
+ SQL.
1844
+ """
1845
+
1846
+ # Set parameter.
1847
+ if type(path) == str:
1848
+ database, table = self.db.database, path
1849
+ else:
1850
+ database, table = path
1851
+
1852
+ # Generate SQL.
1853
+ select = select.replace('\n', '\n ')
1854
+ sql = f'CREATE VIEW `{database}`.`{table}` AS (\n {select}\n)'
1855
+
1856
+ return sql
1857
+
1858
+
1859
+ def get_sql_create_view_stats(
1860
+ self,
1861
+ path: str | tuple[str, str],
1862
+ items: list[dict]
1863
+ ) -> str:
1864
+ """
1865
+ Get SQL of create stats view.
1866
+
1867
+ Parameters
1868
+ ----------
1869
+ path : Path.
1870
+ - `str`: Table name.
1871
+ - `tuple[str, str]`: Database name and table name.
1872
+ items : Items set table.
1873
+ - `Key 'name'`: Item name, required.
1874
+ - `Key 'select'`: Item select SQL, must only return one value, required.
1875
+ - `Key 'comment'`: Item comment.
1876
+ execute : Whether directly execute.
1877
+
1878
+ Returns
1879
+ -------
1880
+ SQL.
1881
+ """
1882
+
1883
+ # Check.
1884
+ if items == []:
1885
+ throw(ValueError, items)
1886
+
1887
+ # Generate select SQL.
1888
+ item_first = items[0]
1889
+ select_first = "SELECT '%s' AS `item`,\n(\n %s\n) AS `value`,\n%s AS `comment`" % (
1890
+ item_first['name'],
1891
+ item_first['select'].replace('\n', '\n '),
1892
+ (
1893
+ 'NULL'
1894
+ if 'comment' not in item_first
1895
+ else "'%s'" % item_first['comment']
1896
+ )
1897
+ )
1898
+ selects = [
1899
+ "SELECT '%s',\n(\n %s\n),\n%s" % (
1900
+ item['name'],
1901
+ item['select'].replace('\n', '\n '),
1902
+ (
1903
+ 'NULL'
1904
+ if 'comment' not in item
1905
+ else "'%s'" % item['comment']
1906
+ )
1907
+ )
1908
+ for item in items[1:]
1909
+ ]
1910
+ selects[0:0] = [select_first]
1911
+ select = '\nUNION\n'.join(selects)
1912
+
1913
+ # Create.
1914
+ sql = self.get_sql_create_view(path, select)
1915
+
1916
+ return sql
1917
+
1918
+
1919
+ def get_sql_drop_database(
1920
+ self,
1921
+ database: str
1922
+ ) -> str:
1923
+ """
1924
+ Get SQL of drop database.
1925
+
1926
+ Parameters
1927
+ ----------
1928
+ database : Database name.
1929
+ execute : Whether directly execute.
1930
+
1931
+ Returns
1932
+ -------
1933
+ SQL.
1934
+ """
1935
+
1936
+ # Generate.
1937
+ sql = f'DROP DATABASE `{database}`'
1938
+
1939
+ return sql
1940
+
1941
+
1942
+ def get_sql_drop_table(
1943
+ self,
1944
+ path: str | tuple[str, str]
1945
+ ) -> str:
1946
+ """
1947
+ Get SQL of drop table.
1948
+
1949
+ Parameters
1950
+ ----------
1951
+ path : Path.
1952
+ - `str`: Table name.
1953
+ - `tuple[str, str]`: Database name and table name.
1954
+ execute : Whether directly execute.
1955
+
1956
+ Returns
1957
+ -------
1958
+ SQL.
1959
+ """
1960
+
1961
+ # Set parameter.
1962
+ if type(path) == str:
1963
+ database, table = self.db.database, path
1964
+ else:
1965
+ database, table = path
1966
+
1967
+ # Generate.
1968
+ sql = f'DROP TABLE `{database}`.`{table}`'
1969
+
1970
+ return sql
1971
+
1972
+
1973
+ def get_sql_drop_view(
1974
+ self,
1975
+ path: str | tuple[str, str]
1976
+ ) -> str:
1977
+ """
1978
+ Get SQL of drop view.
1979
+
1980
+ Parameters
1981
+ ----------
1982
+ path : Path.
1983
+ - `str`: Table name.
1984
+ - `tuple[str, str]`: Database name and table name.
1985
+ execute : Whether directly execute.
1986
+
1987
+ Returns
1988
+ -------
1989
+ SQL.
1990
+ """
1991
+
1992
+ # Set parameter.
1993
+ if type(path) == str:
1994
+ database, table = self.db.database, path
1995
+ else:
1996
+ database, table = path
1997
+
1998
+ # Generate SQL.
1999
+ sql = 'DROP VIEW `%s`.`%s`' % (database, table)
2000
+
2001
+ return sql
2002
+
2003
+
2004
+ def get_sql_alter_database(
2005
+ self,
2006
+ database: str,
2007
+ character: str | None = None,
2008
+ collate: str | None = None
2009
+ ) -> str:
2010
+ """
2011
+ Get SQL of alter database.
2012
+
2013
+ Parameters
2014
+ ----------
2015
+ database : Database name.
2016
+ character : Character set.
2017
+ - `None`: Not alter.
2018
+ - `str`: Alter to this value.
2019
+ collate : Collate rule.
2020
+ - `None`: Not alter.
2021
+ - `str`: Alter to this value.
2022
+ execute : Whether directly execute.
2023
+
2024
+ Returns
2025
+ -------
2026
+ SQL.
2027
+ """
2028
+
2029
+ # Generate.
2030
+
2031
+ ## Character.
2032
+ if character is None:
2033
+ sql_character = ''
2034
+ else:
2035
+ sql_character = f' CHARACTER SET {character}'
2036
+
2037
+ ## Collate.
2038
+ if collate is None:
2039
+ sql_collate = ''
2040
+ else:
2041
+ sql_collate = f' COLLATE {collate}'
2042
+
2043
+ ## Join.
2044
+ sql = f'ALTER DATABASE `{database}`{sql_character}{sql_collate}'
2045
+
2046
+ return sql
2047
+
2048
+
2049
+ def get_sql_alter_table_add(
2050
+ self,
2051
+ path: str | tuple[str, str],
2052
+ fields: FieldSet | list[FieldSet] | None = None,
2053
+ primary: str | list[str] | None = None,
2054
+ indexes: IndexSet | list[IndexSet] | None = None
2055
+ ) -> str:
2056
+ """
2057
+ Get SQL of alter table add filed.
2058
+
2059
+ Parameters
2060
+ ----------
2061
+ path : Path.
2062
+ - `str`: Table name.
2063
+ - `tuple[str, str]`: Database name and table name.
2064
+ fields : Fields set table.
2065
+ - `Key 'name'`: Field name, required.
2066
+ - `Key 'type'`: Field type, required.
2067
+ - `Key 'constraint'`: Field constraint.
2068
+ `Empty or None`: Use 'DEFAULT NULL'.
2069
+ `str`: Use this value.
2070
+ - `Key 'comment'`: Field comment.
2071
+ `Empty or None`: Not comment.
2072
+ `str`: Use this value.
2073
+ - `Key 'position'`: Field position.
2074
+ `None`: Last.
2075
+ `Literal['first']`: First.
2076
+ `str`: After this field.
2077
+ primary : Primary key fields.
2078
+ - `str`: One field.
2079
+ - `list[str]`: Multiple fileds.
2080
+ indexes : Index set table.
2081
+ - `Key 'name'`: Index name, required.
2082
+ - `Key 'fields'`: Index fields, required.
2083
+ `str`: One field.
2084
+ `list[str]`: Multiple fileds.
2085
+ - `Key 'type'`: Index type.
2086
+ `Literal['noraml']`: Noraml key.
2087
+ `Literal['unique']`: Unique key.
2088
+ `Literal['fulltext']`: Full text key.
2089
+ `Literal['spatial']`: Spatial key.
2090
+ - `Key 'comment'`: Field comment.
2091
+ `Empty or None`: Not comment.
2092
+ `str`: Use this value.
2093
+
2094
+ Returns
2095
+ -------
2096
+ SQL.
2097
+ """
2098
+
2099
+ # Set parameter.
2100
+ if type(path) == str:
2101
+ database, table = self.db.database, path
2102
+ else:
2103
+ database, table = path
2104
+ if fields.__class__ == dict:
2105
+ fields = [fields]
2106
+ if primary.__class__ == str:
2107
+ primary = [primary]
2108
+ if primary in ([], ['']):
2109
+ primary = None
2110
+ if indexes.__class__ == dict:
2111
+ indexes = [indexes]
2112
+
2113
+ ## Compatible dictionary key name.
2114
+ fields = deepcopy(fields)
2115
+ for row in fields:
2116
+ row['type_'] = row.pop('type')
2117
+ if indexes is not None:
2118
+ indexes = deepcopy(indexes)
2119
+ for row in indexes:
2120
+ row['type_'] = row.pop('type')
2121
+
2122
+ # Generate.
2123
+ sql_content = []
2124
+
2125
+ ## Fields.
2126
+ if fields is not None:
2127
+ sql_fields = [
2128
+ 'COLUMN ' + self.__get_field_sql(**field)
2129
+ for field in fields
2130
+ ]
2131
+ sql_content.extend(sql_fields)
2132
+
2133
+ ## Primary.
2134
+ if primary is not None:
2135
+ keys = ', '.join(
2136
+ [
2137
+ f'`{key}`'
2138
+ for key in primary
2139
+ ]
2140
+ )
2141
+ sql_primary = f'PRIMARY KEY ({keys}) USING BTREE'
2142
+ sql_content.append(sql_primary)
2143
+
2144
+ ## Indexes.
2145
+ if indexes is not None:
2146
+ sql_indexes = [
2147
+ self.__get_index_sql(**index)
2148
+ for index in indexes
2149
+ ]
2150
+ sql_content.extend(sql_indexes)
2151
+
2152
+ ## Join.
2153
+ sql_content = ',\n ADD '.join(sql_content)
2154
+ sql = (
2155
+ f'ALTER TABLE `{database}`.`{table}`\n'
2156
+ f' ADD {sql_content}'
2157
+ )
2158
+
2159
+ return sql
2160
+
2161
+
2162
+ def get_sql_alter_table_drop(
2163
+ self,
2164
+ path: str | tuple[str, str],
2165
+ fields: str | list[str] | None = None,
2166
+ primary: bool = False,
2167
+ indexes: str | list[str] | None = None
2168
+ ) -> str:
2169
+ """
2170
+ Get SQL of alter table drop field.
2171
+
2172
+ Parameters
2173
+ ----------
2174
+ path : Path.
2175
+ - `str`: Table name.
2176
+ - `tuple[str, str]`: Database name and table name.
2177
+ fields : Delete fields name.
2178
+ primary : Whether delete primary key.
2179
+ indexes : Delete indexes name.
2180
+ execute : Whether directly execute.
2181
+
2182
+ Returns
2183
+ -------
2184
+ SQL.
2185
+ """
2186
+
2187
+ # Set parameter.
2188
+ if type(path) == str:
2189
+ database, table = self.db.database, path
2190
+ else:
2191
+ database, table = path
2192
+ if fields.__class__ == str:
2193
+ fields = [fields]
2194
+ if indexes.__class__ == str:
2195
+ indexes = [indexes]
2196
+
2197
+ # Generate.
2198
+ sql_content = []
2199
+
2200
+ ## Fields.
2201
+ if fields is not None:
2202
+ sql_fields = [
2203
+ 'COLUMN ' + field
2204
+ for field in fields
2205
+ ]
2206
+ sql_content.extend(sql_fields)
2207
+
2208
+ ## Primary.
2209
+ if primary:
2210
+ sql_primary = 'PRIMARY KEY'
2211
+ sql_content.append(sql_primary)
2212
+
2213
+ ## Indexes.
2214
+ if indexes is not None:
2215
+ sql_indexes = [
2216
+ 'INDEX ' + index
2217
+ for index in indexes
2218
+ ]
2219
+ sql_content.extend(sql_indexes)
2220
+
2221
+ ## Join.
2222
+ sql_content = ',\n DROP '.join(sql_content)
2223
+ sql = (
2224
+ f'ALTER TABLE `{database}`.`{table}`\n'
2225
+ f' DROP {sql_content}'
2226
+ )
2227
+
2228
+ return sql
2229
+
2230
+
2231
+ def get_sql_alter_table_change(
2232
+ self,
2233
+ path: str | tuple[str, str],
2234
+ fields: FieldSet | list[FieldSet] | None = None,
2235
+ rename: str | None = None,
2236
+ engine: str | None = None,
2237
+ increment: int | None = None,
2238
+ charset: str | None = None,
2239
+ collate: str | None = None
2240
+ ) -> str:
2241
+ """
2242
+ Get SQL of alter database.
2243
+
2244
+ Parameters
2245
+ ----------
2246
+ path : Path.
2247
+ - `str`: Table name.
2248
+ - `tuple[str, str]`: Database name and table name.
2249
+ fields : Fields set table.
2250
+ - `Key 'name'`: Field name, required.
2251
+ - `Key 'type'`: Field type, required.
2252
+ - `Key 'constraint'`: Field constraint.
2253
+ `Empty or None`: Use 'DEFAULT NULL'.
2254
+ `str`: Use this value.
2255
+ - `Key 'comment'`: Field comment.
2256
+ `Empty or None`: Not comment.
2257
+ `str`: Use this value.
2258
+ - `Key 'position'`: Field position.
2259
+ `None`: Last.
2260
+ `Literal['first']`: First.
2261
+ `str`: After this field.
2262
+ - `Key 'old_name'`: Field old name.
2263
+ rename : Table new name.
2264
+ engine : Engine type.
2265
+ increment : Automatic Increment start value.
2266
+ charset : Charset type.
2267
+ collate : Collate type.
2268
+ execute : Whether directly execute.
2269
+
2270
+ Returns
2271
+ -------
2272
+ SQL.
2273
+ """
2274
+
2275
+ # Set parameter.
2276
+ if type(path) == str:
2277
+ database, table = self.db.database, path
2278
+ else:
2279
+ database, table = path
2280
+ if fields.__class__ == dict:
2281
+ fields = [fields]
2282
+
2283
+ ## Compatible dictionary key name.
2284
+ fields = deepcopy(fields)
2285
+ for row in fields:
2286
+ row['type_'] = row.pop('type')
2287
+
2288
+ # Generate.
2289
+ sql_content = []
2290
+
2291
+ ## Rename.
2292
+ if rename is not None:
2293
+ sql_rename = f'RENAME `{database}`.`{rename}`'
2294
+ sql_content.append(sql_rename)
2295
+
2296
+ ## Fields.
2297
+ if fields is not None:
2298
+ sql_fields = [
2299
+ '%s %s' % (
2300
+ (
2301
+ 'MODIFY'
2302
+ if 'old_name' not in field
2303
+ else 'CHANGE'
2304
+ ),
2305
+ self.__get_field_sql(**field)
2306
+ )
2307
+ for field in fields
2308
+ ]
2309
+ sql_content.extend(sql_fields)
2310
+
2311
+ ## Attribute.
2312
+ sql_attr = []
2313
+
2314
+ ### Engine.
2315
+ if engine is not None:
2316
+ sql_engine = f'ENGINE={engine}'
2317
+ sql_attr.append(sql_engine)
2318
+
2319
+ ### Increment.
2320
+ if increment is not None:
2321
+ sql_increment = f'AUTO_INCREMENT={increment}'
2322
+ sql_attr.append(sql_increment)
2323
+
2324
+ ### Charset.
2325
+ if charset is not None:
2326
+ sql_charset = f'CHARSET={charset}'
2327
+ sql_attr.append(sql_charset)
2328
+
2329
+ ### Collate.
2330
+ if collate is not None:
2331
+ sql_collate = f'COLLATE={collate}'
2332
+ sql_attr.append(sql_collate)
2333
+
2334
+ if sql_attr != []:
2335
+ sql_attr = ' '.join(sql_attr)
2336
+ sql_content.append(sql_attr)
2337
+
2338
+ ## Join.
2339
+ sql_content = ',\n '.join(sql_content)
2340
+ sql = (
2341
+ f'ALTER TABLE `{database}`.`{table}`\n'
2342
+ f' {sql_content}'
2343
+ )
2344
+
2345
+ return sql
2346
+
2347
+
2348
+ def get_sql_alter_view(
2349
+ self,
2350
+ path: str | tuple[str, str],
2351
+ select: str
2352
+ ) -> str:
2353
+ """
2354
+ Get SQL of alter view.
2355
+
2356
+ Parameters
2357
+ ----------
2358
+ path : Path.
2359
+ - `str`: Table name.
2360
+ - `tuple[str, str]`: Database name and table name.
2361
+ select : View select SQL.
2362
+ execute : Whether directly execute.
2363
+
2364
+ Returns
2365
+ -------
2366
+ SQL.
2367
+ """
2368
+
2369
+ # Set parameter.
2370
+ if type(path) == str:
2371
+ database, table = self.db.database, path
2372
+ else:
2373
+ database, table = path
2374
+
2375
+ # Generate SQL.
2376
+ sql = 'ALTER VIEW `%s`.`%s` AS\n%s' % (database, table, select)
2377
+
2378
+ return sql
2379
+
2380
+
2381
+ def get_sql_truncate_table(
2382
+ self,
2383
+ path: str | tuple[str, str]
2384
+ ) -> str:
2385
+ """
2386
+ Get SQL of truncate table.
2387
+
2388
+ Parameters
2389
+ ----------
2390
+ path : Path.
2391
+ - `str`: Table name.
2392
+ - `tuple[str, str]`: Database name and table name.
2393
+ execute : Whether directly execute.
2394
+
2395
+ Returns
2396
+ -------
2397
+ SQL.
2398
+ """
2399
+
2400
+ # Set parameter.
2401
+ if type(path) == str:
2402
+ database, table = self.db.database, path
2403
+ else:
2404
+ database, table = path
2405
+
2406
+ # Generate.
2407
+ sql = f'TRUNCATE TABLE `{database}`.`{table}`'
2408
+
2409
+ return sql
2410
+
2411
+
2412
+ def input_confirm_build(
2413
+ self,
2414
+ sql: str
2415
+ ) -> None:
2416
+ """
2417
+ Print tip text, and confirm execute SQL. If reject, throw exception.
2418
+
2419
+ Parameters
2420
+ ----------
2421
+ sql : SQL.
2422
+ """
2423
+
2424
+ # Confirm.
2425
+ text = 'Do you want to execute SQL to build the database? Otherwise stop program. (y/n) '
2426
+ command = ask(
2427
+ sql,
2428
+ text,
2429
+ title='SQL',
2430
+ frame='top'
2431
+ )
2432
+
2433
+ # Check.
2434
+ while True:
2435
+ command = command.lower()
2436
+ match command:
2437
+
2438
+ ## Confirm.
2439
+ case 'y':
2440
+ break
2441
+
2442
+ ## Stop.
2443
+ case 'n':
2444
+ raise AssertionError('program stop')
2445
+
2446
+ ## Reenter.
2447
+ case _:
2448
+ text = 'Incorrect input, reenter. (y/n) '
2449
+ command = input(text)
2450
+
2451
+
2452
+ def get_orm_table_text(self, model: DatabaseORMModel) -> str:
2453
+ """
2454
+ Get table text from ORM model.
2455
+
2456
+ Parameters
2457
+ ----------
2458
+ model : ORM model instances.
2459
+
2460
+ Returns
2461
+ -------
2462
+ Table text.
2463
+ """
2464
+
2465
+ # Get.
2466
+ table = model._get_table()
2467
+ text = f'TABLE `{self.db.database}`.`{table}`'
2468
+ if 'mysql_charset' in table.kwargs:
2469
+ text += f" | CHARSET '{table.kwargs['mysql_charset']}'"
2470
+ if table.comment:
2471
+ text += f" | COMMENT '{table.comment}'"
2472
+
2473
+ ## Field.
2474
+ text += '\n' + '\n'.join(
2475
+ [
2476
+ f' FIELD `{column.name}` : {column.type}' + (
2477
+ ' | NOT NULL'
2478
+ if (
2479
+ not column.nullable
2480
+ or column.primary_key
2481
+ )
2482
+ else ' | NULL'
2483
+ ) + (
2484
+ ''
2485
+ if not column.primary_key
2486
+ else ' | KEY AUTO'
2487
+ if column.autoincrement
2488
+ else ' | KEY'
2489
+ ) + (
2490
+ f" | DEFAULT '{column.server_default.arg}'"
2491
+ if column.server_default
2492
+ else ''
2493
+ ) + (
2494
+ f" | COMMMENT '{column.comment}'"
2495
+ if column.comment
2496
+ else ''
2497
+ )
2498
+ for column in table.columns
2499
+ ]
2500
+ )
2501
+
2502
+ ## Index.
2503
+ if (table.indexes):
2504
+ text += '\n' + '\n'.join(
2505
+ [
2506
+ (
2507
+ ' UNIQUE'
2508
+ if index.unique
2509
+ else ' NORMAL'
2510
+ ) + f' INDEX `{index.name}` : ' + ', '.join(
2511
+ [
2512
+ f'`{column.name}`'
2513
+ for column in index.expressions
2514
+ ]
2515
+ )
2516
+ for index in table.indexes
2517
+ ]
2518
+ )
2519
+
2520
+ return text
2521
+
2522
+
2523
+ class DatabaseBuild(DatabaseBuildSuper['rdb.Database']):
2524
+ """
2525
+ Database build type.
2526
+ """
2527
+
2528
+
2529
+ def create_orm_table(
2530
+ self,
2531
+ *models: Type[DatabaseORMModel] | DatabaseORMModel,
2532
+ skip: bool = False
2533
+ ) -> None:
2534
+ """
2535
+ Create tables by ORM model.
2536
+
2537
+ Parameters
2538
+ ----------
2539
+ models : ORM model instances.
2540
+ skip : Whether skip existing table.
2541
+ """
2542
+
2543
+ # Create.
2544
+ self.db.orm.create(*models, skip=skip)
2545
+
2546
+
2547
+ def drop_orm_table(
2548
+ self,
2549
+ *models: Type[DatabaseORMModel] | DatabaseORMModel,
2550
+ skip: bool = False
2551
+ ) -> None:
2552
+ """
2553
+ Delete tables by model.
2554
+
2555
+ Parameters
2556
+ ----------
2557
+ models : ORM model instances.
2558
+ skip : Skip not exist table.
2559
+ """
2560
+
2561
+ # Drop.
2562
+ self.db.orm.drop(*models, skip=skip)
2563
+
2564
+
2565
+ def build(
2566
+ self,
2567
+ databases: list[dict] | None = None,
2568
+ tables: list[dict | Type[DatabaseORMModel] | DatabaseORMModel] | None = None,
2569
+ views: list[dict] | None = None,
2570
+ views_stats: list[dict] | None = None,
2571
+ ask: bool = True,
2572
+ skip: bool = False
2573
+ ) -> None:
2574
+ """
2575
+ Build databases or tables.
2576
+
2577
+ Parameters
2578
+ ----------
2579
+ databases : Database build parameters, equivalent to the parameters of method `self.create_database`.
2580
+ tables : Tables build parameters or model, equivalent to the parameters of method `self.create_table` or `self.create_orm_table`.
2581
+ views : Views build parameters, equivalent to the parameters of method `self.create_view`.
2582
+ views_stats : Views stats build parameters, equivalent to the parameters of method `self.create_view_stats`.
2583
+ ask : Whether ask confirm execute.
2584
+ skip : Whether skip existing table.
2585
+ """
2586
+
2587
+ # Set parameter.
2588
+ databases = databases or []
2589
+ tables = tables or []
2590
+ views = views or []
2591
+ views_stats = views_stats or []
2592
+ refresh_schema = False
2593
+
2594
+ # Database.
2595
+ for params in databases:
2596
+ database = params['name']
2597
+
2598
+ ## Exist.
2599
+ if (
2600
+ skip
2601
+ and self.db.schema.exist(database)
2602
+ ):
2603
+ continue
2604
+
2605
+ ## SQL.
2606
+ sql = self.get_sql_create_database(**params)
2607
+
2608
+ ## Confirm.
2609
+ if ask:
2610
+ self.input_confirm_build(sql)
2611
+
2612
+ ## Execute.
2613
+ self.db.execute(sql)
2614
+
2615
+ ## Report.
2616
+ text = f"Database '{database}' build completed."
2617
+ print(text)
2618
+
2619
+ # Table.
2620
+ for params in tables:
2621
+
2622
+ ## Parameter.
2623
+ if type(params) == dict:
2624
+ path: str | tuple[str, str] = params['path']
2625
+ if type(path) == str:
2626
+ database, table = self.db.database, path
2627
+ else:
2628
+ database, table = path
2629
+
2630
+ ### Exist.
2631
+ if (
2632
+ skip
2633
+ and self.db.schema.exist(database, table)
2634
+ ):
2635
+ continue
2636
+
2637
+ ### SQL.
2638
+ sql = self.get_sql_create_table(**params)
2639
+
2640
+ ### Confirm.
2641
+ if ask:
2642
+ self.input_confirm_build(sql)
2643
+
2644
+ ### Execute.
2645
+ self.db.execute(sql)
2646
+
2647
+ ## ORM.
2648
+ else:
2649
+ database = self.db.database
2650
+ table = params._get_table().name
2651
+
2652
+ ## Exist.
2653
+ if (
2654
+ skip
2655
+ and self.db.schema.exist(self.db.database, table)
2656
+ ):
2657
+ continue
2658
+
2659
+ ## Confirm.
2660
+ if ask:
2661
+ text = self.get_orm_table_text(params)
2662
+ self.input_confirm_build(text)
2663
+
2664
+ ## Execute.
2665
+ self.create_orm_table(params)
2666
+
2667
+ ## Report.
2668
+ text = f"Table '{table}' of database '{database}' build completed."
2669
+ print(text)
2670
+ refresh_schema = True
2671
+
2672
+ # Refresh schema.
2673
+ if refresh_schema:
2674
+ self.db.schema()
2675
+
1200
2676
  # View.
1201
2677
  for params in views:
1202
2678
  path = params['path']
@@ -1367,7 +2843,7 @@ class DatabaseBuildAsync(DatabaseBuildSuper['rdb.DatabaseAsync']):
1367
2843
  or issubclass(params, DatabaseORMModel)
1368
2844
  ):
1369
2845
  database = self.db.database
1370
- table = params._table().name
2846
+ table = params._get_table().name
1371
2847
 
1372
2848
  ## Exist.
1373
2849
  if (