mdbq 4.0.59__tar.gz → 4.0.61__tar.gz
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.
- {mdbq-4.0.59 → mdbq-4.0.61}/PKG-INFO +1 -1
- mdbq-4.0.61/mdbq/__version__.py +1 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/uploader.py +437 -41
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq.egg-info/PKG-INFO +1 -1
- mdbq-4.0.59/mdbq/__version__.py +0 -1
- {mdbq-4.0.59 → mdbq-4.0.61}/README.txt +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/__init__.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/log/__init__.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/log/mylogger.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/myconf/__init__.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/myconf/myconf.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/__init__.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/deduplicator.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/mysql.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/s_query.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/mysql/unique_.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/__init__.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/download_sku_picture.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/error_handler.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/otk.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/pov_city.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/other/ua_sj.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/pbix/__init__.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/pbix/pbix_refresh.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/pbix/refresh_all.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/redis/__init__.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/redis/getredis.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq/spider/__init__.py +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq.egg-info/SOURCES.txt +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq.egg-info/dependency_links.txt +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/mdbq.egg-info/top_level.txt +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/setup.cfg +0 -0
- {mdbq-4.0.59 → mdbq-4.0.61}/setup.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
VERSION = '4.0.61'
|
@@ -492,10 +492,30 @@ class MySQLUploader:
|
|
492
492
|
if date_column and date_column in set_typ:
|
493
493
|
safe_date_col = _index_col_sql(date_column)
|
494
494
|
index_defs.append(f"INDEX `idx_{self._normalize_col(date_column)}` ({safe_date_col})")
|
495
|
+
|
496
|
+
# 收集所有唯一约束中涉及的列,避免重复创建普通索引
|
497
|
+
unique_columns = set()
|
498
|
+
if unique_keys:
|
499
|
+
for unique_cols in unique_keys:
|
500
|
+
if unique_cols:
|
501
|
+
for col in unique_cols:
|
502
|
+
normalized_col = self._normalize_col(col)
|
503
|
+
if normalized_col in set_typ:
|
504
|
+
unique_columns.add(normalized_col)
|
505
|
+
|
495
506
|
if indexes:
|
496
507
|
for idx_col in indexes:
|
497
508
|
normalized_idx_col = self._normalize_col(idx_col)
|
498
509
|
if normalized_idx_col in set_typ:
|
510
|
+
# 检查是否与唯一约束冲突
|
511
|
+
if normalized_idx_col in unique_columns:
|
512
|
+
logger.warning('索引列已在唯一约束中定义,跳过普通索引', {
|
513
|
+
'库': db_name,
|
514
|
+
'表': table_name,
|
515
|
+
'列': idx_col,
|
516
|
+
'原因': '列已在唯一约束中定义'
|
517
|
+
})
|
518
|
+
continue
|
499
519
|
safe_idx_col = _index_col_sql(idx_col)
|
500
520
|
index_defs.append(f"INDEX `idx_{normalized_idx_col}` ({safe_idx_col})")
|
501
521
|
else:
|
@@ -1232,22 +1252,48 @@ class MySQLUploader:
|
|
1232
1252
|
:param table_name: 表名
|
1233
1253
|
:param data: 要上传的数据,支持字典、字典列表或DataFrame格式
|
1234
1254
|
:param set_typ: 列名和数据类型字典 {列名: 数据类型}
|
1235
|
-
:param primary_keys:
|
1255
|
+
:param primary_keys: 主键列列表,可选。格式:['col1', 'col2'] 或 None
|
1236
1256
|
:param check_duplicate: 是否检查重复数据,默认为False
|
1237
|
-
:param duplicate_columns:
|
1257
|
+
:param duplicate_columns: 用于检查重复的列,可选。格式:['col1', 'col2'] 或 None
|
1238
1258
|
:param allow_null: 是否允许空值,默认为False
|
1239
1259
|
:param partition_by: 分表方式('year'、'month'、'None'),可选
|
1240
1260
|
:param partition_date_column: 用于分表的日期列名,默认为'日期', 默认会添加为索引
|
1241
1261
|
:param auto_create: 表不存在时是否自动创建,默认为True
|
1242
|
-
:param indexes:
|
1262
|
+
:param indexes: 需要创建索引的列列表,可选。格式:['col1', 'col2'] 或 None
|
1243
1263
|
:param update_on_duplicate: 遇到重复数据时是否更新旧数据,默认为False
|
1244
1264
|
:param transaction_mode: 事务模式,可选值:
|
1245
1265
|
- 'row' : 逐行提交事务(错误隔离性好)
|
1246
1266
|
- 'batch' : 整批提交事务(性能最优)
|
1247
1267
|
- 'hybrid' : 混合模式(每N行提交,平衡性能与安全性)
|
1248
|
-
:param unique_keys:
|
1268
|
+
:param unique_keys: 唯一约束列表,每个元素为列名列表,支持多列组合唯一约束。格式:[['col1', 'col2'], ['col3']] 或 None
|
1249
1269
|
:raises: 可能抛出各种验证和数据库相关异常
|
1250
1270
|
|
1271
|
+
---
|
1272
|
+
参数格式验证:
|
1273
|
+
|
1274
|
+
- primary_keys: 必须是字符串列表或None,如 ['col1', 'col2']
|
1275
|
+
- indexes: 必须是字符串列表或None,如 ['col1', 'col2']
|
1276
|
+
- unique_keys: 必须是嵌套列表或None,如 [['col1', 'col2'], ['col3']]
|
1277
|
+
- 错误示例:unique_keys=['col1', 'col2'] (应该是 [['col1', 'col2']])
|
1278
|
+
- 所有列名不能为空字符串,会自动去除首尾空格
|
1279
|
+
- 重复的列名会被自动去重
|
1280
|
+
|
1281
|
+
空值处理规则:
|
1282
|
+
- None: 直接返回None,忽略此参数
|
1283
|
+
- []: 空列表,返回None,忽略此参数
|
1284
|
+
- [[]]: 包含空列表,跳过空列表,如果最终为空则返回None
|
1285
|
+
- ['']: 包含空字符串,抛出异常(不允许空字符串)
|
1286
|
+
- [' ']: 包含纯空白字符,抛出异常(不允许纯空白字符)
|
1287
|
+
- ['', 'col1']: 混合空字符串和有效字符串,跳过空字符串,保留有效字符串
|
1288
|
+
|
1289
|
+
---
|
1290
|
+
关于 indexes 和 unique_keys 参数:
|
1291
|
+
|
1292
|
+
- indexes 创建普通索引,unique_keys 创建唯一约束
|
1293
|
+
- 如果同一列同时出现在 indexes 和 unique_keys 中,系统会优先创建唯一约束,跳过普通索引
|
1294
|
+
- 唯一约束本身就具有索引功能,因此不会重复创建普通索引
|
1295
|
+
- 建议:如果某列需要唯一性约束,直接使用 unique_keys 参数,无需在 indexes 中重复指定
|
1296
|
+
|
1251
1297
|
---
|
1252
1298
|
unique_keys、check_duplicate、update_on_duplicate 三者组合下的行为总结:
|
1253
1299
|
|
@@ -1278,29 +1324,34 @@ class MySQLUploader:
|
|
1278
1324
|
total_skipped = 0
|
1279
1325
|
total_failed = 0
|
1280
1326
|
|
1281
|
-
logger.info("开始上传", {
|
1282
|
-
'库': db_name,
|
1283
|
-
'表': table_name,
|
1284
|
-
'批次': batch_id,
|
1285
|
-
'传入': len(data) if hasattr(data, '__len__') else 1,
|
1286
|
-
'参数': {
|
1287
|
-
'主键': primary_keys,
|
1288
|
-
'去重': check_duplicate,
|
1289
|
-
'去重列': duplicate_columns,
|
1290
|
-
'允许空值': allow_null,
|
1291
|
-
'分表方式': partition_by,
|
1292
|
-
'分表列': partition_date_column,
|
1293
|
-
# '自动建表': auto_create,
|
1294
|
-
'索引': indexes,
|
1295
|
-
'更新旧数据': update_on_duplicate,
|
1296
|
-
'事务模式': transaction_mode,
|
1297
|
-
'唯一约束': unique_keys
|
1298
|
-
},
|
1299
|
-
# '数据样例': self._shorten_for_log(data, 2)
|
1300
|
-
})
|
1301
|
-
|
1302
1327
|
try:
|
1303
|
-
#
|
1328
|
+
# 验证参数格式
|
1329
|
+
validated_primary_keys = self._validate_primary_keys_format(primary_keys, db_name, table_name)
|
1330
|
+
validated_indexes = self._validate_indexes_format(indexes, db_name, table_name)
|
1331
|
+
validated_unique_keys = self._validate_unique_keys_format(unique_keys, db_name, table_name)
|
1332
|
+
|
1333
|
+
logger.info("开始上传", {
|
1334
|
+
'库': db_name,
|
1335
|
+
'表': table_name,
|
1336
|
+
'批次': batch_id,
|
1337
|
+
'传入': len(data) if hasattr(data, '__len__') else 1,
|
1338
|
+
'参数': {
|
1339
|
+
'主键': validated_primary_keys,
|
1340
|
+
'去重': check_duplicate,
|
1341
|
+
'去重列': duplicate_columns,
|
1342
|
+
'允许空值': allow_null,
|
1343
|
+
'分表方式': partition_by,
|
1344
|
+
'分表列': partition_date_column,
|
1345
|
+
# '自动建表': auto_create,
|
1346
|
+
'索引': validated_indexes,
|
1347
|
+
'更新旧数据': update_on_duplicate,
|
1348
|
+
'事务模式': transaction_mode,
|
1349
|
+
'唯一约束': validated_unique_keys
|
1350
|
+
},
|
1351
|
+
# '数据样例': self._shorten_for_log(data, 2)
|
1352
|
+
})
|
1353
|
+
|
1354
|
+
# 验证分表参数
|
1304
1355
|
if partition_by:
|
1305
1356
|
partition_by = str(partition_by).lower()
|
1306
1357
|
if partition_by not in ['year', 'month']:
|
@@ -1370,10 +1421,10 @@ class MySQLUploader:
|
|
1370
1421
|
try:
|
1371
1422
|
inserted, skipped, failed = self._upload_to_table(
|
1372
1423
|
db_name, part_table, part_data, filtered_set_typ,
|
1373
|
-
|
1424
|
+
validated_primary_keys, check_duplicate, duplicate_columns,
|
1374
1425
|
allow_null, auto_create, partition_date_column,
|
1375
|
-
|
1376
|
-
|
1426
|
+
validated_indexes, batch_id, update_on_duplicate, transaction_mode,
|
1427
|
+
validated_unique_keys
|
1377
1428
|
)
|
1378
1429
|
total_inserted += inserted
|
1379
1430
|
total_skipped += skipped
|
@@ -1397,10 +1448,10 @@ class MySQLUploader:
|
|
1397
1448
|
# 不分表,直接上传
|
1398
1449
|
inserted, skipped, failed = self._upload_to_table(
|
1399
1450
|
db_name, table_name, prepared_data, filtered_set_typ,
|
1400
|
-
|
1451
|
+
validated_primary_keys, check_duplicate, duplicate_columns,
|
1401
1452
|
allow_null, auto_create, partition_date_column,
|
1402
|
-
|
1403
|
-
|
1453
|
+
validated_indexes, batch_id, update_on_duplicate, transaction_mode,
|
1454
|
+
validated_unique_keys
|
1404
1455
|
)
|
1405
1456
|
total_inserted = inserted
|
1406
1457
|
total_skipped = skipped
|
@@ -1435,8 +1486,16 @@ class MySQLUploader:
|
|
1435
1486
|
'失败': total_failed
|
1436
1487
|
})
|
1437
1488
|
|
1438
|
-
#
|
1439
|
-
|
1489
|
+
# 更新索引(只有在成功时才执行)
|
1490
|
+
if success_flag and 'validated_indexes' in locals():
|
1491
|
+
try:
|
1492
|
+
self._update_indexes(db_name, table_name, validated_indexes)
|
1493
|
+
except Exception as e:
|
1494
|
+
logger.warning('更新索引时发生错误', {
|
1495
|
+
'库': db_name,
|
1496
|
+
'表': table_name,
|
1497
|
+
'错误': str(e)
|
1498
|
+
})
|
1440
1499
|
return True
|
1441
1500
|
|
1442
1501
|
@_execute_with_retry
|
@@ -1835,6 +1894,7 @@ class MySQLUploader:
|
|
1835
1894
|
def _update_indexes(self, db_name: str, table_name: str, indexes: Optional[List[str]]):
|
1836
1895
|
"""
|
1837
1896
|
更新索引,避免重复添加或更新,同时注意大小写一致性。
|
1897
|
+
注意:如果列已经在unique_keys中定义,则不会重复创建普通索引。
|
1838
1898
|
|
1839
1899
|
:param db_name: 数据库名
|
1840
1900
|
:param table_name: 表名
|
@@ -1846,7 +1906,7 @@ class MySQLUploader:
|
|
1846
1906
|
# 规范化索引列名
|
1847
1907
|
normalized_indexes = [self._normalize_col(idx) for idx in indexes]
|
1848
1908
|
|
1849
|
-
#
|
1909
|
+
# 获取现有索引(包括普通索引和唯一约束)
|
1850
1910
|
try:
|
1851
1911
|
existing_indexes = self._get_existing_indexes(db_name, table_name)
|
1852
1912
|
except Exception as e:
|
@@ -1855,13 +1915,20 @@ class MySQLUploader:
|
|
1855
1915
|
|
1856
1916
|
# 获取表中现有的列名
|
1857
1917
|
try:
|
1858
|
-
existing_columns = self.
|
1918
|
+
existing_columns = self._get_table_columns(db_name, table_name)
|
1859
1919
|
except Exception as e:
|
1860
1920
|
logger.error('获取现有列时发生错误', {'库': db_name, '表': table_name, '错误': str(e)})
|
1861
1921
|
raise
|
1862
1922
|
|
1863
|
-
#
|
1864
|
-
indexes_to_add = [
|
1923
|
+
# 找出需要添加的索引(排除已存在的索引和不在表中的列)
|
1924
|
+
indexes_to_add = []
|
1925
|
+
for idx in normalized_indexes:
|
1926
|
+
if idx not in existing_indexes and idx in existing_columns:
|
1927
|
+
indexes_to_add.append(idx)
|
1928
|
+
elif idx in existing_indexes:
|
1929
|
+
logger.debug('索引已存在,跳过', {'库': db_name, '表': table_name, '列': idx})
|
1930
|
+
elif idx not in existing_columns:
|
1931
|
+
logger.warning('索引列不存在于表中,跳过', {'库': db_name, '表': table_name, '列': idx})
|
1865
1932
|
|
1866
1933
|
# 添加新索引
|
1867
1934
|
for idx in indexes_to_add:
|
@@ -1873,7 +1940,7 @@ class MySQLUploader:
|
|
1873
1940
|
|
1874
1941
|
def _get_existing_indexes(self, db_name: str, table_name: str) -> Set[str]:
|
1875
1942
|
"""
|
1876
|
-
|
1943
|
+
获取表中现有的索引列名(包括普通索引和唯一约束)。
|
1877
1944
|
|
1878
1945
|
:param db_name: 数据库名
|
1879
1946
|
:param table_name: 表名
|
@@ -2007,6 +2074,222 @@ class MySQLUploader:
|
|
2007
2074
|
conn.rollback()
|
2008
2075
|
raise
|
2009
2076
|
|
2077
|
+
def _validate_unique_keys_format(self, unique_keys: Optional[List[List[str]]], db_name: str = None, table_name: str = None) -> Optional[List[List[str]]]:
|
2078
|
+
"""
|
2079
|
+
验证unique_keys参数的格式是否正确
|
2080
|
+
|
2081
|
+
:param unique_keys: 唯一约束列表
|
2082
|
+
:param db_name: 数据库名,用于日志记录
|
2083
|
+
:param table_name: 表名,用于日志记录
|
2084
|
+
:return: 验证后的unique_keys,如果验证失败则抛出异常
|
2085
|
+
:raises ValueError: 当参数格式不正确时抛出
|
2086
|
+
"""
|
2087
|
+
if unique_keys is None:
|
2088
|
+
return None
|
2089
|
+
|
2090
|
+
if not isinstance(unique_keys, list):
|
2091
|
+
error_msg = f"unique_keys参数必须是列表类型,当前类型: {type(unique_keys).__name__}"
|
2092
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2093
|
+
raise ValueError(error_msg)
|
2094
|
+
|
2095
|
+
# 检查是否为空列表
|
2096
|
+
if len(unique_keys) == 0:
|
2097
|
+
logger.warning('unique_keys为空列表,将忽略此参数', {'库': db_name, '表': table_name})
|
2098
|
+
return None
|
2099
|
+
|
2100
|
+
validated_keys = []
|
2101
|
+
empty_groups_count = 0
|
2102
|
+
|
2103
|
+
for i, key_group in enumerate(unique_keys):
|
2104
|
+
# 检查每个元素是否为列表
|
2105
|
+
if not isinstance(key_group, list):
|
2106
|
+
error_msg = f"unique_keys[{i}]必须是列表类型,当前类型: {type(key_group).__name__},值: {key_group}"
|
2107
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2108
|
+
raise ValueError(error_msg)
|
2109
|
+
|
2110
|
+
# 检查是否为空列表
|
2111
|
+
if len(key_group) == 0:
|
2112
|
+
empty_groups_count += 1
|
2113
|
+
logger.warning(f'unique_keys[{i}]为空列表,跳过', {'库': db_name, '表': table_name})
|
2114
|
+
continue
|
2115
|
+
|
2116
|
+
# 检查每个列名是否为字符串
|
2117
|
+
validated_group = []
|
2118
|
+
for j, col_name in enumerate(key_group):
|
2119
|
+
if not isinstance(col_name, str):
|
2120
|
+
error_msg = f"unique_keys[{i}][{j}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
|
2121
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2122
|
+
raise ValueError(error_msg)
|
2123
|
+
|
2124
|
+
# 检查是否为空字符串或纯空白字符
|
2125
|
+
stripped_name = col_name.strip()
|
2126
|
+
if not stripped_name:
|
2127
|
+
error_msg = f"unique_keys[{i}][{j}]不能为空字符串或纯空白字符,原始值: '{col_name}'"
|
2128
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2129
|
+
raise ValueError(error_msg)
|
2130
|
+
|
2131
|
+
validated_group.append(stripped_name)
|
2132
|
+
|
2133
|
+
# 去重并检查是否有重复列名
|
2134
|
+
if len(validated_group) != len(set(validated_group)):
|
2135
|
+
error_msg = f"unique_keys[{i}]中存在重复列名: {validated_group}"
|
2136
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2137
|
+
raise ValueError(error_msg)
|
2138
|
+
|
2139
|
+
validated_keys.append(validated_group)
|
2140
|
+
|
2141
|
+
# 检查验证后的结果
|
2142
|
+
if not validated_keys:
|
2143
|
+
if empty_groups_count > 0:
|
2144
|
+
logger.warning(f'unique_keys包含{empty_groups_count}个空列表,验证后为空,将忽略此参数', {
|
2145
|
+
'库': db_name, '表': table_name, '空列表数量': empty_groups_count
|
2146
|
+
})
|
2147
|
+
else:
|
2148
|
+
logger.warning('unique_keys验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
|
2149
|
+
return None
|
2150
|
+
|
2151
|
+
logger.debug('unique_keys格式验证通过', {
|
2152
|
+
'库': db_name,
|
2153
|
+
'表': table_name,
|
2154
|
+
'原始': unique_keys,
|
2155
|
+
'验证后': validated_keys,
|
2156
|
+
'跳过的空列表': empty_groups_count
|
2157
|
+
})
|
2158
|
+
return validated_keys
|
2159
|
+
|
2160
|
+
def _validate_indexes_format(self, indexes: Optional[List[str]], db_name: str = None, table_name: str = None) -> Optional[List[str]]:
|
2161
|
+
"""
|
2162
|
+
验证indexes参数的格式是否正确
|
2163
|
+
|
2164
|
+
:param indexes: 索引列列表
|
2165
|
+
:param db_name: 数据库名,用于日志记录
|
2166
|
+
:param table_name: 表名,用于日志记录
|
2167
|
+
:return: 验证后的indexes,如果验证失败则抛出异常
|
2168
|
+
:raises ValueError: 当参数格式不正确时抛出
|
2169
|
+
"""
|
2170
|
+
if indexes is None:
|
2171
|
+
return None
|
2172
|
+
|
2173
|
+
if not isinstance(indexes, list):
|
2174
|
+
error_msg = f"indexes参数必须是列表类型,当前类型: {type(indexes).__name__}"
|
2175
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'indexes': indexes})
|
2176
|
+
raise ValueError(error_msg)
|
2177
|
+
|
2178
|
+
# 检查是否为空列表
|
2179
|
+
if len(indexes) == 0:
|
2180
|
+
logger.warning('indexes为空列表,将忽略此参数', {'库': db_name, '表': table_name})
|
2181
|
+
return None
|
2182
|
+
|
2183
|
+
validated_indexes = []
|
2184
|
+
empty_strings_count = 0
|
2185
|
+
|
2186
|
+
for i, col_name in enumerate(indexes):
|
2187
|
+
if not isinstance(col_name, str):
|
2188
|
+
error_msg = f"indexes[{i}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
|
2189
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'indexes': indexes})
|
2190
|
+
raise ValueError(error_msg)
|
2191
|
+
|
2192
|
+
# 检查是否为空字符串或纯空白字符
|
2193
|
+
stripped_name = col_name.strip()
|
2194
|
+
if not stripped_name:
|
2195
|
+
empty_strings_count += 1
|
2196
|
+
logger.warning(f'indexes[{i}]为空字符串或纯空白字符,跳过,原始值: "{col_name}"', {
|
2197
|
+
'库': db_name, '表': table_name, 'indexes': indexes
|
2198
|
+
})
|
2199
|
+
continue
|
2200
|
+
|
2201
|
+
validated_indexes.append(stripped_name)
|
2202
|
+
|
2203
|
+
# 去重
|
2204
|
+
validated_indexes = list(dict.fromkeys(validated_indexes))
|
2205
|
+
|
2206
|
+
# 检查验证后的结果
|
2207
|
+
if not validated_indexes:
|
2208
|
+
if empty_strings_count > 0:
|
2209
|
+
logger.warning(f'indexes包含{empty_strings_count}个空字符串,验证后为空,将忽略此参数', {
|
2210
|
+
'库': db_name, '表': table_name, '空字符串数量': empty_strings_count
|
2211
|
+
})
|
2212
|
+
else:
|
2213
|
+
logger.warning('indexes验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
|
2214
|
+
return None
|
2215
|
+
|
2216
|
+
logger.debug('indexes格式验证通过', {
|
2217
|
+
'库': db_name,
|
2218
|
+
'表': table_name,
|
2219
|
+
'原始': indexes,
|
2220
|
+
'验证后': validated_indexes,
|
2221
|
+
'跳过的空字符串': empty_strings_count
|
2222
|
+
})
|
2223
|
+
return validated_indexes
|
2224
|
+
|
2225
|
+
def _validate_primary_keys_format(self, primary_keys: Optional[List[str]], db_name: str = None, table_name: str = None) -> Optional[List[str]]:
|
2226
|
+
"""
|
2227
|
+
验证primary_keys参数的格式是否正确
|
2228
|
+
|
2229
|
+
:param primary_keys: 主键列列表
|
2230
|
+
:param db_name: 数据库名,用于日志记录
|
2231
|
+
:param table_name: 表名,用于日志记录
|
2232
|
+
:return: 验证后的primary_keys,如果验证失败则抛出异常
|
2233
|
+
:raises ValueError: 当参数格式不正确时抛出
|
2234
|
+
"""
|
2235
|
+
if primary_keys is None:
|
2236
|
+
return None
|
2237
|
+
|
2238
|
+
if not isinstance(primary_keys, list):
|
2239
|
+
error_msg = f"primary_keys参数必须是列表类型,当前类型: {type(primary_keys).__name__}"
|
2240
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
|
2241
|
+
raise ValueError(error_msg)
|
2242
|
+
|
2243
|
+
# 检查是否为空列表
|
2244
|
+
if len(primary_keys) == 0:
|
2245
|
+
logger.warning('primary_keys为空列表,将忽略此参数', {'库': db_name, '表': table_name})
|
2246
|
+
return None
|
2247
|
+
|
2248
|
+
validated_keys = []
|
2249
|
+
empty_strings_count = 0
|
2250
|
+
|
2251
|
+
for i, col_name in enumerate(primary_keys):
|
2252
|
+
if not isinstance(col_name, str):
|
2253
|
+
error_msg = f"primary_keys[{i}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
|
2254
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
|
2255
|
+
raise ValueError(error_msg)
|
2256
|
+
|
2257
|
+
# 检查是否为空字符串或纯空白字符
|
2258
|
+
stripped_name = col_name.strip()
|
2259
|
+
if not stripped_name:
|
2260
|
+
empty_strings_count += 1
|
2261
|
+
logger.warning(f'primary_keys[{i}]为空字符串或纯空白字符,跳过,原始值: "{col_name}"', {
|
2262
|
+
'库': db_name, '表': table_name, 'primary_keys': primary_keys
|
2263
|
+
})
|
2264
|
+
continue
|
2265
|
+
|
2266
|
+
validated_keys.append(stripped_name)
|
2267
|
+
|
2268
|
+
# 去重并检查是否有重复列名
|
2269
|
+
if len(validated_keys) != len(set(validated_keys)):
|
2270
|
+
error_msg = f"primary_keys中存在重复列名: {validated_keys}"
|
2271
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
|
2272
|
+
raise ValueError(error_msg)
|
2273
|
+
|
2274
|
+
# 检查验证后的结果
|
2275
|
+
if not validated_keys:
|
2276
|
+
if empty_strings_count > 0:
|
2277
|
+
logger.warning(f'primary_keys包含{empty_strings_count}个空字符串,验证后为空,将忽略此参数', {
|
2278
|
+
'库': db_name, '表': table_name, '空字符串数量': empty_strings_count
|
2279
|
+
})
|
2280
|
+
else:
|
2281
|
+
logger.warning('primary_keys验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
|
2282
|
+
return None
|
2283
|
+
|
2284
|
+
logger.debug('primary_keys格式验证通过', {
|
2285
|
+
'库': db_name,
|
2286
|
+
'表': table_name,
|
2287
|
+
'原始': primary_keys,
|
2288
|
+
'验证后': validated_keys,
|
2289
|
+
'跳过的空字符串': empty_strings_count
|
2290
|
+
})
|
2291
|
+
return validated_keys
|
2292
|
+
|
2010
2293
|
|
2011
2294
|
def main():
|
2012
2295
|
dir_path = os.path.expanduser("~")
|
@@ -2042,7 +2325,120 @@ def main():
|
|
2042
2325
|
{'日期': '2023-02-20', 'name': 'Bob', 'AGE': 25, 'salary': 45000.75},
|
2043
2326
|
]
|
2044
2327
|
|
2045
|
-
#
|
2328
|
+
# 测试参数验证功能
|
2329
|
+
print("=== 测试参数验证功能 ===")
|
2330
|
+
|
2331
|
+
# 正确的格式
|
2332
|
+
print("1. 测试正确的unique_keys格式:")
|
2333
|
+
try:
|
2334
|
+
valid_unique_keys = [['日期', 'name'], ['age']]
|
2335
|
+
result = uploader._validate_unique_keys_format(valid_unique_keys, 'test_db', 'test_table')
|
2336
|
+
print(f" 通过: {result}")
|
2337
|
+
except Exception as e:
|
2338
|
+
print(f" 失败: {e}")
|
2339
|
+
|
2340
|
+
# 错误的格式 - 缺少一层嵌套
|
2341
|
+
print("2. 测试错误的unique_keys格式 (缺少嵌套):")
|
2342
|
+
try:
|
2343
|
+
invalid_unique_keys = ['日期', 'name'] # 错误:应该是 [['日期', 'name']]
|
2344
|
+
result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
|
2345
|
+
print(f" 通过: {result}")
|
2346
|
+
except Exception as e:
|
2347
|
+
print(f" 正确捕获错误: {e}")
|
2348
|
+
|
2349
|
+
# 错误的格式 - 包含非字符串元素
|
2350
|
+
print("3. 测试错误的unique_keys格式 (非字符串元素):")
|
2351
|
+
try:
|
2352
|
+
invalid_unique_keys = [['日期', 123]] # 错误:123不是字符串
|
2353
|
+
result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
|
2354
|
+
print(f" 通过: {result}")
|
2355
|
+
except Exception as e:
|
2356
|
+
print(f" 正确捕获错误: {e}")
|
2357
|
+
|
2358
|
+
# 错误的格式 - 空字符串
|
2359
|
+
print("4. 测试错误的unique_keys格式 (空字符串):")
|
2360
|
+
try:
|
2361
|
+
invalid_unique_keys = [['日期', '']] # 错误:空字符串
|
2362
|
+
result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
|
2363
|
+
print(f" 通过: {result}")
|
2364
|
+
except Exception as e:
|
2365
|
+
print(f" 正确捕获错误: {e}")
|
2366
|
+
|
2367
|
+
# 错误的格式 - 重复列名
|
2368
|
+
print("5. 测试错误的unique_keys格式 (重复列名):")
|
2369
|
+
try:
|
2370
|
+
invalid_unique_keys = [['日期', '日期']] # 错误:重复列名
|
2371
|
+
result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
|
2372
|
+
print(f" 通过: {result}")
|
2373
|
+
except Exception as e:
|
2374
|
+
print(f" 正确捕获错误: {e}")
|
2375
|
+
|
2376
|
+
# 空值测试 - 空列表
|
2377
|
+
print("6. 测试空值情况 - 空列表:")
|
2378
|
+
try:
|
2379
|
+
empty_list = []
|
2380
|
+
result = uploader._validate_unique_keys_format(empty_list, 'test_db', 'test_table')
|
2381
|
+
print(f" 通过: {result}")
|
2382
|
+
except Exception as e:
|
2383
|
+
print(f" 失败: {e}")
|
2384
|
+
|
2385
|
+
# 空值测试 - 包含空列表
|
2386
|
+
print("7. 测试空值情况 - 包含空列表 [[]]:")
|
2387
|
+
try:
|
2388
|
+
empty_nested = [[]]
|
2389
|
+
result = uploader._validate_unique_keys_format(empty_nested, 'test_db', 'test_table')
|
2390
|
+
print(f" 通过: {result}")
|
2391
|
+
except Exception as e:
|
2392
|
+
print(f" 失败: {e}")
|
2393
|
+
|
2394
|
+
# 空值测试 - 混合空列表和有效列表
|
2395
|
+
print("8. 测试空值情况 - 混合空列表和有效列表 [[], ['col1']]:")
|
2396
|
+
try:
|
2397
|
+
mixed_empty = [[], ['col1']]
|
2398
|
+
result = uploader._validate_unique_keys_format(mixed_empty, 'test_db', 'test_table')
|
2399
|
+
print(f" 通过: {result}")
|
2400
|
+
except Exception as e:
|
2401
|
+
print(f" 失败: {e}")
|
2402
|
+
|
2403
|
+
# 空值测试 - 包含空字符串的列表
|
2404
|
+
print("9. 测试空值情况 - 包含空字符串的列表 [[''], ['col1']]:")
|
2405
|
+
try:
|
2406
|
+
empty_string_list = [[''], ['col1']]
|
2407
|
+
result = uploader._validate_unique_keys_format(empty_string_list, 'test_db', 'test_table')
|
2408
|
+
print(f" 通过: {result}")
|
2409
|
+
except Exception as e:
|
2410
|
+
print(f" 正确捕获错误: {e}")
|
2411
|
+
|
2412
|
+
# 空值测试 - 包含纯空白字符的列表
|
2413
|
+
print("10. 测试空值情况 - 包含纯空白字符的列表 [[' '], ['col1']]:")
|
2414
|
+
try:
|
2415
|
+
whitespace_list = [[' '], ['col1']]
|
2416
|
+
result = uploader._validate_unique_keys_format(whitespace_list, 'test_db', 'test_table')
|
2417
|
+
print(f" 通过: {result}")
|
2418
|
+
except Exception as e:
|
2419
|
+
print(f" 正确捕获错误: {e}")
|
2420
|
+
|
2421
|
+
# 测试indexes的空值处理
|
2422
|
+
print("\n=== 测试indexes空值处理 ===")
|
2423
|
+
print("11. 测试indexes包含空字符串 ['', 'col1']:")
|
2424
|
+
try:
|
2425
|
+
indexes_with_empty = ['', 'col1']
|
2426
|
+
result = uploader._validate_indexes_format(indexes_with_empty, 'test_db', 'test_table')
|
2427
|
+
print(f" 通过: {result}")
|
2428
|
+
except Exception as e:
|
2429
|
+
print(f" 失败: {e}")
|
2430
|
+
|
2431
|
+
# 测试primary_keys的空值处理
|
2432
|
+
print("12. 测试primary_keys包含空字符串 ['', 'col1']:")
|
2433
|
+
try:
|
2434
|
+
primary_keys_with_empty = ['', 'col1']
|
2435
|
+
result = uploader._validate_primary_keys_format(primary_keys_with_empty, 'test_db', 'test_table')
|
2436
|
+
print(f" 通过: {result}")
|
2437
|
+
except Exception as e:
|
2438
|
+
print(f" 失败: {e}")
|
2439
|
+
|
2440
|
+
# 上传数据(使用正确的格式)
|
2441
|
+
print("\n=== 开始上传数据 ===")
|
2046
2442
|
uploader.upload_data(
|
2047
2443
|
db_name='测试库',
|
2048
2444
|
table_name='测试表',
|
@@ -2057,7 +2453,7 @@ def main():
|
|
2057
2453
|
partition_date_column='日期', # 用于分表的日期列名,默认为'日期'
|
2058
2454
|
indexes=[], # 普通索引列
|
2059
2455
|
transaction_mode='row', # 事务模式
|
2060
|
-
unique_keys=[['日期', 'name', 'age']], # 唯一约束列表
|
2456
|
+
unique_keys=[['日期', 'name', 'age']], # 唯一约束列表 - 正确的格式
|
2061
2457
|
)
|
2062
2458
|
|
2063
2459
|
uploader.close()
|
mdbq-4.0.59/mdbq/__version__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
VERSION = '4.0.59'
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|