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.
Files changed (33) hide show
  1. {mdbq-4.0.58 → mdbq-4.0.60}/PKG-INFO +1 -1
  2. mdbq-4.0.60/mdbq/__version__.py +1 -0
  3. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/uploader.py +490 -29
  4. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq.egg-info/PKG-INFO +1 -1
  5. mdbq-4.0.58/mdbq/__version__.py +0 -1
  6. {mdbq-4.0.58 → mdbq-4.0.60}/README.txt +0 -0
  7. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/__init__.py +0 -0
  8. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/log/__init__.py +0 -0
  9. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/log/mylogger.py +0 -0
  10. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/myconf/__init__.py +0 -0
  11. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/myconf/myconf.py +0 -0
  12. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/__init__.py +0 -0
  13. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/deduplicator.py +0 -0
  14. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/mysql.py +0 -0
  15. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/s_query.py +0 -0
  16. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/mysql/unique_.py +0 -0
  17. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/__init__.py +0 -0
  18. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/download_sku_picture.py +0 -0
  19. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/error_handler.py +0 -0
  20. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/otk.py +0 -0
  21. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/pov_city.py +0 -0
  22. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/other/ua_sj.py +0 -0
  23. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/pbix/__init__.py +0 -0
  24. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/pbix/pbix_refresh.py +0 -0
  25. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/pbix/refresh_all.py +0 -0
  26. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/redis/__init__.py +0 -0
  27. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/redis/getredis.py +0 -0
  28. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq/spider/__init__.py +0 -0
  29. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq.egg-info/SOURCES.txt +0 -0
  30. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq.egg-info/dependency_links.txt +0 -0
  31. {mdbq-4.0.58 → mdbq-4.0.60}/mdbq.egg-info/top_level.txt +0 -0
  32. {mdbq-4.0.58 → mdbq-4.0.60}/setup.cfg +0 -0
  33. {mdbq-4.0.58 → mdbq-4.0.60}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.58
3
+ Version: 4.0.60
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -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
- safe_primary_keys = [_index_col_sql(pk) for pk in primary_keys]
457
- primary_key_sql = f"PRIMARY KEY ({','.join(safe_primary_keys)})"
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
- if idx_col in set_typ:
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_{self._normalize_col(idx_col)}` ({safe_idx_col})")
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
- safe_unique_cols = [_index_col_sql(col) for col in unique_cols]
491
- unique_name = f"uniq_{'_'.join([self._normalize_col(c) for c in unique_cols])}"
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
- '主键': primary_keys,
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
- '索引': indexes,
1340
+ '索引': validated_indexes,
1222
1341
  '更新旧数据': update_on_duplicate,
1223
1342
  '事务模式': transaction_mode,
1224
- '唯一约束': unique_keys
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
- primary_keys, check_duplicate, duplicate_columns,
1424
+ validated_primary_keys, check_duplicate, duplicate_columns,
1301
1425
  allow_null, auto_create, partition_date_column,
1302
- indexes, batch_id, update_on_duplicate, transaction_mode,
1303
- unique_keys
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
- primary_keys, check_duplicate, duplicate_columns,
1451
+ validated_primary_keys, check_duplicate, duplicate_columns,
1328
1452
  allow_null, auto_create, partition_date_column,
1329
- indexes, batch_id, update_on_duplicate, transaction_mode,
1330
- unique_keys
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, indexes)
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._get_existing_indexes(db_name, table_name)
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 = [idx for idx in normalized_indexes if idx not in existing_indexes and idx in existing_columns]
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.58
3
+ Version: 4.0.60
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -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