OpenMetadata 词表术语关系类型 CSV 导入导出:设计计划与源码实现全解析
2026/9/14 5:53:31 网站建设 项目流程

OpenMetadata 词表术语关系类型 CSV 导入导出:设计计划与源码实现全解析

【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata

本文围绕 OpenMetadata 仓库中的规划文档 csv-relation-types-plan.md,系统讲解词表(Glossary)术语 CSV 导入/导出增强方案:从旧格式丢失关系类型的数据问题出发,完整呈现relationType:termFQN新格式的设计、解析规则与向后兼容策略,并对照 CsvUtil.java 与 GlossaryRepository.java 的当前源码,还原该方案在导出、导入两侧的实际实现、边界处理与单元测试验证方式。读完后,你将能够编写带关系类型前缀的术语 CSV 文件,理解导入解析器如何区分"关系类型前缀"与"含冒号的 FQN",并掌握其单元测试的组织思路。

一、问题背景:旧版 CSV 格式丢失关系类型

OpenMetadata 的词表术语实体通过relatedTerms字段维护与其他术语的关系,每个关系由TermRelation对象承载,包含目标术语引用(term)和关系类型(relationType)两个部分。在旧版 CSV 导入/导出实现中:

  • 导出侧:只导出关联术语的 FQN,形如Glossary.Term1;Glossary.Term2,关系类型信息被丢弃;
  • 导入侧:对所有解析出的关系硬编码withRelationType("relatedTo")

这导致两类数据损失:

  1. 术语本身带有synonymbroadernarrower或自定义关系类型时,导出的 CSV 无法表达;
  2. 将导出的 CSV 再次导入后,所有关系类型都会退化为relatedTo

而数据库中实际上一直正确存储着关系类型——损失只发生在 CSV 序列化/反序列化环节。因此该增强方案的核心目标是:在不破坏旧格式兼容性的前提下,让 CSV 成为无损往返(round-trip)的载体

二、新 CSV 格式设计:relationType:termFQN

2.1 格式定义

新格式采用relationType:termFQN键值对,多个值之间以分号(;,即FIELD_SEPARATOR)分隔。这与 OpenMetadata CSV 体系中既有的type:value编码惯例保持一致(例如 owner 字段的team:marketing、extension 字段的key:value)。

规划文档 csv-relation-types-plan.md 中给出的三种典型形态:

# 新格式:带关系类型前缀 relatedTerms synonym:Finance.Revenue;broader:Finance.Income;narrower:Finance.Net Revenue # 向后兼容:无前缀时默认 relatedTo relatedTerms Finance.Revenue;Finance.Income # 混合格式(新旧条目共存) relatedTerms synonym:Finance.Revenue;Finance.Income;broader:Finance.Gross Income

2.2 默认关系类型

方案定义了以下内置关系类型:

关系类型说明
relatedTo通用关联术语(默认值)
synonym同义术语
broader更宽泛的上位词
narrower更具体的下位词
antonym反义术语
partOf部分/组件关系
hasPart包含关系

2.3 解析规则

  1. 若条目包含:,且冒号前的前缀是有效的关系类型→ 使用该关系类型,冒号后的部分作为术语 FQN;
  2. 若无:,或前缀不是有效关系类型 → 默认按relatedTo处理,整个字符串视为 FQN;
  3. 有效关系类型的判定依据glossaryTermRelationSettings(关系类型配置)或内置默认值——这一条在最终实现中得到了动态化落地,见下文 4.2 节。

2.4 向后兼容行为矩阵

CSV 格式导入行为
Glossary.Term1;Glossary.Term2全部关系 →relatedTo
synonym:Glossary.Term1;Glossary.Term2前者 →synonym,后者 →relatedTo
synonym:Glossary.Term1;broader:Glossary.Term2两个关系类型均保留

三、导出侧实现:CsvUtil.addTermRelations

3.1 当前源码

该方案的导出增强已在 CsvUtil.java 中落地。方法职责与 Javadoc 描述:

