使用pymongo重命名集合字段

发布时间 2023-06-13 09:24:12作者: 仅此而已-远方

背景

根据mongo规范,需要将多个库的多个集合中的所有驼峰字段名称全部改为下划线分割的。如果使用mongo命令重命名集合字段,处理起来比较麻烦:

  • 表结构的形式比较多:比如嵌套子文档、嵌套列表文档等
  • 有些集合的字段不固定
  • 有些集合的字段较多
  • 多个库的多个集合,需要执行多次

所以可以通过写一个python脚本来实现。

 

实现思路

  • 将需要执行重命名的库和集合定义好
  • 遍历每个集合,查询出集合中的所有文档
  • 遍历所有文档,更新文档:
    • 处理嵌套子文档字段
    • 处理嵌套文档列表字段
    • 处理普通字段

 

脚本

新建一个 python 脚本:

#!/usr/bin/python3
#_*_encoding:utf-8_*_
 
"""
    重命名字段:多个库的多个集合中的所有驼峰字段名称全部改为下划线分割的
"""
 
import pymongo
 
# 数据库连接参数
mongoclient = pymongo.MongoClient("mongodb://root:{mongo_password}@{mongo_ip}:{mongo_port}/?authSource=admin&authMechanism=SCRAM-SHA-256&readPreference=primary&directConnection=true&ssl=false")
 
# 要处理的数据库和表前缀
db_names = ["tenant_af00df779294c39ecb41"]
collection_names = [
    "app_ids_detect_config",
    "app_ids_global_detect_config",
    "app_ids_white_rule",
    "app_ids_scan_task"
]
 
# 对单个集合中的字段进行重命名
def rename_collection_fields(db_name, collection_name):
    db = mongoclient[db_name]
    collection = db[collection_name]
 
    # 遍历集合中所有文档,然后一条条进行重命名
    for document in collection.find():
        rename_document_fields(document)
        collection.replace_one({'_id': document['_id']}, document)
 
# 对单个文档中的字段进行重命名
def rename_document_fields(doc):
    for key in list(doc.keys()):
        # 递归处理嵌套子文档
        if isinstance(doc[key], dict):
            rename_document_fields(doc[key])
 
        # 递归处理嵌套列表
        if isinstance(doc[key], list):
            for item_doc in doc[key]:
                # 只用处理对象列表(排除基本类型列表)
                if isinstance(item_doc, dict):
                    rename_document_fields(item_doc)
 
        # 重命名普通字段
        if key != "_id":
            new_key = convert_to_underline(key)
            if new_key != key:
                doc[new_key] = doc.pop(key)
 
# 将驼峰转换为下划线
def convert_to_underline(name):
    # 将字符串中的大写字母前面插入下划线,并全部转换为小写
    return ''.join(['_' + i.lower() if i.isupper() else i for i in name]).lstrip('_')
 
if __name__ == "__main__":
    for db_name in db_names:
        for collection_name in collection_names:
            rename_collection_fields(db_name, collection_name)
            print(f"finish rename: {db_name}.{collection_name}")

说明:

  • 执行脚本前,环境信息:根据具体环境,替换mongo账户名/密码,以及ip地址
    • python3环境
    • 安装pymongo模块:sudo pip3 install pymongo
  • 根据具体需求,替换需要重命名的库和集合名称
  • 该脚本是幂等的,支持重复执行

 

扩展

如果需要将某些库中的所有以 "app_ids_" 为前缀的集合都修改,可以使用如下脚本:

#!/usr/bin/python3
#_*_encoding:utf-8_*_
 
"""
    重命名字段:多个库的多个集合中的所有驼峰字段名称全部改为下划线分割的
"""
 
import pymongo
 
# 数据库连接参数
mongoclient = pymongo.MongoClient("mongodb://root:{mongo_password}@{mongo_ip}:{mongo_port}/?authSource=admin&authMechanism=SCRAM-SHA-256&readPreference=primary&directConnection=true&ssl=false")
 
# 要处理的数据库和表前缀
db_names = ["tenant_af00df779294c39ecb41"]
collection_prefix = "app_ids_"
 
# 对单个集合中的字段进行重命名
def rename_collection_fields(db_name, collection_name):
    db = mongoclient[db_name]
    collection = db[collection_name]
 
    # 遍历集合中所有文档,然后一条条进行重命名
    for document in collection.find():
        rename_document_fields(document)
        collection.replace_one({'_id': document['_id']}, document)
 
# 对单个文档中的字段进行重命名
def rename_document_fields(doc):
    for key in list(doc.keys()):
        # 递归处理嵌套子文档
        if isinstance(doc[key], dict):
            rename_document_fields(doc[key])
 
        # 递归处理嵌套列表
        if isinstance(doc[key], list):
            for item_doc in doc[key]:
                # 只用处理对象列表(排除基本类型列表)
                if isinstance(item_doc, dict):
                    rename_document_fields(item_doc)
 
        # 重命名普通字段
        if key != "_id":
            new_key = convert_to_underline(key)
            if new_key != key:
                doc[new_key] = doc.pop(key)
 
# 将驼峰转换为下划线
def convert_to_underline(name):
    # 将字符串中的大写字母前面插入下划线,并全部转换为小写
    return ''.join(['_' + i.lower() if i.isupper() else i for i in name]).lstrip('_')
 
if __name__ == "__main__":
    for db_name in db_names:
        db = mongoclient[db_name]
        # 获取库中所有以 “app_ids_” 为前缀的集合名称
        collection_names = [name for name in db.list_collection_names() if name.startswith(collection_prefix)]
        for collection_name in collection_names:
            rename_collection_fields(db_name, collection_name)
            print(f"finish rename: {db_name}.{collection_name}")