表与字段信息同步到元信息数据库

这一章先抓住一条落库链路:配置文件描述表字段,系统去数仓补齐真实结构,再转换成内部实体,最后写入 Meta MySQ

ORM、Repository、事务这些细节都服务于这条链路

正式进入第一条真正落地的业务链路:把配置文件里描述的表和字段,转换成系统内部统一的数据对象,并写入 Meta MySQL

async def build(self, config_path: Path):
    context = OmegaConf.load(config_path)
    schema = OmegaConf.structured(MetaConfig)
    meta_config: MetaConfig = OmegaConf.to_object(OmegaConf.merge(schema, context))
    if meta_config.tables:
        # 先把表信息和字段信息写入 Meta MySQL
        column_infos = await self._save_tables_to_meta_db(meta_config)
        # 建立向量索引
        await self._save_column_info_to_qdrant(column_infos)
        # 后根据配置中的 sync 标记,为部分字段建立字段值全文索引
        await self._save_value_info_to_es(meta_config, column_infos)

主要讲 : column_infos = await self._save_tables_to_meta_db(meta_config)

 

 _save_tables_to_meta_db() 到底做了什么:

"""
遍历配置中的每张表,先构造 TableInfo
去 DW 查询这张表所有字段的真实类型
再遍历这张表下的每个字段,查询一部分真实取值作为示例
构造 ColumnInfo
开启事务,把表信息和字段信息统一写入 Meta MySQL

"""

async def _save_tables_to_meta_db(self, meta_config: MetaConfig) -> list[ColumnInfo]:
    table_infos: list[TableInfo] = []
    column_infos: list[ColumnInfo] = []

    for table in meta_config.tables:
        table_info = TableInfo(
            id=table.name,
            name=table.name,
            role=table.role,
            description=table.description,
        )
        table_infos.append(table_info)

        column_types: dict[str, str] = await self.dw_mysql_repository.get_column_types(
            table.name
        )
        for column in table.columns:
            column_values: list = await self.dw_mysql_repository.get_column_values(
                table.name, column.name, 10
            )
            column_info = ColumnInfo(
                id=f"{table.name}.{column.name}",
                name=column.name,
                type=column_types[column.name],
                role=column.role,
                examples=column_values,
                description=column.description,
                alias=column.alias,
                table_id=table.name,
            )
            column_infos.append(column_info)

    async with self.meta_mysql_repository.session.begin():
        await self.meta_mysql_repository.save_table_infos(table_infos)
        await self.meta_mysql_repository.save_column_infos(column_infos)

    return column_infos

1.配置文件转化为业务实体

”配置文件不是直接入库的,配置文件会先被转换成业务实体“

也就是说,这一章不是把 YAML 逐行塞进数据库,而是先把配置中的描述组织成系统内部统一的数据对象。

id=f"{table.name}.{column.name}"  保证同名字段不同表的唯一性。如果只用字段名做唯一标识,不同表中的同名字段很快就会冲突

2. DW Repository

# 查一张表所有字段的真实类型
async def get_column_types(self, table_name: str) -> dict[str, str]:
    sql = f"show columns from {table_name}"
    result = await self.session.execute(text(sql))
    return {row.Field: row.Type for row in result.fetchall()}

# 查某个字段的一部分真实取值
async def get_column_values(self, table_name: str, column_name: str, limit: int):
    sql = f"select distinct {column_name} from {table_name} limit {limit}"
    result = await self.session.execute(text(sql))
    return result.scalars().fetchall()

3. 字段类型为什么是一张表查一次

  column_types = await self.dw_mysql_repository.get_column_types(table.name)。它放在外层表循环里,是因为字段类型的查询粒度是“按表”。 这样后面遍历字段时,就可以直接通过:column_types[column.name]。拿到当前字段的真实类型

4. 字段示例值为什么是一列一查

column_values = await self.dw_mysql_repository.get_column_values(
    table.name, column.name, 10
)

放在内层字段循环里,是因为示例值的查询粒度是“按字段。特别注意一点:这一章拿到的是“示例值”,不是该字段的全部取值。

这一层的目标不是为了直接构建全文索引,而是为了让字段元数据先带上一小部分有代表性的真实样本

5. 为什么service是先处理实体业务类,而不是直接操作ORM

如果 Service 层直接依赖 ORM 模型,会带来一个明显问题:业务逻辑会直接绑定到底层存储实现,一旦 ORM 模型变化,业务层代码也要跟着改。

而现在这样分层之后:

  • Service 层只关心业务对象怎么组织
  • Repository 层才关心底层如何写入数据库

这也是为什么后面同一批 ColumnInfo 还能继续被拿去构建 Qdrant 向量索引、参与 Elasticsearch 取值索引,而不必让业务层到处带着 ORM 对象跑。

6. meta_repository和mapper到底负责什么

现在我们已经有了两类业务实体:TableInfoColumnInfo。接下来要做的,就是把它们写入 Meta MySQL。这部分职责在 MetaMySQLRepository 中。

async def save_table_infos(self, table_infos: list[TableInfo]):
    models = [TableInfoMapper.to_model(table_info) for table_info in table_infos]
    self.session.add_all(models)


async def save_column_infos(self, columns_info: list[ColumnInfo]):
    models = [ColumnInfoMapper.to_model(column_info) for column_info in columns_info]
    self.session.add_all(models)

最关键的点,不是 add_all(),而是:Repository 对外暴露的输入仍然是业务实体,而不是 ORM 模型。

也就是说,Service 层只需要把“业务上已经整理好的数据对象”交给仓储层,至于怎么转换、怎么落库,由仓储层继续往下吸收。

mapper的作用是翻译

class TableInfoMapper:
    @staticmethod
    def to_model(table_info: TableInfo) -> TableInfoMySQL:
        return TableInfoMySQL(**asdict(table_info))


class ColumnInfoMapper:
    @staticmethod
    def to_model(column_info: ColumnInfo) -> ColumnInfoMySQL:
        return ColumnInfoMySQL(**asdict(column_info))

角色分工非常清楚:

  • TableInfoColumnInfo 是业务实体
  • TableInfoMySQLColumnInfoMySQL 是 ORM 模型
  • Mapper 负责把前者转换成后者

add_all() 做的事情,本质上只是:

  • 把这些 ORM 对象加入当前 Session
  • 告诉 ORM:这些对象后续需要被持久化

它本身还不是最终的提交动作。真正的数据落库,要发生在事务提交阶段。

7. 整条链路回顾

  1. 脚本读取 meta_config.yaml
  2. MetaKnowledgeService.build(config_path) 进入表链路
  3. 遍历配置中的每张表,构造 TableInfo
  4. 通过 DWMySQLRepository.get_column_types(...) 查询该表所有字段的真实类型
  5. 遍历该表中的每个字段,再通过 DWMySQLRepository.get_column_values(...) 查询示例值
  6. 构造 ColumnInfo
  7. 打开一笔事务
  8. MetaMySQLRepository 接收业务实体,并通过 Mapper 转成 ORM 模型
  9. session.add_all() 把这些 ORM 对象加入当前 Session
  10. 事务结束后统一提交,数据真正写入 Meta MySQL

如果把这段流程再压缩成一句话,可以记成:配置描述先变成业务实体,业务实体再补齐真实结构与样本,最后以事务方式落到元数据库。

 

posted @ 2026-05-22 16:43  幻影之舞  阅读(2)  评论(0)    收藏  举报