/** * Add term relations to CSV record with relation type prefix. * Format: "relationType:termFQN" for non-default relations, or just "termFQN" for "relatedTo". * Example: "synonym:Glossary.Term1;broader:Glossary.Term2;Glossary.Term3" */ public static List<String> addTermRelations( List<String> csvRecord, List<org.openmetadata.schema.type.TermRelation> termRelations) { csvRecord.add( nullOrEmpty(termRelations) ? null : termRelations.stream() .map( tr -> { String relationType = tr.getRelationType(); String fqn = tr.getTerm().getFullyQualifiedName(); // Include relation type prefix for non-default relations if (relationType != null && !relationType.isEmpty() && !relationType.equals("relatedTo")) { return relationType + ENTITY_TYPE_SEPARATOR + fqn; } return fqn; }) .sorted() .collect(Collectors.joining(FIELD_SEPARATOR))); return csvRecord; }

实现要点(对照规划文档中的"New"版本代码):

  • 省略默认前缀:仅当relationType非空且不等于relatedTo时,才拼接relationType:前缀;默认关系保持裸 FQN 输出,使导出文件与旧格式视觉上兼容;
  • 复用既有分隔符常量:前缀与 FQN 之间使用ENTITY_TYPE_SEPARATOR(值为":"),条目之间使用FIELD_SEPARATOR(值为";"),两者定义于 CsvUtil.java;
  • 排序保证确定性:拼接前对条目做.sorted(),确保同一份数据多次导出结果一致(对比旧实现只排序 FQN,新实现排序的是"前缀 + FQN"整体字符串);
  • 空值处理nullOrEmpty(termRelations)为真时写入null,与工具类中addEntityReferencesaddTagLabels等其他字段方法的行为一致。

3.2 导出调用链

导出入口在 GlossaryRepository.java 的addRecord方法中,relatedTerms位于词表术语 CSV 的第 6 列(下标 5):

CsvUtil.addFieldList(recordList, entity.getSynonyms()); // 列 4:synonyms addTermRelations(recordList, entity.getRelatedTerms()); // 列 5:relatedTerms addField(recordList, termReferencesToRecord(...)); // 列 6:references

从源码结构看,词表术语 CSV 的完整列布局为:0parent、1name、2displayName、3description、4synonyms、5relatedTerms、6references、7tags、8reviewers、9owner、10glossaryStatus、11color、12iconURL、13domains、14extension

3.3 导出侧单元测试

CsvUtilTest.java 覆盖了规划文档"Phase 2: Testing"中的导出用例,且断言比规划更具体:

测试方法验证点
testAddTermRelationsHandlesNullAndEmptyInputsnull与空列表均输出单一null字段
testAddTermRelationsOmitsRelatedToPrefixrelatedTo关系导出为裸 FQN:Glossary.Alpha
testAddTermRelationsTreatsNullRelationTypeAsRelatedTo关系类型为null时按默认处理,不产生前缀
testAddTermRelationsEmitsPrefixForNonDefaultType非默认类型输出synonym:Glossary.Alpha
testAddTermRelationsSortsAndMixesTypes混合类型排序后输出Glossary.Alpha;broader:Glossary.Beta;synonym:Glossary.Zeta

最后一个用例同时验证了"排序作用于带前缀的完整字符串"这一行为,可认为是对规划文档中"New"版导出代码的回归验证。

四、导入侧实现:GlossaryRepository.getTermRelationsFromCsv

4.1 解析主流程

导入侧实现位于 GlossaryRepository.java(GlossaryTerm内部导入器的第 6 列解析,由第 320 行withRelatedTerms(getTermRelationsFromCsv(printer, csvRecord, 5))调用):

/** * Parse term relations from CSV field with support for relation type prefix. * Format: "relationType:termFQN" or just "termFQN" (defaults to "relatedTo"). * Example: "synonym:Glossary.Term1;broader:Glossary.Term2;Glossary.Term3" */ private List<TermRelation> getTermRelationsFromCsv( CSVPrinter printer, CSVRecord csvRecord, int fieldNumber) throws IOException { if (!processRecord) { return null; } String fieldValue = csvRecord.get(fieldNumber); if (nullOrEmpty(fieldValue)) { return null; } List<TermRelation> termRelations = new ArrayList<>(); String[] entries = fieldValue.split(FIELD_SEPARATOR); for (String entry : entries) { String relationType = "relatedTo"; // Default relation type String termFqn = entry.trim(); // Check for relationType:fqn format int colonIndex = entry.indexOf(':'); if (colonIndex > 0) { String prefix = entry.substring(0, colonIndex).trim(); String suffix = entry.substring(colonIndex + 1).trim(); if (isValidRelationType(prefix)) { relationType = prefix; termFqn = suffix; } else if (!prefix.contains(".")) { // Prefix has no dots, so it looks like an intended relation type, not part of an FQN importFailure( printer, invalidField( fieldNumber, String.format( "Invalid relation type '%s' in entry '%s'. " + "Valid types: %s", prefix, entry.trim(), getValidRelationTypeNames())), csvRecord); continue; } // If prefix contains dots, it's likely part of an FQN — treat entire string as FQN } // Resolve the term FQN to an EntityReference EntityReference termRef = getEntityReference(printer, csvRecord, fieldNumber, GLOSSARY_TERM, termFqn); if (termRef != null) { GlossaryTerm resolvedTerm = Entity.getEntity(GLOSSARY_TERM, termRef.getId(), "", Include.NON_DELETED); if (resolvedTerm.getEntityStatus() != null && resolvedTerm.getEntityStatus() != EntityStatus.APPROVED) { importFailure( printer, invalidField( fieldNumber, String.format( "Glossary term '%s' must have APPROVED status. Current: %s", termFqn, resolvedTerm.getEntityStatus())), csvRecord); processRecord = false; continue; } termRelations.add(new TermRelation().withTerm(termRef).withRelationType(relationType)); } } return termRelations.isEmpty() ? null : termRelations; }

逐段对照规划文档的"New"版本代码,实现的要点与增强如下:

(1)冒号定位与三元判定逻辑

解析器对每个条目查找第一个冒号(entry.indexOf(':')),并按前缀特征做三分支判定:

  • colonIndex > 0且前缀命中有效关系类型集合 → 前缀作为关系类型、冒号后作为 FQN;
  • 前缀不在有效集合中,但前缀不含点号→ 判定用户"本意是写关系类型但写错了",通过importFailure记录字段级错误(报错信息中列出全部合法类型名)并跳过该条目;
  • 前缀含点号→ 大概率是 FQN 自身的一部分(例如Database:Schema.Table这类含冒号的 FQN),整个字符串按 FQN 处理,关系类型回落为relatedTo

规划文档中"Edge Cases"第 1 条(FQN 含冒号)与第 2 条(无效关系类型)正是由此覆盖。需要注意实现与规划的一处差异:规划建议"无效前缀时整体按 FQN +relatedTo处理",而实现引入了"前缀无点号则报错"的更严格分支——这使拼写错误的关系类型(如synonm:...)不会静默降级,而是在导入报告中显式暴露。另外,规划中"空关系类型:Glossary.Term默认relatedTo"的场景对应colonIndex == 0不满足> 0条件的路径,整个条目按 FQN 处理,行为与规划一致。

(2)关系类型白名单的动态化

规划文档给出的实现草案使用硬编码集合加占位方法:

private static final Set<String> VALID_RELATION_TYPES = Set.of( "relatedTo", "synonym", "broader", "narrower", "antonym", "partOf", "hasPart" );

而当前源码改为从关系类型 DAO 动态加载(GlossaryRepository.java):

private boolean isValidRelationType(String relationType) { return validRelationTypeNames.contains(relationType); } private String getValidRelationTypeNames() { return validRelationTypeNames.stream().sorted().collect(Collectors.joining(", ")); } private static Set<String> loadValidRelationTypeNames() { RelationshipTypeResolver resolver = new RelationshipTypeResolver(Entity.getCollectionDAO().relationshipTypeDAO()); return resolver.list().stream() .map(RelationshipType::getName) .collect(Collectors.toUnmodifiableSet()); }

白名单由 RelationshipTypeResolver.java 基于relationshipTypeDAO列出全部关系类型(RelationshipType::getName)。这意味着规划文档"Edge Cases"第 4 条——自定义关系类型——在实现中是真实生效的:只要实例的关系类型配置(glossaryTermRelationSettings)中登记了自定义类型,CSV 前缀即可使用该类型,无需修改代码;对应的解析与报错行为由 RelationshipTypeResolverTest.java 所在模块的测试保障。

(3)关联术语状态校验

实现中还包含规划文档未提及的一层校验:每个被关联的术语解析后,会通过Entity.getEntity回查其实体状态,若状态非APPROVED则报字段级错误。这保证了 CSV 导入不会把草稿或已弃用术语建立为关系目标,属于导入质量约束的加强。

五、字段文档更新:glossaryCsvDocumentation.json

规划文档"Phase 1 / 1.3"要求同步更新术语 CSV 的字段文档。当前 glossaryCsvDocumentation.json 中的relatedTerms字段说明:

{ "name": "relatedTerms", "required": false, "description": "List of related glossary terms with optional relation type. Format: `relationType:termFQN` or just `termFQN` (defaults to `relatedTo`). Multiple terms separated by `;`. Valid relation types: `relatedTo` (default), `synonym`, `broader`, `narrower`, `antonym`, `partOf`, `hasPart`.", "examples": [ "`Business terms.Client Identifier;Support.Subscriber Id` - Both default to relatedTo", "`synonym:Business terms.Client Identifier;broader:Support.Subscriber Id` - With explicit relation types", "`synonym:Finance.Revenue;Finance.Income;narrower:Finance.Net Revenue` - Mixed format" ] }

该文档与规划中提出的"格式说明 + 三类示例(纯旧格式 / 显式类型 / 混合格式)"要求一一对应,且示例使用真实术语 FQN,可直接复制到 CSV 文件中验证。

六、边界场景与迁移说明

6.1 边界场景核对(对照规划"Phase 3: Edge Cases")

场景规划预期当前源码实现
FQN 含冒号(如Database:Schema.Term前缀校验失败时整体按 FQN 处理前缀含点号 → 整体按 FQN 处理,默认relatedTo
无效关系类型整体按 FQN +relatedTo前缀无点号 → 显式importFailure报错(更严格);前缀含点号 → 按 FQN 处理
空关系类型(:Glossary.Term默认relatedTocolonIndex == 0不进入前缀分支,整体按 FQN 处理
自定义关系类型依赖glossaryTermRelationSettings查询loadValidRelationTypeNames()RelationshipTypeResolver动态加载,真实生效

6.2 迁移说明(规划"Migration Notes")

  • 无需数据库迁移:数据库早已正确存储关系类型,改动只涉及 CSV 序列化层;
  • 存量 CSV 继续可用:旧格式文件(无前缀)导入后全部映射为relatedTo,行为不变;
  • 新导出:非默认关系自动带类型前缀,导出→再导入的往返过程不再丢失synonymbroadernarrower等语义。

6.3 涉及文件清单(对照规划"Files to Modify")

文件变更当前仓库状态
CsvUtil.javaaddTermRelations()输出关系类型前缀已落地
GlossaryRepository.javagetTermRelationsFromCsv()解析关系类型已落地,且白名单动态化
glossaryCsvDocumentation.json字段说明与示例更新已落地
CsvUtilTest.java导出侧 5 个单元用例已落地
RelationshipTypeResolverTest.java关系类型解析器测试已落地

七、小结

这份"CSV Import/Export Enhancement for Glossary Term Relations"方案的价值在于用最小的格式扩展解决了语义丢失问题:

  1. 通过relationType:termFQN前缀在导出/导入两侧无损保留关系类型
  2. 默认关系省略前缀,与旧格式 CSV 完全向后兼容,存量文件无需迁移;
  3. 解析器以"冒号位置 + 前缀是否含点号"双特征区分关系类型前缀与含冒号 FQN,并在前缀拼写错误时显式报错;
  4. 有效关系类型白名单由RelationshipTypeResolver从关系类型配置动态加载,自定义关系类型无需改码即可参与 CSV 往返
  5. 导出确定性(排序)、空值语义、字段文档与单元/集成测试均按规划逐项落地。

对使用者的直接建议:编写或审查词表术语 CSV 时,relatedTerms列中凡是需要表达同义、上下位、反义等语义的条目都应使用显式前缀,而普通关联可继续书写裸 FQN;导入前若不确定合法类型名,可参考导入错误信息中列出的Valid types清单,或核对该实例的关系类型配置。

【免费下载链接】OpenMetadataThe Open Context Layer for Data and AI , OpenMetadata is the open platform for building trusted data context and business semantics for humans, AI assistants, and agents.项目地址: https://gitcode.com/GitHub_Trending/op/OpenMetadata

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询