Appearance
OpenSPG DSL 解析源码位置详解
目录
1. 概述
OpenSPG使用两种DSL语言,分别用于不同的目的:
1.1 两种DSL
| DSL类型 | 用途 | 文件扩展名 | 解析方式 |
|---|---|---|---|
| Schema DSL | 定义知识图谱的类型结构(EntityType、Relation等) | .schema | Java字符串解析 |
| KGDSL | 图查询和逻辑推理 | .kgdsl | ANTLR4语法生成 |
1.2 解析器架构
┌─────────────────────────────────────────────────────────────┐
│ OpenSPG DSL解析架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ Schema DSL │ │ KGDSL │ │
│ │ (.schema文件) │ │ (.kgdsl文件) │ │
│ └──────────┬───────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ SchemaScript │ │ ANTLR4 Parser │ │
│ │ TranslateUtil.java │ │ (KGDSL.g4) │ │
│ │ │ │ │ │
│ │ 字符串分割+正则 │ │ 词法+语法分析 │ │
│ └──────────┬───────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ SchemaModel │ │ ParseTree │ │
│ │ (中间模型) │ │ (语法树) │ │
│ └──────────┬───────────┘ └──────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ BaseAdvancedType │ │ LogicalPlan │ │
│ │ (Schema对象) │ │ (逻辑计划) │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘2. Schema DSL 解析
2.1 核心解析类
文件位置:
server/biz/schema/src/main/java/com/antgroup/openspg/server/biz/schema/model/SchemaScriptTranslateUtil.java类的职责:
- 将Schema DSL文本解析为
SchemaModel对象 - 支持双向转换:DSL ↔ SchemaModel
- 使用纯Java字符串处理,不依赖ANTLR
2.2 核心方法
2.2.1 DSL → SchemaModel 解析
java
/**
* 将Schema DSL脚本解析为SchemaModel对象
*
* @param script DSL文本内容
* @return SchemaModel对象
*/
public static SchemaModel translateScript(String script) {
SchemaModel schemaModel = new SchemaModel();
// 1. 按行分割DSL文本
List<String> lines = Splitter.on(LINE_SEPARATOR)
.omitEmptyStrings()
.splitToList(script.trim());
if (CollectionUtils.isEmpty(lines)) {
throw new IllegalArgumentException("schema structure error");
}
// 2. 解析第一行的namespace声明
String firstLine = lines.get(0);
String namespace = null;
if (firstLine.contains("namespace")) {
namespace = firstLine.replace("namespace", "").trim();
}
if (StringUtils.isBlank(namespace)) {
throw new IllegalArgumentException("schema miss namespace");
}
schemaModel.setNamespace(namespace);
// 3. 按类型分组解析
List<NodeTypeModel> nodeTypeList = Lists.newArrayList();
List<String> typeList = null;
for (int i = 1; i < lines.size(); i++) {
String line = lines.get(i);
// 跳过空行和注释
if (StringUtils.isBlank(line) || line.startsWith("#")) {
continue;
}
// 计算缩进级别
int indentCount = indentCount(line);
boolean lastLine = i == lines.size() - 1;
if (indentCount == 0) {
// 缩进为0表示新的类型声明
if (CollectionUtils.isNotEmpty(typeList)) {
NodeTypeModel nodeTypeModel = toNodeTypeModel(namespace, typeList);
nodeTypeList.add(nodeTypeModel);
}
typeList = Lists.newArrayList();
typeList.add(line);
} else {
// 累积当前类型的定义行
typeList.add(line);
}
// 处理最后一个类型
if (lastLine && CollectionUtils.isNotEmpty(typeList)) {
NodeTypeModel nodeTypeModel = toNodeTypeModel(namespace, typeList);
nodeTypeList.add(nodeTypeModel);
}
}
schemaModel.setNodeTypeModels(nodeTypeList);
return schemaModel;
}2.2.2 SchemaModel → DSL 转换
java
/**
* 将SchemaModel对象转换为Schema DSL文本
*
* @param schemaModel SchemaModel对象
* @return DSL文本内容
*/
public static String translateSchema(SchemaModel schemaModel) {
StringBuilder sb = new StringBuilder();
// 1. 输出namespace声明
sb.append("namespace ")
.append(schemaModel.getNamespace())
.append(EMPTY_LINE);
if (CollectionUtils.isEmpty(schemaModel.getNodeTypeModels())) {
return sb.toString();
}
// 2. 遍历所有类型
for (NodeTypeModel nodeTypeModel : schemaModel.getNodeTypeModels()) {
AdvancedTypeEnum advancedTypeEnum = AdvancedTypeEnum.valueOf(nodeTypeModel.getType());
// 输出类型声明: Person(人物): EntityType
sb.append(nodeTypeModel.getName())
.append("(")
.append(nodeTypeModel.getNameZh())
.append("): ")
.append(advancedTypeEnum.getCode())
.append(LINE_SEPARATOR);
// 输出描述
if (StringUtils.isNotBlank(nodeTypeModel.getDesc())) {
setDeep(1, sb);
sb.append("desc:")
.append(nodeTypeModel.getDesc())
.append(LINE_SEPARATOR);
}
// 输出属性
translateProperties(schemaModel.getNamespace(), nodeTypeModel, sb);
// 输出关系
translateRelations(schemaModel.getNamespace(), nodeTypeModel, sb);
sb.append(LINE_SEPARATOR);
}
return sb.toString();
}2.3 缩进识别机制
Schema DSL解析的核心是基于缩进层级的结构识别:
java
/**
* 计算行的缩进级别
*
* @param input 输入行
* @return 缩进空格数
*/
private static int indentCount(String input) {
int count = 0;
for (char c : input.toCharArray()) {
if (c == '\t') {
count = count + 4; // Tab = 4个空格
} else if (c == ' ') {
count++;
} else {
break; // 遇到非空白字符停止
}
}
return count;
}缩进层级规则:
| 缩进级别 | 含义 | 示例 |
|---|---|---|
| 0 | 类型声明 | Person(人物): EntityType |
| 1 | 关键字(properties、relations、desc) | properties: |
| 2 | 属性/关系定义 | age(年龄): Integer |
| 3 | 属性配置 | constraint: NotNull |
| 4+ | 嵌套规则内容 | Define (s:Person)... |
完整示例:
namespace DEFAULT ← 缩进0: namespace声明
Person(人物): EntityType ← 缩进0: 类型声明
desc: 自然人实体 ← 缩进1: 描述
properties: ← 缩进1: properties关键字
age(年龄): Integer ← 缩进2: 属性定义
constraint: RangeConstraint(0, 150) ← 缩进3: 约束配置
index: Range ← 缩进3: 索引配置
name(姓名): Text ← 缩进2: 属性定义
index: Text ← 缩进3: 索引配置
constraint: NotNull ← 缩进3: 约束配置
relations: ← 缩进1: relations关键字
worksAt(工作于): Company ← 缩进2: 关系定义
properties: ← 缩进3: 关系属性
position(职位): Text ← 缩进4: 关系的属性定义2.4 类型信息提取
java
/**
* 从类型声明行提取类型信息
* 输入格式: Person(自然人): EntityType
*
* @param s 类型声明行
* @return (name, nameZh, type) 三元组
*/
private static MutableTriple<String, String, String> getType(String s) {
// 使用分隔符 '(', ')', ':' 分割字符串
List<String> list = Splitter.on(CharMatcher.anyOf("():"))
.omitEmptyStrings()
.trimResults()
.splitToList(s);
// list[0] = "Person"
// list[1] = "自然人"
// list[2] = "EntityType"
return MutableTriple.of(
list.get(0), // name (英文名)
list.get(1), // nameZh (中文名)
list.get(2) // type (类型类别)
);
}2.5 约束解析
java
/**
* 解析约束表达式
* 示例: "NotNull,Unique" 或 "Enum=\"男,女,未知\""
*/
private static void parseConstraintForProperty(
String expression, List<BaseConstraintItem> constraintItems) {
// 1. 解析带值的约束 (Enum, Regular)
Pattern pattern = Pattern.compile(
"(Enum|Regular)\\s*?=\\s*?\"([^\"]+)\"",
Pattern.CASE_INSENSITIVE
);
Matcher matcher = pattern.matcher(expression);
while (matcher.find()) {
String constraintType = matcher.group(1).toLowerCase();
String constraintValue = matcher.group(2).trim();
if ("enum".equals(constraintType)) {
// 解析枚举值
String[] enumValues = constraintValue.split(",");
List<String> values = Arrays.stream(enumValues)
.map(String::trim)
.collect(Collectors.toList());
constraintItems.add(new EnumConstraint(values));
} else if ("regular".equals(constraintType)) {
// 解析正则表达式
constraintItems.add(new RegularConstraint(constraintValue));
}
// 从表达式中移除已解析的部分
expression = expression.replaceFirst(matcher.group(), "");
}
// 2. 解析标志型约束 (MultiValue, NotNull, Unique)
String[] array = expression.split(",");
for (String cons : array) {
cons = cons.trim();
if ("multivalue".equalsIgnoreCase(cons)) {
constraintItems.add(new MultiValConstraint());
} else if ("notnull".equalsIgnoreCase(cons)) {
constraintItems.add(new NotNullConstraint());
} else if ("unique".equalsIgnoreCase(cons)) {
constraintItems.add(new UniqueConstraint());
}
}
}2.6 解析示例
输入 DSL:
schema
namespace DEFAULT
Person(人物): EntityType
desc: 自然人实体
properties:
name(姓名): Text
index: Text
constraint: NotNull
age(年龄): Integer
constraint: RangeConstraint(0, 150)
gender(性别): Text
constraint: Enum="男,女,未知"
relations:
worksAt(工作于): Company
Company(公司): EntityType
properties:
name(公司名称): Text
index: Text解析流程:
java
// 1. 解析namespace
String namespace = "DEFAULT";
// 2. 解析Person类型
NodeTypeModel personType = new NodeTypeModel();
personType.setName("Person");
personType.setNameZh("人物");
personType.setType("ENTITY_TYPE");
personType.setDesc("自然人实体");
// 3. 解析Person的属性
PropertyModel nameProperty = new PropertyModel();
nameProperty.setName("name");
nameProperty.setNameZh("姓名");
nameProperty.setType("Text");
nameProperty.setIndex("TEXT");
nameProperty.setConstraint(new NotNullConstraint());
PropertyModel ageProperty = new PropertyModel();
ageProperty.setName("age");
ageProperty.setNameZh("年龄");
ageProperty.setType("Integer");
ageProperty.setConstraint(new RangeConstraint(0, 150));
PropertyModel genderProperty = new PropertyModel();
genderProperty.setName("gender");
genderProperty.setNameZh("性别");
genderProperty.setType("Text");
genderProperty.setConstraint(new EnumConstraint(Arrays.asList("男", "女", "未知")));
// 4. 解析Person的关系
EdgeTypeModel worksAtRelation = new EdgeTypeModel();
worksAtRelation.setName("worksAt");
worksAtRelation.setNameZh("工作于");
worksAtRelation.setTargetTypeName("Company");
// 5. 组装SchemaModel
SchemaModel schemaModel = new SchemaModel();
schemaModel.setNamespace(namespace);
schemaModel.setNodeTypeModels(Arrays.asList(personType, companyType));输出 SchemaModel 结构:
json
{
"namespace": "DEFAULT",
"nodeTypeModels": [
{
"name": "Person",
"nameZh": "人物",
"type": "ENTITY_TYPE",
"desc": "自然人实体",
"properties": [
{
"name": "name",
"nameZh": "姓名",
"type": "Text",
"index": "TEXT",
"constraint": {"type": "NOT_NULL"}
},
{
"name": "age",
"nameZh": "年龄",
"type": "Integer",
"constraint": {"type": "RANGE", "min": 0, "max": 150}
},
{
"name": "gender",
"nameZh": "性别",
"type": "Text",
"constraint": {"type": "ENUM", "values": ["男", "女", "未知"]}
}
],
"relations": [
{
"name": "worksAt",
"nameZh": "工作于",
"targetTypeName": "Company"
}
]
},
{
"name": "Company",
"nameZh": "公司",
"type": "ENTITY_TYPE",
"properties": [
{
"name": "name",
"nameZh": "公司名称",
"type": "Text",
"index": "TEXT"
}
]
}
]
}3. KGDSL 解析
3.1 ANTLR4语法文件
文件位置:
reasoner/kgdsl-parser/src/main/antlr4/com/antgroup/openspg/reasoner/KGDSL.g4ANTLR4介绍:
- ANTLR (ANother Tool for Language Recognition) 是一个强大的语法生成器
- 通过定义语法规则(.g4文件),自动生成词法分析器和语法分析器
- 生成的Java代码位于
target/generated-sources/antlr4/
3.2 KGDSL语法定义
3.2.1 根结构
antlr4
grammar KGDSL;
// 定义根结构
script: (
base_rule_define // 计算模式
| base_predicated_define // Define模式
| kgdsl_old_define // KGDSL 1.0兼容模式
)*;3.2.2 Define模式
antlr4
// Define (s:Person)-[p:worksAt]->(o:Company) { ... }
the_define_structure_symbol: DEFINE;
the_define_structure
: the_define_structure_symbol predicated_define '{' base_rule_define '}'
;
predicated_define
: node_pattern full_edge_pointing_right node_pattern
;示例:
kgdsl
Define (s:Person)-[p:worksAt]->(o:Company) {
GraphStructure {
(s)-[p1:manages]->(team:Team)
}
Rule {
o.name == "阿里巴巴"
}
}3.2.3 GraphStructure定义
antlr4
// GraphStructure { ... }
the_graph_structure
: graph_structure_head '{' graph_structure_define? '}'
;
graph_structure_head
: GRAPH_STRUCTURE
| STRUCTURE
;
graph_structure_define
: graph_structure_body
| path_pattern_list
;
graph_structure_body
: graph_alias_define
| path_pattern
| node_pattern
;示例:
kgdsl
GraphStructure {
path1: (s:Person)-[p1:parent]->(child:Person)
path2: (s)-[p2:worksAt]->(company:Company)
}3.2.4 Rule定义
antlr4
// Rule { ... }
the_rule
: rule_head '{' rule_body? '}'
;
rule_head
: RULE
| CONSTRAINT
;
rule_body
: rule_define+
;
rule_define
: rule_define_statement
| rule_assign_statement
;示例:
kgdsl
Rule {
// 约束条件
s.age >= 25
s.age <= 60
o.foundYear >= "1990"
// 赋值语句
result = (s, o)
}3.2.5 节点和边模式
antlr4
// 节点模式: (s:Person)
node_pattern
: '(' variable_name? (':' label_name)? ')'
;
// 边模式: -[p:worksAt]->
full_edge_pointing_right
: '-' '[' variable_name? (':' label_name)? ']' '->'
;
// 路径模式: (s)-[p]->(o)
path_pattern
: node_pattern full_edge_pointing_right node_pattern
;3.3 生成的解析器类
ANTLR4根据 KGDSL.g4 自动生成以下Java类:
生成位置:
reasoner/kgdsl-parser/target/generated-sources/antlr4/com/antgroup/openspg/reasoner/
├── KGDSLLexer.java ← 词法分析器(将字符流转换为Token流)
├── KGDSLParser.java ← 语法分析器(将Token流转换为ParseTree)
├── KGDSLBaseVisitor.java ← 访问器基类(遍历ParseTree)
└── KGDSLListener.java ← 监听器接口(事件驱动)3.3.1 词法分析器 (Lexer)
java
/**
* ANTLR自动生成的词法分析器
* 将输入字符串分解为Token流
*/
public class KGDSLLexer extends Lexer {
// Token类型定义
public static final int
DEFINE=1, GRAPH_STRUCTURE=2, STRUCTURE=3, RULE=4, CONSTRAINT=5,
LPAREN=6, RPAREN=7, LBRACE=8, RBRACE=9, LBRACKET=10, RBRACKET=11,
ARROW=12, COLON=13, COMMA=14, DOT=15, SEMICOLON=16,
EQ=17, NEQ=18, LT=19, GT=20, LE=21, GE=22,
AND=23, OR=24, NOT=25,
IDENTIFIER=26, NUMBER=27, STRING=28, WS=29, COMMENT=30;
// ... 自动生成的词法规则实现
}使用示例:
java
// 创建字符流
CharStream input = CharStreams.fromString("Define (s:Person)-[p:worksAt]->(o:Company)");
// 创建词法分析器
KGDSLLexer lexer = new KGDSLLexer(input);
// 获取Token流
CommonTokenStream tokens = new CommonTokenStream(lexer);
tokens.fill();
// 打印所有Token
for (Token token : tokens.getTokens()) {
System.out.println(token.getType() + ": " + token.getText());
}3.3.2 语法分析器 (Parser)
java
/**
* ANTLR自动生成的语法分析器
* 将Token流解析为语法树
*/
public class KGDSLParser extends Parser {
/**
* 解析根结构
*/
public static class ScriptContext extends ParserRuleContext {
public List<Base_rule_defineContext> base_rule_define() { ... }
public List<Base_predicated_defineContext> base_predicated_define() { ... }
}
/**
* 解析Define结构
*/
public static class The_define_structureContext extends ParserRuleContext {
public The_define_structure_symbolContext the_define_structure_symbol() { ... }
public Predicated_defineContext predicated_define() { ... }
public Base_rule_defineContext base_rule_define() { ... }
}
/**
* 解析节点模式: (s:Person)
*/
public static class Node_patternContext extends ParserRuleContext {
public Variable_nameContext variable_name() { ... }
public Label_nameContext label_name() { ... }
}
// ... 更多自动生成的解析方法
}使用示例:
java
// 创建词法分析器和语法分析器
CharStream input = CharStreams.fromString(kgdslQuery);
KGDSLLexer lexer = new KGDSLLexer(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
KGDSLParser parser = new KGDSLParser(tokens);
// 解析根结构
KGDSLParser.ScriptContext tree = parser.script();
// 遍历语法树
System.out.println(tree.toStringTree(parser));3.3.3 访问器 (Visitor)
java
/**
* 访问器基类
* 用于遍历ParseTree并提取信息
*/
public class KGDSLBaseVisitor<T> extends AbstractParseTreeVisitor<T>
implements KGDSLVisitor<T> {
@Override
public T visitScript(KGDSLParser.ScriptContext ctx) {
return visitChildren(ctx);
}
@Override
public T visitThe_define_structure(KGDSLParser.The_define_structureContext ctx) {
return visitChildren(ctx);
}
@Override
public T visitNode_pattern(KGDSLParser.Node_patternContext ctx) {
return visitChildren(ctx);
}
// ... 更多visit方法
}自定义Visitor示例:
java
/**
* 自定义Visitor,提取Define语句中的节点和边信息
*/
public class KGDSLInfoExtractor extends KGDSLBaseVisitor<Void> {
private List<String> nodes = new ArrayList<>();
private List<String> edges = new ArrayList<>();
@Override
public Void visitNode_pattern(KGDSLParser.Node_patternContext ctx) {
// 提取节点信息
String variable = ctx.variable_name() != null ?
ctx.variable_name().getText() : "";
String label = ctx.label_name() != null ?
ctx.label_name().getText() : "";
nodes.add(variable + ":" + label);
return null;
}
@Override
public Void visitFull_edge_pointing_right(
KGDSLParser.Full_edge_pointing_rightContext ctx) {
// 提取边信息
String variable = ctx.variable_name() != null ?
ctx.variable_name().getText() : "";
String label = ctx.label_name() != null ?
ctx.label_name().getText() : "";
edges.add(variable + ":" + label);
return null;
}
public List<String> getNodes() { return nodes; }
public List<String> getEdges() { return edges; }
}
// 使用
KGDSLInfoExtractor extractor = new KGDSLInfoExtractor();
extractor.visit(parseTree);
System.out.println("Nodes: " + extractor.getNodes());
System.out.println("Edges: " + extractor.getEdges());3.4 KGDSL完整示例
示例1:简单查询
KGDSL查询:
kgdsl
Define (s:Person)-[p:worksAt]->(o:Company)
Constraint {
o.name == "阿里巴巴"
s.age >= 25
}
Rule: result = (s, o)解析过程:
词法分析:生成Token流
DEFINE, LPAREN, IDENTIFIER(s), COLON, IDENTIFIER(Person), RPAREN, DASH, LBRACKET, IDENTIFIER(p), COLON, IDENTIFIER(worksAt), RBRACKET, ARROW, LPAREN, IDENTIFIER(o), COLON, IDENTIFIER(Company), RPAREN语法分析:构建ParseTree
script └── base_predicated_define └── the_define_structure ├── DEFINE ├── predicated_define │ ├── node_pattern (s:Person) │ ├── full_edge_pointing_right (-[p:worksAt]->) │ └── node_pattern (o:Company) └── base_rule_define ├── the_rule │ └── rule_body │ ├── o.name == "阿里巴巴" │ └── s.age >= 25 └── result = (s, o)
示例2:复杂图结构查询
KGDSL查询:
kgdsl
Define (s:Person)-[p:closeRel]->(o:Person) {
GraphStructure {
path1: (s)-[p1:parent]->(o)
path2: (s)-[p2:children]->(o)
path3: (s)-[p3:sibling]->(o)
}
Rule {
// 近亲定义:父母、子女或兄弟姐妹关系
R1("近亲关系"): path1 | path2 | path3
}
}解析后的逻辑计划:
scala
// 逻辑计划
val logicalPlan =
Project(fields = List(Var("s"), Var("o")),
Filter(expr = Or(
PatternMatch(pattern = path1),
PatternMatch(pattern = path2),
PatternMatch(pattern = path3)
),
PatternScan(
pattern = GraphPattern(
nodes = List(Node("s", "Person"), Node("o", "Person")),
edges = List(
Edge("s", "p1", "o", "parent"),
Edge("s", "p2", "o", "children"),
Edge("s", "p3", "o", "sibling")
)
)
)
)
)4. 解析器对比
4.1 技术对比
| 特性 | Schema DSL | KGDSL |
|---|---|---|
| 解析技术 | 纯Java字符串处理 | ANTLR4语法生成 |
| 复杂度 | 简单(基于缩进) | 复杂(完整语法树) |
| 可扩展性 | 有限 | 高(修改.g4即可) |
| 性能 | 快(无额外开销) | 中等(解析开销) |
| 错误提示 | 简单 | 详细(行列定位) |
| IDE支持 | 无 | 有(ANTLR插件) |
4.2 使用场景对比
| 场景 | Schema DSL | KGDSL |
|---|---|---|
| 类型定义 | ✅ 主要用途 | ❌ 不适用 |
| 图查询 | ❌ 不支持 | ✅ 主要用途 |
| 逻辑推理 | ❌ 不支持 | ✅ 支持 |
| 约束定义 | ✅ 支持 | ✅ 支持 |
| 规则表达 | ⚠️ 有限支持 | ✅ 完整支持 |
4.3 代码量对比
Schema DSL解析器:
SchemaScriptTranslateUtil.java: 648行
相关辅助类: ~200行
总计: ~850行
KGDSL解析器:
KGDSL.g4: ~1500行 (语法定义)
生成的Java代码: ~10000行
自定义Visitor: ~500行
总计: ~12000行4.4 语法示例对比
Schema DSL示例
schema
namespace DEFAULT
Person(人物): EntityType
properties:
name(姓名): Text
index: Text
age(年龄): Integer
relations:
worksAt(工作于): Company特点:
- 简洁清晰
- 基于缩进
- 类似YAML格式
- 适合类型定义
KGDSL示例
kgdsl
Define (s:Person)-[p:worksAt]->(o:Company) {
GraphStructure {
(s)-[manages]->(team:Team)
}
Rule {
o.name == "阿里巴巴"
s.age >= 25
}
}特点:
- 图模式匹配
- 支持复杂表达式
- 类似Cypher/SQL
- 适合查询和推理
5. 完整解析流程
5.1 Schema DSL完整流程
┌─────────────────────────────────────────────────────────────┐
│ Schema DSL 完整解析流程 │
└─────────────────────────────────────────────────────────────┘
第1步:读取DSL文件
↓
┌─────────────────┐
│ .schema文件 │
│ 或DSL字符串 │
└────────┬────────┘
│
第2步:DSL文本解析 (SchemaScriptTranslateUtil.translateScript)
↓
┌─────────────────┐
│ 按行分割 │ lines = script.split("\n")
│ 识别namespace │ namespace = "DEFAULT"
│ 计算缩进层级 │ indentCount(line)
│ 提取类型信息 │ getType(line)
│ 解析属性/关系 │ getPropertyModelList()
│ 解析约束 │ parseConstraintForProperty()
└────────┬────────┘
│
第3步:构建中间模型
↓
┌─────────────────┐
│ SchemaModel │
│ { │
│ namespace │
│ nodeTypes[] │
│ ├─ name │
│ ├─ type │
│ ├─ props[] │
│ └─ rels[] │
│ } │
└────────┬────────┘
│
第4步:转换为Schema对象 (SchemaModelConvertor.convert)
↓
┌─────────────────┐
│ BaseAdvancedType│
│ ├─ EntityType │
│ ├─ ConceptType │
│ ├─ EventType │
│ └─ ... │
└────────┬────────┘
│
第5步:Schema处理管道 (SchemaAlterPipeline)
↓
┌─────────────────┐
│ PreProcessStage │ → 验证、继承、内置属性
│ ProcessStage │ → 持久化到MySQL
│ PostProcessStage│ → 同步到Neo4j/TuGraph
└────────┬────────┘
│
第6步:存储
↓
┌─────────────────┐
│ MySQL数据库 │ kg_ontology_entity
│ (Schema元数据) │ kg_ontology_entity_property_range
└─────────────────┘
↓
┌─────────────────┐
│ Neo4j/TuGraph │ CREATE CONSTRAINT
│ (图结构同步) │ CREATE INDEX
└─────────────────┘5.2 KGDSL完整流程
┌─────────────────────────────────────────────────────────────┐
│ KGDSL 完整解析流程 │
└─────────────────────────────────────────────────────────────┘
第1步:KGDSL查询字符串
↓
┌─────────────────┐
│ KGDSL查询 │
│ Define (s:P)-> │
│ (o:C) {...} │
└────────┬────────┘
│
第2步:词法分析 (KGDSLLexer)
↓
┌─────────────────┐
│ Token流 │
│ ├─ DEFINE │
│ ├─ LPAREN │
│ ├─ IDENTIFIER │
│ ├─ COLON │
│ └─ ... │
└────────┬────────┘
│
第3步:语法分析 (KGDSLParser)
↓
┌─────────────────┐
│ ParseTree │
│ script │
│ └─define │
│ ├─pattern │
│ ├─graph │
│ └─rule │
└────────┬────────┘
│
第4步:语法树遍历 (Visitor/Listener)
↓
┌─────────────────┐
│ 提取查询信息 │
│ ├─ 节点列表 │
│ ├─ 边列表 │
│ ├─ 约束条件 │
│ └─ 规则表达式 │
└────────┬────────┘
│
第5步:构建逻辑计划 (LogicalPlanner)
↓
┌─────────────────┐
│ LogicalPlan │
│ ├─ PatternScan │
│ ├─ Filter │
│ ├─ Project │
│ └─ Aggregate │
└────────┬────────┘
│
第6步:优化 (Optimizer)
↓
┌─────────────────┐
│ 优化后的计划 │
│ ├─ 谓词下推 │
│ ├─ 连接重排序 │
│ └─ 索引选择 │
└────────┬────────┘
│
第7步:生成物理计划 (PhysicalPlanner)
↓
┌─────────────────┐
│ PhysicalPlan │
│ ├─ Unfold │
│ ├─ Join │
│ └─ Aggregate │
└────────┬────────┘
│
第8步:执行查询
↓
┌─────────────────┐
│ 查询Neo4j/ │
│ TuGraph │
└────────┬────────┘
│
第9步:返回结果
↓
┌─────────────────┐
│ 查询结果 │
│ ├─ nodes[] │
│ ├─ edges[] │
│ └─ paths[] │
└─────────────────┘6. 使用示例
6.1 Schema DSL使用示例
示例1:定义基本Schema
java
package com.example.openspg;
import com.antgroup.openspg.server.biz.schema.model.*;
import com.antgroup.openspg.server.biz.schema.SchemaManager;
public class SchemaDSLExample {
public static void main(String[] args) {
// ========== 1. 定义Schema DSL ==========
String schemaDSL = """
namespace DEFAULT
Person(人物): EntityType
desc: 自然人实体
properties:
name(姓名): Text
index: Text
constraint: NotNull
age(年龄): Integer
constraint: RangeConstraint(0, 150)
relations:
worksAt(工作于): Company
Company(公司): EntityType
properties:
name(公司名称): Text
index: Text
constraint: NotNull
foundYear(成立年份): Text
""";
// ========== 2. 解析DSL ==========
SchemaModel schemaModel = SchemaScriptTranslateUtil.translateScript(schemaDSL);
System.out.println("Namespace: " + schemaModel.getNamespace());
System.out.println("类型数量: " + schemaModel.getNodeTypeModels().size());
// ========== 3. 打印解析结果 ==========
for (NodeTypeModel nodeType : schemaModel.getNodeTypeModels()) {
System.out.println("\n类型: " + nodeType.getName() +
" (" + nodeType.getNameZh() + ")");
System.out.println(" 类别: " + nodeType.getType());
System.out.println(" 描述: " + nodeType.getDesc());
// 打印属性
if (nodeType.getProperties() != null) {
System.out.println(" 属性:");
for (PropertyModel prop : nodeType.getProperties()) {
System.out.println(" - " + prop.getName() +
" (" + prop.getNameZh() + "): " +
prop.getType());
if (prop.getIndex() != null) {
System.out.println(" 索引: " + prop.getIndex());
}
if (prop.getConstraint() != null) {
System.out.println(" 约束: " + prop.getConstraint());
}
}
}
// 打印关系
if (nodeType.getRelations() != null) {
System.out.println(" 关系:");
for (EdgeTypeModel edge : nodeType.getRelations()) {
System.out.println(" - " + edge.getName() +
" (" + edge.getNameZh() + ") -> " +
edge.getTargetTypeName());
}
}
}
// ========== 4. 反向转换:SchemaModel → DSL ==========
String generatedDSL = SchemaScriptTranslateUtil.translateSchema(schemaModel);
System.out.println("\n生成的DSL:\n" + generatedDSL);
}
}输出:
Namespace: DEFAULT
类型数量: 2
类型: Person (人物)
类别: ENTITY_TYPE
描述: 自然人实体
属性:
- name (姓名): Text
索引: TEXT
约束: NOT_NULL
- age (年龄): Integer
约束: RANGE(0, 150)
关系:
- worksAt (工作于) -> Company
类型: Company (公司)
类别: ENTITY_TYPE
描述: null
属性:
- name (公司名称): Text
索引: TEXT
约束: NOT_NULL
- foundYear (成立年份): Text
生成的DSL:
namespace DEFAULT
Person(人物): EntityType
desc: 自然人实体
properties:
name(姓名): Text
index: Text
constraint: NotNull
age(年龄): Integer
constraint: RangeConstraint(0, 150)
relations:
worksAt(工作于): Company
Company(公司): EntityType
properties:
name(公司名称): Text
index: Text
constraint: NotNull
foundYear(成立年份): Text示例2:从文件读取Schema DSL
java
import java.nio.file.Files;
import java.nio.file.Paths;
public class SchemaDSLFileExample {
public static void main(String[] args) throws Exception {
// ========== 1. 从文件读取DSL ==========
String dslContent = Files.readString(
Paths.get("/path/to/schema/enterprise.schema")
);
// ========== 2. 解析DSL ==========
SchemaModel schemaModel = SchemaScriptTranslateUtil.translateScript(dslContent);
// ========== 3. 转换为Schema对象 ==========
List<BaseAdvancedType> spgTypes = schemaModel.getNodeTypeModels().stream()
.map(SchemaModelConvertor::convert2NodeTypeModel)
.collect(Collectors.toList());
// ========== 4. 提交到OpenSPG ==========
SchemaManager schemaManager = applicationContext.getBean(SchemaManager.class);
SchemaDraft draft = new SchemaDraft();
draft.setAlterSpgTypes(spgTypes);
SchemaAlterRequest request = new SchemaAlterRequest();
request.setProjectId(1L);
request.setSchemaDraft(draft);
schemaManager.alterSchema(request);
System.out.println("Schema创建成功!");
}
}6.2 KGDSL使用示例
示例1:简单查询
java
package com.example.openspg;
import com.antgroup.openspg.reasoner.*;
import org.antlr.v4.runtime.*;
public class KGDSLExample {
public static void main(String[] args) {
// ========== 1. 定义KGDSL查询 ==========
String kgdslQuery = """
Define (s:Person)-[p:worksAt]->(o:Company)
Constraint {
o.name == "阿里巴巴"
s.age >= 25
}
Rule: result = (s, o)
""";
// ========== 2. 创建词法分析器 ==========
CharStream input = CharStreams.fromString(kgdslQuery);
KGDSLLexer lexer = new KGDSLLexer(input);
// ========== 3. 创建语法分析器 ==========
CommonTokenStream tokens = new CommonTokenStream(lexer);
KGDSLParser parser = new KGDSLParser(tokens);
// ========== 4. 解析查询 ==========
KGDSLParser.ScriptContext tree = parser.script();
// ========== 5. 打印语法树 ==========
System.out.println("语法树:");
System.out.println(tree.toStringTree(parser));
// ========== 6. 使用自定义Visitor提取信息 ==========
KGDSLInfoExtractor extractor = new KGDSLInfoExtractor();
extractor.visit(tree);
System.out.println("\n节点:");
for (String node : extractor.getNodes()) {
System.out.println(" - " + node);
}
System.out.println("\n边:");
for (String edge : extractor.getEdges()) {
System.out.println(" - " + edge);
}
}
}
/**
* 自定义Visitor,提取KGDSL信息
*/
class KGDSLInfoExtractor extends KGDSLBaseVisitor<Void> {
private List<String> nodes = new ArrayList<>();
private List<String> edges = new ArrayList<>();
@Override
public Void visitNode_pattern(KGDSLParser.Node_patternContext ctx) {
String variable = ctx.variable_name() != null ?
ctx.variable_name().getText() : "";
String label = ctx.label_name() != null ?
ctx.label_name().getText() : "";
nodes.add(variable + ":" + label);
return null;
}
@Override
public Void visitFull_edge_pointing_right(
KGDSLParser.Full_edge_pointing_rightContext ctx) {
String variable = ctx.variable_name() != null ?
ctx.variable_name().getText() : "";
String label = ctx.label_name() != null ?
ctx.label_name().getText() : "";
edges.add(variable + ":" + label);
return null;
}
public List<String> getNodes() { return nodes; }
public List<String> getEdges() { return edges; }
}输出:
语法树:
(script (base_predicated_define (the_define_structure
DEFINE (predicated_define
(node_pattern ( s : Person ))
(full_edge_pointing_right - [ p : worksAt ] ->)
(node_pattern ( o : Company ))
) { (base_rule_define ...) }
)))
节点:
- s:Person
- o:Company
边:
- p:worksAt示例2:完整的KGDSL查询执行
java
import com.antgroup.openspg.server.biz.service.ReasonerManager;
import com.antgroup.openspg.server.api.facade.dto.service.request.ReasonerTaskRequest;
import com.antgroup.openspg.server.api.facade.dto.service.response.ReasonerTaskResponse;
public class KGDSLQueryExample {
@Autowired
private ReasonerManager reasonerManager;
public void executeKGDSLQuery() {
// ========== 1. 编写KGDSL查询 ==========
String kgdslQuery = """
Define (s:Person)-[p:worksAt]->(o:Company) {
GraphStructure {
(s)-[manages]->(team:Team)
}
Rule {
o.name == "阿里巴巴"
s.age >= 25
s.age <= 60
}
}
""";
// ========== 2. 创建推理请求 ==========
ReasonerTaskRequest request = new ReasonerTaskRequest();
request.setProjectId(1L);
request.setDsl(kgdslQuery);
request.setParams(new HashMap<>());
// ========== 3. 执行推理 ==========
System.out.println("执行KGDSL查询...");
ReasonerTaskResponse response = reasonerManager.reason(request);
// ========== 4. 处理结果 ==========
ReasonerTask task = response.getTask();
System.out.println("任务ID: " + task.getTaskId());
System.out.println("状态: " + task.getStatus());
List<Map<String, Object>> results = task.getResults();
System.out.println("\n查询结果 (共 " + results.size() + " 条):");
for (Map<String, Object> result : results) {
System.out.println(" 员工: " + result.get("s"));
System.out.println(" 公司: " + result.get("o"));
System.out.println(" 管理团队: " + result.get("team"));
System.out.println(" ---");
}
}
}附录
A. Schema DSL完整语法速查
schema
# Namespace声明(必需)
namespace <命名空间名称>
# 类型定义
<类型名>(<中文名>): <类型类别>
desc: <描述> # 可选
parent: <父类型名> # 可选,继承
properties: # 属性列表
<属性名>(<中文名>): <属性类型>
index: <索引类型> # 可选:Text/KeyWord/Vector/Range
constraint: <约束> # 可选:NotNull/Unique/Enum/Regular/Range
rule: [[ <KGDSL规则> ]] # 可选:逻辑规则
properties: # 可选:子属性(结构化属性)
<子属性名>(<中文名>): <子属性类型>
relations: # 关系列表
<关系名>(<中文名>): <目标类型>
semantics: <语义关系> # 可选:inverseOf/subPropertyOf
properties: # 可选:关系属性
<关系属性名>(<中文名>): <属性类型>
rule: [[ <KGDSL规则> ]] # 可选:逻辑规则类型类别:
EntityType- 实体类型ConceptType- 概念类型EventType- 事件类型StandardType- 标准类型
基础类型:
Text- 文本Integer- 整数Float- 浮点数Boolean- 布尔值Timestamp- 时间戳
B. KGDSL完整语法速查
kgdsl
# Define模式(定义查询模式)
Define (<主体节点>)-[<关系>]->(<客体节点>) {
GraphStructure { # 图结构定义(可选)
<路径名>: (<起点>)-[<边>]->(<终点>)
}
Constraint { # 约束条件(可选)
<节点>.<属性> <操作符> <值>
<表达式1> AND <表达式2>
<表达式1> OR <表达式2>
}
Rule { # 规则定义(可选)
<变量> = <表达式>
<规则名>("<描述>"): <条件>
}
}
# 节点模式
(<变量名>:<类型标签>)
(<变量名>)
(:<类型标签>)
# 边模式
-[<变量名>:<关系标签>]-> # 单向边
-[<变量名>:<关系标签>]- # 无向边
<-[<变量名>:<关系标签>]- # 反向边
# 操作符
== != > < >= <= # 比较
AND OR NOT # 逻辑
in not in # 包含C. 文件位置索引
OpenSPG项目根目录/
├── server/biz/schema/
│ └── src/main/java/com/antgroup/openspg/server/biz/schema/
│ └── model/
│ ├── SchemaScriptTranslateUtil.java ← Schema DSL解析器
│ ├── SchemaModel.java ← Schema中间模型
│ ├── NodeTypeModel.java ← 类型模型
│ ├── PropertyModel.java ← 属性模型
│ └── EdgeTypeModel.java ← 关系模型
│
├── reasoner/kgdsl-parser/
│ ├── src/main/antlr4/com/antgroup/openspg/reasoner/
│ │ └── KGDSL.g4 ← KGDSL语法定义
│ └── target/generated-sources/antlr4/
│ └── com/antgroup/openspg/reasoner/
│ ├── KGDSLLexer.java ← 生成的词法分析器
│ ├── KGDSLParser.java ← 生成的语法分析器
│ └── KGDSLBaseVisitor.java ← 生成的访问器
│
└── dev/release/mysql/sql/
├── openspg-initdb.sql ← MySQL初始化SQL
└── initdb.sql ← MySQL补充SQLD. 参考资源
- OpenSPG官方文档: https://openspg.yuque.com/ndx6g9/docs
- ANTLR4官方文档: https://www.antlr.org/
- Schema DSL详细文档:
schema-dsl-parsing.md - 数据库Schema文档:
database-schema.md
版本信息
- 文档版本: 1.0
- OpenSPG版本: 0.8
- 最后更新: 2025-12-13
许可证
本文档基于 Apache License 2.0 开源协议。