Skip to content

OpenSPG DSL 解析源码位置详解 ​

目录 ​


1. 概述 ​

OpenSPG使用两种DSL语言,分别用于不同的目的:

1.1 两种DSL ​

DSL类型用途文件扩展名解析方式
Schema DSL定义知识图谱的类型结构(EntityType、Relation等).schemaJava字符串解析
KGDSL图查询和逻辑推理.kgdslANTLR4语法生成

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.g4

ANTLR4介绍:

  • 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)

解析过程:

  1. 词法分析:生成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
  2. 语法分析:构建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 DSLKGDSL
解析技术纯Java字符串处理ANTLR4语法生成
复杂度简单(基于缩进)复杂(完整语法树)
可扩展性有限高(修改.g4即可)
性能快(无额外开销)中等(解析开销)
错误提示简单详细(行列定位)
IDE支持无有(ANTLR插件)

4.2 使用场景对比 ​

场景Schema DSLKGDSL
类型定义✅ 主要用途❌ 不适用
图查询❌ 不支持✅ 主要用途
逻辑推理❌ 不支持✅ 支持
约束定义✅ 支持✅ 支持
规则表达⚠️ 有限支持✅ 完整支持

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补充SQL

D. 参考资源 ​


版本信息 ​

  • 文档版本: 1.0
  • OpenSPG版本: 0.8
  • 最后更新: 2025-12-13

许可证 ​

本文档基于 Apache License 2.0 开源协议。