mdbq 4.0.58__tar.gz → 4.0.60__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.58 → mdbq-4.0.60}/PKG-INFO +1 -1
- mdbq-4.0.60/mdbq/__version__.py +1 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/uploader.py +490 -29
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq.egg-info/PKG-INFO +1 -1
- mdbq-4.0.58/mdbq/__version__.py +0 -1
- {mdbq-4.0.58 → mdbq-4.0.60}/README.txt +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/__init__.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/log/__init__.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/log/mylogger.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/myconf/__init__.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/myconf/myconf.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/__init__.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/deduplicator.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/mysql.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/s_query.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/unique_.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/__init__.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/download_sku_picture.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/error_handler.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/otk.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/pov_city.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/ua_sj.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/pbix/__init__.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/pbix/pbix_refresh.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/pbix/refresh_all.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/redis/__init__.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/redis/getredis.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/spider/__init__.py +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq.egg-info/SOURCES.txt +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq.egg-info/dependency_links.txt +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/mdbq.egg-info/top_level.txt +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/setup.cfg +0 -0
- {mdbq-4.0.58 → mdbq-4.0.60}/setup.py +0 -0
@@ -0,0 +1 @@
|
|
1
|
+
VERSION = '4.0.60'
|
@@ -348,6 +348,10 @@ class MySQLUploader:
|
|
348
348
|
import hashlib
|
349
349
|
hash_suffix = hashlib.md5(identifier.encode('utf-8')).hexdigest()[:8]
|
350
350
|
cleaned = f'unknown_col_{hash_suffix}'
|
351
|
+
|
352
|
+
# 确保标识符不以数字开头(MySQL要求)
|
353
|
+
if cleaned and cleaned[0].isdigit():
|
354
|
+
cleaned = f'col_{cleaned}'
|
351
355
|
mysql_keywords = {
|
352
356
|
'select', 'insert', 'update', 'delete', 'from', 'where', 'and', 'or',
|
353
357
|
'not', 'like', 'in', 'is', 'null', 'true', 'false', 'between'
|
@@ -452,9 +456,33 @@ class MySQLUploader:
|
|
452
456
|
|
453
457
|
# 处理主键
|
454
458
|
if primary_keys and len(primary_keys) > 0:
|
455
|
-
#
|
456
|
-
|
457
|
-
|
459
|
+
# 验证主键列是否存在于set_typ中
|
460
|
+
valid_primary_keys = []
|
461
|
+
for pk in primary_keys:
|
462
|
+
normalized_pk = self._normalize_col(pk)
|
463
|
+
if normalized_pk in set_typ:
|
464
|
+
valid_primary_keys.append(pk)
|
465
|
+
else:
|
466
|
+
logger.warning('主键列不存在于表结构中,跳过', {
|
467
|
+
'库': db_name,
|
468
|
+
'表': table_name,
|
469
|
+
'列': pk,
|
470
|
+
'规范化后': normalized_pk,
|
471
|
+
'可用列': list(set_typ.keys())
|
472
|
+
})
|
473
|
+
|
474
|
+
if valid_primary_keys:
|
475
|
+
# 如果指定了主键,直接使用指定的主键
|
476
|
+
safe_primary_keys = [_index_col_sql(pk) for pk in valid_primary_keys]
|
477
|
+
primary_key_sql = f"PRIMARY KEY ({','.join(safe_primary_keys)})"
|
478
|
+
else:
|
479
|
+
# 如果没有有效的主键,使用id作为主键
|
480
|
+
logger.warning('所有主键列都不存在于表结构中,使用默认id主键', {
|
481
|
+
'库': db_name,
|
482
|
+
'表': table_name,
|
483
|
+
'原始主键': primary_keys
|
484
|
+
})
|
485
|
+
primary_key_sql = f"PRIMARY KEY (`id`)"
|
458
486
|
else:
|
459
487
|
# 如果没有指定主键,使用id作为主键
|
460
488
|
primary_key_sql = f"PRIMARY KEY (`id`)"
|
@@ -464,11 +492,40 @@ class MySQLUploader:
|
|
464
492
|
if date_column and date_column in set_typ:
|
465
493
|
safe_date_col = _index_col_sql(date_column)
|
466
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
|
+
|
467
506
|
if indexes:
|
468
507
|
for idx_col in indexes:
|
469
|
-
|
508
|
+
normalized_idx_col = self._normalize_col(idx_col)
|
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
|
470
519
|
safe_idx_col = _index_col_sql(idx_col)
|
471
|
-
index_defs.append(f"INDEX `idx_{
|
520
|
+
index_defs.append(f"INDEX `idx_{normalized_idx_col}` ({safe_idx_col})")
|
521
|
+
else:
|
522
|
+
logger.warning('索引列不存在于表结构中,跳过', {
|
523
|
+
'库': db_name,
|
524
|
+
'表': table_name,
|
525
|
+
'列': idx_col,
|
526
|
+
'规范化后': normalized_idx_col,
|
527
|
+
'可用列': list(set_typ.keys())
|
528
|
+
})
|
472
529
|
|
473
530
|
# UNIQUE KEY定义
|
474
531
|
unique_defs = []
|
@@ -487,12 +544,48 @@ class MySQLUploader:
|
|
487
544
|
'主键': primary_keys
|
488
545
|
})
|
489
546
|
continue
|
490
|
-
|
491
|
-
|
547
|
+
|
548
|
+
# 验证唯一约束的列是否存在于set_typ中
|
549
|
+
valid_unique_cols = []
|
550
|
+
for col in unique_cols:
|
551
|
+
normalized_col = self._normalize_col(col)
|
552
|
+
if normalized_col in set_typ:
|
553
|
+
valid_unique_cols.append(col)
|
554
|
+
else:
|
555
|
+
logger.warning('唯一约束列不存在于表结构中,跳过', {
|
556
|
+
'库': db_name,
|
557
|
+
'表': table_name,
|
558
|
+
'列': col,
|
559
|
+
'规范化后': normalized_col,
|
560
|
+
'可用列': list(set_typ.keys())
|
561
|
+
})
|
562
|
+
|
563
|
+
if not valid_unique_cols:
|
564
|
+
logger.warning('唯一约束的所有列都不存在于表结构中,跳过整个约束', {
|
565
|
+
'库': db_name,
|
566
|
+
'表': table_name,
|
567
|
+
'原始约束': unique_cols
|
568
|
+
})
|
569
|
+
continue
|
570
|
+
|
571
|
+
safe_unique_cols = [_index_col_sql(col) for col in valid_unique_cols]
|
572
|
+
unique_name = f"uniq_{'_'.join([self._normalize_col(c) for c in valid_unique_cols])}"
|
492
573
|
unique_defs.append(f"UNIQUE KEY `{unique_name}` ({','.join(safe_unique_cols)})")
|
493
574
|
|
494
575
|
index_defs = list(set(index_defs))
|
495
576
|
all_defs = column_defs + [primary_key_sql] + index_defs + unique_defs
|
577
|
+
|
578
|
+
# 添加调试日志
|
579
|
+
logger.debug('建表SQL生成', {
|
580
|
+
'库': db_name,
|
581
|
+
'表': table_name,
|
582
|
+
'列定义': column_defs,
|
583
|
+
'主键': primary_key_sql,
|
584
|
+
'索引': index_defs,
|
585
|
+
'唯一约束': unique_defs,
|
586
|
+
'set_typ键': list(set_typ.keys())
|
587
|
+
})
|
588
|
+
|
496
589
|
sql = f"""
|
497
590
|
CREATE TABLE IF NOT EXISTS `{db_name}`.`{table_name}` (
|
498
591
|
{','.join(all_defs)}
|
@@ -1159,22 +1252,48 @@ class MySQLUploader:
|
|
1159
1252
|
:param table_name: 表名
|
1160
1253
|
:param data: 要上传的数据,支持字典、字典列表或DataFrame格式
|
1161
1254
|
:param set_typ: 列名和数据类型字典 {列名: 数据类型}
|
1162
|
-
:param primary_keys:
|
1255
|
+
:param primary_keys: 主键列列表,可选。格式:['col1', 'col2'] 或 None
|
1163
1256
|
:param check_duplicate: 是否检查重复数据,默认为False
|
1164
|
-
:param duplicate_columns:
|
1257
|
+
:param duplicate_columns: 用于检查重复的列,可选。格式:['col1', 'col2'] 或 None
|
1165
1258
|
:param allow_null: 是否允许空值,默认为False
|
1166
1259
|
:param partition_by: 分表方式('year'、'month'、'None'),可选
|
1167
1260
|
:param partition_date_column: 用于分表的日期列名,默认为'日期', 默认会添加为索引
|
1168
1261
|
:param auto_create: 表不存在时是否自动创建,默认为True
|
1169
|
-
:param indexes:
|
1262
|
+
:param indexes: 需要创建索引的列列表,可选。格式:['col1', 'col2'] 或 None
|
1170
1263
|
:param update_on_duplicate: 遇到重复数据时是否更新旧数据,默认为False
|
1171
1264
|
:param transaction_mode: 事务模式,可选值:
|
1172
1265
|
- 'row' : 逐行提交事务(错误隔离性好)
|
1173
1266
|
- 'batch' : 整批提交事务(性能最优)
|
1174
1267
|
- 'hybrid' : 混合模式(每N行提交,平衡性能与安全性)
|
1175
|
-
:param unique_keys:
|
1268
|
+
:param unique_keys: 唯一约束列表,每个元素为列名列表,支持多列组合唯一约束。格式:[['col1', 'col2'], ['col3']] 或 None
|
1176
1269
|
:raises: 可能抛出各种验证和数据库相关异常
|
1177
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
|
+
|
1178
1297
|
---
|
1179
1298
|
unique_keys、check_duplicate、update_on_duplicate 三者组合下的行为总结:
|
1180
1299
|
|
@@ -1211,23 +1330,28 @@ class MySQLUploader:
|
|
1211
1330
|
'批次': batch_id,
|
1212
1331
|
'传入': len(data) if hasattr(data, '__len__') else 1,
|
1213
1332
|
'参数': {
|
1214
|
-
'主键':
|
1333
|
+
'主键': validated_primary_keys,
|
1215
1334
|
'去重': check_duplicate,
|
1216
1335
|
'去重列': duplicate_columns,
|
1217
1336
|
'允许空值': allow_null,
|
1218
1337
|
'分表方式': partition_by,
|
1219
1338
|
'分表列': partition_date_column,
|
1220
1339
|
# '自动建表': auto_create,
|
1221
|
-
'索引':
|
1340
|
+
'索引': validated_indexes,
|
1222
1341
|
'更新旧数据': update_on_duplicate,
|
1223
1342
|
'事务模式': transaction_mode,
|
1224
|
-
'唯一约束':
|
1343
|
+
'唯一约束': validated_unique_keys
|
1225
1344
|
},
|
1226
1345
|
# '数据样例': self._shorten_for_log(data, 2)
|
1227
1346
|
})
|
1228
1347
|
|
1229
1348
|
try:
|
1230
|
-
#
|
1349
|
+
# 验证参数格式
|
1350
|
+
validated_primary_keys = self._validate_primary_keys_format(primary_keys, db_name, table_name)
|
1351
|
+
validated_indexes = self._validate_indexes_format(indexes, db_name, table_name)
|
1352
|
+
validated_unique_keys = self._validate_unique_keys_format(unique_keys, db_name, table_name)
|
1353
|
+
|
1354
|
+
# 验证分表参数
|
1231
1355
|
if partition_by:
|
1232
1356
|
partition_by = str(partition_by).lower()
|
1233
1357
|
if partition_by not in ['year', 'month']:
|
@@ -1297,10 +1421,10 @@ class MySQLUploader:
|
|
1297
1421
|
try:
|
1298
1422
|
inserted, skipped, failed = self._upload_to_table(
|
1299
1423
|
db_name, part_table, part_data, filtered_set_typ,
|
1300
|
-
|
1424
|
+
validated_primary_keys, check_duplicate, duplicate_columns,
|
1301
1425
|
allow_null, auto_create, partition_date_column,
|
1302
|
-
|
1303
|
-
|
1426
|
+
validated_indexes, batch_id, update_on_duplicate, transaction_mode,
|
1427
|
+
validated_unique_keys
|
1304
1428
|
)
|
1305
1429
|
total_inserted += inserted
|
1306
1430
|
total_skipped += skipped
|
@@ -1324,10 +1448,10 @@ class MySQLUploader:
|
|
1324
1448
|
# 不分表,直接上传
|
1325
1449
|
inserted, skipped, failed = self._upload_to_table(
|
1326
1450
|
db_name, table_name, prepared_data, filtered_set_typ,
|
1327
|
-
|
1451
|
+
validated_primary_keys, check_duplicate, duplicate_columns,
|
1328
1452
|
allow_null, auto_create, partition_date_column,
|
1329
|
-
|
1330
|
-
|
1453
|
+
validated_indexes, batch_id, update_on_duplicate, transaction_mode,
|
1454
|
+
validated_unique_keys
|
1331
1455
|
)
|
1332
1456
|
total_inserted = inserted
|
1333
1457
|
total_skipped = skipped
|
@@ -1363,7 +1487,7 @@ class MySQLUploader:
|
|
1363
1487
|
})
|
1364
1488
|
|
1365
1489
|
# 更新索引
|
1366
|
-
self._update_indexes(db_name, table_name,
|
1490
|
+
self._update_indexes(db_name, table_name, validated_indexes)
|
1367
1491
|
return True
|
1368
1492
|
|
1369
1493
|
@_execute_with_retry
|
@@ -1762,6 +1886,7 @@ class MySQLUploader:
|
|
1762
1886
|
def _update_indexes(self, db_name: str, table_name: str, indexes: Optional[List[str]]):
|
1763
1887
|
"""
|
1764
1888
|
更新索引,避免重复添加或更新,同时注意大小写一致性。
|
1889
|
+
注意:如果列已经在unique_keys中定义,则不会重复创建普通索引。
|
1765
1890
|
|
1766
1891
|
:param db_name: 数据库名
|
1767
1892
|
:param table_name: 表名
|
@@ -1773,7 +1898,7 @@ class MySQLUploader:
|
|
1773
1898
|
# 规范化索引列名
|
1774
1899
|
normalized_indexes = [self._normalize_col(idx) for idx in indexes]
|
1775
1900
|
|
1776
|
-
#
|
1901
|
+
# 获取现有索引(包括普通索引和唯一约束)
|
1777
1902
|
try:
|
1778
1903
|
existing_indexes = self._get_existing_indexes(db_name, table_name)
|
1779
1904
|
except Exception as e:
|
@@ -1782,13 +1907,20 @@ class MySQLUploader:
|
|
1782
1907
|
|
1783
1908
|
# 获取表中现有的列名
|
1784
1909
|
try:
|
1785
|
-
existing_columns = self.
|
1910
|
+
existing_columns = self._get_table_columns(db_name, table_name)
|
1786
1911
|
except Exception as e:
|
1787
1912
|
logger.error('获取现有列时发生错误', {'库': db_name, '表': table_name, '错误': str(e)})
|
1788
1913
|
raise
|
1789
1914
|
|
1790
|
-
#
|
1791
|
-
indexes_to_add = [
|
1915
|
+
# 找出需要添加的索引(排除已存在的索引和不在表中的列)
|
1916
|
+
indexes_to_add = []
|
1917
|
+
for idx in normalized_indexes:
|
1918
|
+
if idx not in existing_indexes and idx in existing_columns:
|
1919
|
+
indexes_to_add.append(idx)
|
1920
|
+
elif idx in existing_indexes:
|
1921
|
+
logger.debug('索引已存在,跳过', {'库': db_name, '表': table_name, '列': idx})
|
1922
|
+
elif idx not in existing_columns:
|
1923
|
+
logger.warning('索引列不存在于表中,跳过', {'库': db_name, '表': table_name, '列': idx})
|
1792
1924
|
|
1793
1925
|
# 添加新索引
|
1794
1926
|
for idx in indexes_to_add:
|
@@ -1800,7 +1932,7 @@ class MySQLUploader:
|
|
1800
1932
|
|
1801
1933
|
def _get_existing_indexes(self, db_name: str, table_name: str) -> Set[str]:
|
1802
1934
|
"""
|
1803
|
-
|
1935
|
+
获取表中现有的索引列名(包括普通索引和唯一约束)。
|
1804
1936
|
|
1805
1937
|
:param db_name: 数据库名
|
1806
1938
|
:param table_name: 表名
|
@@ -1934,6 +2066,222 @@ class MySQLUploader:
|
|
1934
2066
|
conn.rollback()
|
1935
2067
|
raise
|
1936
2068
|
|
2069
|
+
def _validate_unique_keys_format(self, unique_keys: Optional[List[List[str]]], db_name: str = None, table_name: str = None) -> Optional[List[List[str]]]:
|
2070
|
+
"""
|
2071
|
+
验证unique_keys参数的格式是否正确
|
2072
|
+
|
2073
|
+
:param unique_keys: 唯一约束列表
|
2074
|
+
:param db_name: 数据库名,用于日志记录
|
2075
|
+
:param table_name: 表名,用于日志记录
|
2076
|
+
:return: 验证后的unique_keys,如果验证失败则抛出异常
|
2077
|
+
:raises ValueError: 当参数格式不正确时抛出
|
2078
|
+
"""
|
2079
|
+
if unique_keys is None:
|
2080
|
+
return None
|
2081
|
+
|
2082
|
+
if not isinstance(unique_keys, list):
|
2083
|
+
error_msg = f"unique_keys参数必须是列表类型,当前类型: {type(unique_keys).__name__}"
|
2084
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2085
|
+
raise ValueError(error_msg)
|
2086
|
+
|
2087
|
+
# 检查是否为空列表
|
2088
|
+
if len(unique_keys) == 0:
|
2089
|
+
logger.warning('unique_keys为空列表,将忽略此参数', {'库': db_name, '表': table_name})
|
2090
|
+
return None
|
2091
|
+
|
2092
|
+
validated_keys = []
|
2093
|
+
empty_groups_count = 0
|
2094
|
+
|
2095
|
+
for i, key_group in enumerate(unique_keys):
|
2096
|
+
# 检查每个元素是否为列表
|
2097
|
+
if not isinstance(key_group, list):
|
2098
|
+
error_msg = f"unique_keys[{i}]必须是列表类型,当前类型: {type(key_group).__name__},值: {key_group}"
|
2099
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2100
|
+
raise ValueError(error_msg)
|
2101
|
+
|
2102
|
+
# 检查是否为空列表
|
2103
|
+
if len(key_group) == 0:
|
2104
|
+
empty_groups_count += 1
|
2105
|
+
logger.warning(f'unique_keys[{i}]为空列表,跳过', {'库': db_name, '表': table_name})
|
2106
|
+
continue
|
2107
|
+
|
2108
|
+
# 检查每个列名是否为字符串
|
2109
|
+
validated_group = []
|
2110
|
+
for j, col_name in enumerate(key_group):
|
2111
|
+
if not isinstance(col_name, str):
|
2112
|
+
error_msg = f"unique_keys[{i}][{j}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
|
2113
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2114
|
+
raise ValueError(error_msg)
|
2115
|
+
|
2116
|
+
# 检查是否为空字符串或纯空白字符
|
2117
|
+
stripped_name = col_name.strip()
|
2118
|
+
if not stripped_name:
|
2119
|
+
error_msg = f"unique_keys[{i}][{j}]不能为空字符串或纯空白字符,原始值: '{col_name}'"
|
2120
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2121
|
+
raise ValueError(error_msg)
|
2122
|
+
|
2123
|
+
validated_group.append(stripped_name)
|
2124
|
+
|
2125
|
+
# 去重并检查是否有重复列名
|
2126
|
+
if len(validated_group) != len(set(validated_group)):
|
2127
|
+
error_msg = f"unique_keys[{i}]中存在重复列名: {validated_group}"
|
2128
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'unique_keys': unique_keys})
|
2129
|
+
raise ValueError(error_msg)
|
2130
|
+
|
2131
|
+
validated_keys.append(validated_group)
|
2132
|
+
|
2133
|
+
# 检查验证后的结果
|
2134
|
+
if not validated_keys:
|
2135
|
+
if empty_groups_count > 0:
|
2136
|
+
logger.warning(f'unique_keys包含{empty_groups_count}个空列表,验证后为空,将忽略此参数', {
|
2137
|
+
'库': db_name, '表': table_name, '空列表数量': empty_groups_count
|
2138
|
+
})
|
2139
|
+
else:
|
2140
|
+
logger.warning('unique_keys验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
|
2141
|
+
return None
|
2142
|
+
|
2143
|
+
logger.debug('unique_keys格式验证通过', {
|
2144
|
+
'库': db_name,
|
2145
|
+
'表': table_name,
|
2146
|
+
'原始': unique_keys,
|
2147
|
+
'验证后': validated_keys,
|
2148
|
+
'跳过的空列表': empty_groups_count
|
2149
|
+
})
|
2150
|
+
return validated_keys
|
2151
|
+
|
2152
|
+
def _validate_indexes_format(self, indexes: Optional[List[str]], db_name: str = None, table_name: str = None) -> Optional[List[str]]:
|
2153
|
+
"""
|
2154
|
+
验证indexes参数的格式是否正确
|
2155
|
+
|
2156
|
+
:param indexes: 索引列列表
|
2157
|
+
:param db_name: 数据库名,用于日志记录
|
2158
|
+
:param table_name: 表名,用于日志记录
|
2159
|
+
:return: 验证后的indexes,如果验证失败则抛出异常
|
2160
|
+
:raises ValueError: 当参数格式不正确时抛出
|
2161
|
+
"""
|
2162
|
+
if indexes is None:
|
2163
|
+
return None
|
2164
|
+
|
2165
|
+
if not isinstance(indexes, list):
|
2166
|
+
error_msg = f"indexes参数必须是列表类型,当前类型: {type(indexes).__name__}"
|
2167
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'indexes': indexes})
|
2168
|
+
raise ValueError(error_msg)
|
2169
|
+
|
2170
|
+
# 检查是否为空列表
|
2171
|
+
if len(indexes) == 0:
|
2172
|
+
logger.warning('indexes为空列表,将忽略此参数', {'库': db_name, '表': table_name})
|
2173
|
+
return None
|
2174
|
+
|
2175
|
+
validated_indexes = []
|
2176
|
+
empty_strings_count = 0
|
2177
|
+
|
2178
|
+
for i, col_name in enumerate(indexes):
|
2179
|
+
if not isinstance(col_name, str):
|
2180
|
+
error_msg = f"indexes[{i}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
|
2181
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'indexes': indexes})
|
2182
|
+
raise ValueError(error_msg)
|
2183
|
+
|
2184
|
+
# 检查是否为空字符串或纯空白字符
|
2185
|
+
stripped_name = col_name.strip()
|
2186
|
+
if not stripped_name:
|
2187
|
+
empty_strings_count += 1
|
2188
|
+
logger.warning(f'indexes[{i}]为空字符串或纯空白字符,跳过,原始值: "{col_name}"', {
|
2189
|
+
'库': db_name, '表': table_name, 'indexes': indexes
|
2190
|
+
})
|
2191
|
+
continue
|
2192
|
+
|
2193
|
+
validated_indexes.append(stripped_name)
|
2194
|
+
|
2195
|
+
# 去重
|
2196
|
+
validated_indexes = list(dict.fromkeys(validated_indexes))
|
2197
|
+
|
2198
|
+
# 检查验证后的结果
|
2199
|
+
if not validated_indexes:
|
2200
|
+
if empty_strings_count > 0:
|
2201
|
+
logger.warning(f'indexes包含{empty_strings_count}个空字符串,验证后为空,将忽略此参数', {
|
2202
|
+
'库': db_name, '表': table_name, '空字符串数量': empty_strings_count
|
2203
|
+
})
|
2204
|
+
else:
|
2205
|
+
logger.warning('indexes验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
|
2206
|
+
return None
|
2207
|
+
|
2208
|
+
logger.debug('indexes格式验证通过', {
|
2209
|
+
'库': db_name,
|
2210
|
+
'表': table_name,
|
2211
|
+
'原始': indexes,
|
2212
|
+
'验证后': validated_indexes,
|
2213
|
+
'跳过的空字符串': empty_strings_count
|
2214
|
+
})
|
2215
|
+
return validated_indexes
|
2216
|
+
|
2217
|
+
def _validate_primary_keys_format(self, primary_keys: Optional[List[str]], db_name: str = None, table_name: str = None) -> Optional[List[str]]:
|
2218
|
+
"""
|
2219
|
+
验证primary_keys参数的格式是否正确
|
2220
|
+
|
2221
|
+
:param primary_keys: 主键列列表
|
2222
|
+
:param db_name: 数据库名,用于日志记录
|
2223
|
+
:param table_name: 表名,用于日志记录
|
2224
|
+
:return: 验证后的primary_keys,如果验证失败则抛出异常
|
2225
|
+
:raises ValueError: 当参数格式不正确时抛出
|
2226
|
+
"""
|
2227
|
+
if primary_keys is None:
|
2228
|
+
return None
|
2229
|
+
|
2230
|
+
if not isinstance(primary_keys, list):
|
2231
|
+
error_msg = f"primary_keys参数必须是列表类型,当前类型: {type(primary_keys).__name__}"
|
2232
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
|
2233
|
+
raise ValueError(error_msg)
|
2234
|
+
|
2235
|
+
# 检查是否为空列表
|
2236
|
+
if len(primary_keys) == 0:
|
2237
|
+
logger.warning('primary_keys为空列表,将忽略此参数', {'库': db_name, '表': table_name})
|
2238
|
+
return None
|
2239
|
+
|
2240
|
+
validated_keys = []
|
2241
|
+
empty_strings_count = 0
|
2242
|
+
|
2243
|
+
for i, col_name in enumerate(primary_keys):
|
2244
|
+
if not isinstance(col_name, str):
|
2245
|
+
error_msg = f"primary_keys[{i}]必须是字符串类型,当前类型: {type(col_name).__name__},值: {col_name}"
|
2246
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
|
2247
|
+
raise ValueError(error_msg)
|
2248
|
+
|
2249
|
+
# 检查是否为空字符串或纯空白字符
|
2250
|
+
stripped_name = col_name.strip()
|
2251
|
+
if not stripped_name:
|
2252
|
+
empty_strings_count += 1
|
2253
|
+
logger.warning(f'primary_keys[{i}]为空字符串或纯空白字符,跳过,原始值: "{col_name}"', {
|
2254
|
+
'库': db_name, '表': table_name, 'primary_keys': primary_keys
|
2255
|
+
})
|
2256
|
+
continue
|
2257
|
+
|
2258
|
+
validated_keys.append(stripped_name)
|
2259
|
+
|
2260
|
+
# 去重并检查是否有重复列名
|
2261
|
+
if len(validated_keys) != len(set(validated_keys)):
|
2262
|
+
error_msg = f"primary_keys中存在重复列名: {validated_keys}"
|
2263
|
+
logger.error(error_msg, {'库': db_name, '表': table_name, 'primary_keys': primary_keys})
|
2264
|
+
raise ValueError(error_msg)
|
2265
|
+
|
2266
|
+
# 检查验证后的结果
|
2267
|
+
if not validated_keys:
|
2268
|
+
if empty_strings_count > 0:
|
2269
|
+
logger.warning(f'primary_keys包含{empty_strings_count}个空字符串,验证后为空,将忽略此参数', {
|
2270
|
+
'库': db_name, '表': table_name, '空字符串数量': empty_strings_count
|
2271
|
+
})
|
2272
|
+
else:
|
2273
|
+
logger.warning('primary_keys验证后为空,将忽略此参数', {'库': db_name, '表': table_name})
|
2274
|
+
return None
|
2275
|
+
|
2276
|
+
logger.debug('primary_keys格式验证通过', {
|
2277
|
+
'库': db_name,
|
2278
|
+
'表': table_name,
|
2279
|
+
'原始': primary_keys,
|
2280
|
+
'验证后': validated_keys,
|
2281
|
+
'跳过的空字符串': empty_strings_count
|
2282
|
+
})
|
2283
|
+
return validated_keys
|
2284
|
+
|
1937
2285
|
|
1938
2286
|
def main():
|
1939
2287
|
dir_path = os.path.expanduser("~")
|
@@ -1969,7 +2317,120 @@ def main():
|
|
1969
2317
|
{'日期': '2023-02-20', 'name': 'Bob', 'AGE': 25, 'salary': 45000.75},
|
1970
2318
|
]
|
1971
2319
|
|
1972
|
-
#
|
2320
|
+
# 测试参数验证功能
|
2321
|
+
print("=== 测试参数验证功能 ===")
|
2322
|
+
|
2323
|
+
# 正确的格式
|
2324
|
+
print("1. 测试正确的unique_keys格式:")
|
2325
|
+
try:
|
2326
|
+
valid_unique_keys = [['日期', 'name'], ['age']]
|
2327
|
+
result = uploader._validate_unique_keys_format(valid_unique_keys, 'test_db', 'test_table')
|
2328
|
+
print(f" 通过: {result}")
|
2329
|
+
except Exception as e:
|
2330
|
+
print(f" 失败: {e}")
|
2331
|
+
|
2332
|
+
# 错误的格式 - 缺少一层嵌套
|
2333
|
+
print("2. 测试错误的unique_keys格式 (缺少嵌套):")
|
2334
|
+
try:
|
2335
|
+
invalid_unique_keys = ['日期', 'name'] # 错误:应该是 [['日期', 'name']]
|
2336
|
+
result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
|
2337
|
+
print(f" 通过: {result}")
|
2338
|
+
except Exception as e:
|
2339
|
+
print(f" 正确捕获错误: {e}")
|
2340
|
+
|
2341
|
+
# 错误的格式 - 包含非字符串元素
|
2342
|
+
print("3. 测试错误的unique_keys格式 (非字符串元素):")
|
2343
|
+
try:
|
2344
|
+
invalid_unique_keys = [['日期', 123]] # 错误:123不是字符串
|
2345
|
+
result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
|
2346
|
+
print(f" 通过: {result}")
|
2347
|
+
except Exception as e:
|
2348
|
+
print(f" 正确捕获错误: {e}")
|
2349
|
+
|
2350
|
+
# 错误的格式 - 空字符串
|
2351
|
+
print("4. 测试错误的unique_keys格式 (空字符串):")
|
2352
|
+
try:
|
2353
|
+
invalid_unique_keys = [['日期', '']] # 错误:空字符串
|
2354
|
+
result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
|
2355
|
+
print(f" 通过: {result}")
|
2356
|
+
except Exception as e:
|
2357
|
+
print(f" 正确捕获错误: {e}")
|
2358
|
+
|
2359
|
+
# 错误的格式 - 重复列名
|
2360
|
+
print("5. 测试错误的unique_keys格式 (重复列名):")
|
2361
|
+
try:
|
2362
|
+
invalid_unique_keys = [['日期', '日期']] # 错误:重复列名
|
2363
|
+
result = uploader._validate_unique_keys_format(invalid_unique_keys, 'test_db', 'test_table')
|
2364
|
+
print(f" 通过: {result}")
|
2365
|
+
except Exception as e:
|
2366
|
+
print(f" 正确捕获错误: {e}")
|
2367
|
+
|
2368
|
+
# 空值测试 - 空列表
|
2369
|
+
print("6. 测试空值情况 - 空列表:")
|
2370
|
+
try:
|
2371
|
+
empty_list = []
|
2372
|
+
result = uploader._validate_unique_keys_format(empty_list, 'test_db', 'test_table')
|
2373
|
+
print(f" 通过: {result}")
|
2374
|
+
except Exception as e:
|
2375
|
+
print(f" 失败: {e}")
|
2376
|
+
|
2377
|
+
# 空值测试 - 包含空列表
|
2378
|
+
print("7. 测试空值情况 - 包含空列表 [[]]:")
|
2379
|
+
try:
|
2380
|
+
empty_nested = [[]]
|
2381
|
+
result = uploader._validate_unique_keys_format(empty_nested, 'test_db', 'test_table')
|
2382
|
+
print(f" 通过: {result}")
|
2383
|
+
except Exception as e:
|
2384
|
+
print(f" 失败: {e}")
|
2385
|
+
|
2386
|
+
# 空值测试 - 混合空列表和有效列表
|
2387
|
+
print("8. 测试空值情况 - 混合空列表和有效列表 [[], ['col1']]:")
|
2388
|
+
try:
|
2389
|
+
mixed_empty = [[], ['col1']]
|
2390
|
+
result = uploader._validate_unique_keys_format(mixed_empty, 'test_db', 'test_table')
|
2391
|
+
print(f" 通过: {result}")
|
2392
|
+
except Exception as e:
|
2393
|
+
print(f" 失败: {e}")
|
2394
|
+
|
2395
|
+
# 空值测试 - 包含空字符串的列表
|
2396
|
+
print("9. 测试空值情况 - 包含空字符串的列表 [[''], ['col1']]:")
|
2397
|
+
try:
|
2398
|
+
empty_string_list = [[''], ['col1']]
|
2399
|
+
result = uploader._validate_unique_keys_format(empty_string_list, 'test_db', 'test_table')
|
2400
|
+
print(f" 通过: {result}")
|
2401
|
+
except Exception as e:
|
2402
|
+
print(f" 正确捕获错误: {e}")
|
2403
|
+
|
2404
|
+
# 空值测试 - 包含纯空白字符的列表
|
2405
|
+
print("10. 测试空值情况 - 包含纯空白字符的列表 [[' '], ['col1']]:")
|
2406
|
+
try:
|
2407
|
+
whitespace_list = [[' '], ['col1']]
|
2408
|
+
result = uploader._validate_unique_keys_format(whitespace_list, 'test_db', 'test_table')
|
2409
|
+
print(f" 通过: {result}")
|
2410
|
+
except Exception as e:
|
2411
|
+
print(f" 正确捕获错误: {e}")
|
2412
|
+
|
2413
|
+
# 测试indexes的空值处理
|
2414
|
+
print("\n=== 测试indexes空值处理 ===")
|
2415
|
+
print("11. 测试indexes包含空字符串 ['', 'col1']:")
|
2416
|
+
try:
|
2417
|
+
indexes_with_empty = ['', 'col1']
|
2418
|
+
result = uploader._validate_indexes_format(indexes_with_empty, 'test_db', 'test_table')
|
2419
|
+
print(f" 通过: {result}")
|
2420
|
+
except Exception as e:
|
2421
|
+
print(f" 失败: {e}")
|
2422
|
+
|
2423
|
+
# 测试primary_keys的空值处理
|
2424
|
+
print("12. 测试primary_keys包含空字符串 ['', 'col1']:")
|
2425
|
+
try:
|
2426
|
+
primary_keys_with_empty = ['', 'col1']
|
2427
|
+
result = uploader._validate_primary_keys_format(primary_keys_with_empty, 'test_db', 'test_table')
|
2428
|
+
print(f" 通过: {result}")
|
2429
|
+
except Exception as e:
|
2430
|
+
print(f" 失败: {e}")
|
2431
|
+
|
2432
|
+
# 上传数据(使用正确的格式)
|
2433
|
+
print("\n=== 开始上传数据 ===")
|
1973
2434
|
uploader.upload_data(
|
1974
2435
|
db_name='测试库',
|
1975
2436
|
table_name='测试表',
|
@@ -1984,7 +2445,7 @@ def main():
|
|
1984
2445
|
partition_date_column='日期', # 用于分表的日期列名,默认为'日期'
|
1985
2446
|
indexes=[], # 普通索引列
|
1986
2447
|
transaction_mode='row', # 事务模式
|
1987
|
-
unique_keys=[['日期', 'name', 'age']], # 唯一约束列表
|
2448
|
+
unique_keys=[['日期', 'name', 'age']], # 唯一约束列表 - 正确的格式
|
1988
2449
|
)
|
1989
2450
|
|
1990
2451
|
uploader.close()
|
mdbq-4.0.58/mdbq/__version__.py
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
VERSION = '4.0.58'
|
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
|