表与字段信息同步到元信息数据库
这一章先抓住一条落库链路:配置文件描述表字段,系统去数仓补齐真实结构,再转换成内部实体,最后写入 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到底负责什么
现在我们已经有了两类业务实体:TableInfo、ColumnInfo。接下来要做的,就是把它们写入 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))
角色分工非常清楚:
TableInfo、ColumnInfo是业务实体TableInfoMySQL、ColumnInfoMySQL是 ORM 模型Mapper负责把前者转换成后者
add_all() 做的事情,本质上只是:
- 把这些 ORM 对象加入当前
Session - 告诉 ORM:这些对象后续需要被持久化
它本身还不是最终的提交动作。真正的数据落库,要发生在事务提交阶段。
7. 整条链路回顾
- 脚本读取
meta_config.yaml MetaKnowledgeService.build(config_path)进入表链路- 遍历配置中的每张表,构造
TableInfo - 通过
DWMySQLRepository.get_column_types(...)查询该表所有字段的真实类型 - 遍历该表中的每个字段,再通过
DWMySQLRepository.get_column_values(...)查询示例值 - 构造
ColumnInfo - 打开一笔事务
MetaMySQLRepository接收业务实体,并通过Mapper转成 ORM 模型session.add_all()把这些 ORM 对象加入当前Session- 事务结束后统一提交,数据真正写入
Meta MySQL
如果把这段流程再压缩成一句话,可以记成:配置描述先变成业务实体,业务实体再补齐真实结构与样本,最后以事务方式落到元数据库。

浙公网安备 33010602011771号