有奖捉虫:行业应用 & 管理与支持文档专题 HOT
本文介绍如何使用 Go 语言连接和操作图数据库 KonisGraph。以 Gremlin-console tutorials 中的人和软件的关系图为示例。
enter image description here

如图所示,整个图包含2类点 person 和 software,2类边 knows 和 created,和几类属性 id、name、age、lang、weight。

环境准备

1. 安装 Go 语言环境,参考 Go 语言官网
2. 获取图数据库的连接参数。在 控制台 实例详情页中可以查看实例的 VIP 和 PORT,即内网地址和 Gremlin 端口。

示例程序

新建一个 demo 目录,并初始化 module

mkdir graph_demo
cd graph_demo
go mod init demo

示例项目目录结构

|- graph_demo
|- go.mod
|- go.sum
|- main.go
|- model
|- meta.go
|- property.go
|- vertex.go
|- edge.go

定义点、边及属性等模型

meta.go: 属性、点边等对应的元数据定义

package model

type PropertyType string

const (
PropertyLong = "T_LONG"
PropertyInt = "T_INT"
PropertyInt64 = "T_INT64"
PropertyString = "T_STRING"
PropertyDouble = "T_DOUBLE"
)

type PropertyMeta struct {
Label string
Type PropertyType
Default string
}

type VertexMeta struct {
Label string
Primary string
Properties []string
}

type EdgeMeta struct {
Label string
SrcVertexLabel string
DstVertexLabel string
Properties []string
}

property.go: 属性模型定义

package model

type Property struct {
Type string `json:"@type"`
Value PropertyValue `json:"@Value"`
}

type PropertyValue struct {
Key string `json:"key"`
Value interface{} `json:"value"`
}

vertex.go: 点模型定义

package model

import "encoding/json"

type VertexList struct {
listOfVertices List
Vertices []Vertex
}

type List struct {
Type string `json:"@type"`
Value []interface{} `json:"@value"`
}

type Vertex struct {
Type string `json:"@type"`
Value VertexValue `json:"@value"`
}

type VertexValue struct {
ID interface{} `json:"id"`
Label string `json:"label"`
Properties map[string][]Property `json:"properties,omitempty"`
}

func (vl *VertexList) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &vl.listOfVertices); err == nil {
if data, err = json.Marshal(vl.listOfVertices.Value); err != nil {
return err
}
}

return json.Unmarshal(data, &vl.Vertices)
}

edge.go: 边模型定义

package model

import "encoding/json"

type EdgeList struct {
listOfEdges List
Edges []Edge
}

type Edge struct {
Type string `json:"@type"`
Value EdgeValue `json:"@value"`
}

type EdgeValue struct {
ID interface{} `json:"id"`
Label string `json:"label"`
InVLabel string `json:"inVLabel,omitempty"`
OutVLabel string `json:"outVLabel,omitempty"`
InV interface{} `json:"inV,omitempty"`
OutV interface{} `json:"outV,omitempty"`
Properties map[string][]Property `json:"properties,omitempty"`
}

func (el *EdgeList) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &el.listOfEdges); err == nil {
if data, err = json.Marshal(el.listOfEdges.Value); err != nil {
return err
}
}

return json.Unmarshal(data, &el.Edges)
}

数据库操作

package main

import (
"fmt"
"log"
"strings"
"tutorial/model"

"github.com/northwesternmutual/grammes"
)

type Tutorial struct {
*grammes.Client
}

func New(host string, port int, username, password string) (*Tutorial, error) {
url := fmt.Sprintf("ws://%s:%d", host, port)
c, err := grammes.DialWithWebSocket(url, grammes.WithAuthUserPass(username, password))
if err != nil {
return nil, err
}
return &Tutorial{Client: c}, nil
}

func (t *Tutorial) CreatePropertyMetas(metas ...model.PropertyMeta) error {
for _, meta := range metas {
expr := fmt.Sprintf(`s.addP("%s", "%s", "%s")`, meta.Label, meta.Type, meta.Default)
_, err := t.ExecuteStringQuery(expr)
if err != nil {
return err
}
}
return nil
}

func (t *Tutorial) CreateVertexMetas(metas ...model.VertexMeta) error {
for _, meta := range metas {
expr := fmt.Sprintf(`s.addV("%s", "%s", ["%s"])`, meta.Label, meta.Primary, strings.Join(meta.Properties, "\\",\\""))
_, err := t.ExecuteStringQuery(expr)
if err != nil {
return err
}
}
return nil
}

func (t *Tutorial) CreateEdgeMetas(metas ...model.EdgeMeta) error {
for _, meta := range metas {
expr := fmt.Sprintf(`s.addE("%s", "%s", "%s", ["%s"])`, meta.Label, meta.SrcVertexLabel, meta.DstVertexLabel,
strings.Join(meta.Properties, "\\",\\""))
_, err := t.ExecuteStringQuery(expr)
if err != nil {
return err
}
}

return nil
}

func createMetas(tutorial *Tutorial) {
propMetas := []model.PropertyMeta{
{Label: "id", Type: model.PropertyLong, Default: "0"},
{Label: "name", Type: model.PropertyString, Default: ""},
{Label: "age", Type: model.PropertyInt, Default: "0"},
{Label: "weight", Type: model.PropertyDouble, Default: "0.0"},
{Label: "lang", Type: model.PropertyString, Default: ""},
}
if err := tutorial.CreatePropertyMetas(propMetas...); err != nil {
log.Fatal("create property meta err ", err)
}

vertexMetas := []model.VertexMeta{
{Label: "person", Primary: "id", Properties: []string{"name", "age"}},
{Label: "software", Primary: "id", Properties: []string{"name", "lang"}},
}
if err := tutorial.CreateVertexMetas(vertexMetas...); err != nil {
log.Fatal("create vertex meta err ", err)
}

edgeMetas := []model.EdgeMeta{
{Label: "knows", SrcVertexLabel: "person", DstVertexLabel: "person", Properties: []string{"weight"}},
{Label: "created", SrcVertexLabel: "person", DstVertexLabel: "software", Properties: []string{"weight"}},
}
if err := tutorial.CreateEdgeMetas(edgeMetas...); err != nil {
log.Fatal("create edge meta err ", err)
}
}

func addVertexAndEdge(tutorial *Tutorial) {
//添加点
//添加点 person,其中 id=1,属性 name=marko,age=29
_, err := tutorial.ExecuteBoundStringQuery("g.addV(LABEL).property(id, ID).property('name', NAME).property('age', AGE) ", map[string]string{"ID": "1", "LABEL": "person", "NAME": "marko", "AGE": "29"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加点 person,其中 id=2,属性 name=vadas,age=27
_, err = tutorial.ExecuteBoundStringQuery("g.addV(LABEL).property(id, ID).property('name', NAME).property('age', AGE) ", map[string]string{"ID": "2", "LABEL": "person", "NAME": "vadas", "AGE": "27"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加点 person,其中 id=4,属性 name=josh,age=32
_, err = tutorial.ExecuteBoundStringQuery("g.addV(LABEL).property(id, ID).property('name', NAME).property('age', AGE) ", map[string]string{"ID": "4", "LABEL": "person", "NAME": "josh", "AGE": "32"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加点 person,其中 id=6,属性 name =peter,age=35
_, err = tutorial.ExecuteBoundStringQuery("g.addV(LABEL).property(id, ID).property('name', NAME).property('age', AGE) ", map[string]string{"ID": "6", "LABEL": "person", "NAME": "peter", "AGE": "35"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加点 software,其中 id=3,属性 name=lop,lang=java
_, err = tutorial.ExecuteBoundStringQuery("g.addV(LABEL).property(id, ID).property('name', NAME).property('lang', LANG) ", map[string]string{"ID": "3", "LABEL": "software", "NAME": "lop", "LANG": "java"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加点 software,其中 id=5,属性 name=ripple,lang=java
_, err = tutorial.ExecuteBoundStringQuery("g.addV(LABEL).property(id, ID).property('name', NAME).property('lang', LANG) ", map[string]string{"ID": "5", "LABEL": "software", "NAME": "ripple", "LANG": "java"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}

//添加边
//添加边 knows,其中起点 id 为1,终点 id 为2
_, err = tutorial.ExecuteBoundStringQuery("g.V(SRC).addE(LABEL).to(V(DST)) ", map[string]string{"LABEL": "knows", "SRC": "1", "DST": "2"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加边 knows,其中起点 id 为1,终点 id 为4
_, err = tutorial.ExecuteBoundStringQuery("g.V(SRC).addE(LABEL).to(V(DST)) ", map[string]string{"LABEL": "knows", "SRC": "1", "DST": "4"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加边 created,其中起点 id 为1,终点 id 为3,属性 weight=0.4
_, err = tutorial.ExecuteBoundStringQuery("g.V(SRC).addE(LABEL).to(V(DST)).property('weight', WEIGHT) ", map[string]string{"LABEL": "created", "SRC": "1", "DST": "3", "WEIGHT": "0.4"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加边 created,其中起点 id 为6,终点 id 为3,属性 weight=0.2
_, err = tutorial.ExecuteBoundStringQuery("g.V(SRC).addE(LABEL).to(V(DST)).property('weight', WEIGHT) ", map[string]string{"LABEL": "created", "SRC": "6", "DST": "3", "WEIGHT": "0.2"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加边 created,其中起点 id 为4,终点 id 为3,属性 weight=0.4
_, err = tutorial.ExecuteBoundStringQuery("g.V(SRC).addE(LABEL).to(V(DST)).property('weight', WEIGHT) ", map[string]string{"LABEL": "created", "SRC": "4", "DST": "3", "WEIGHT": "0.4"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
//添加边 created,其中起点 id 为4,终点 id 为5,属性 weight=1.0
_, err = tutorial.ExecuteBoundStringQuery("g.V(SRC).addE(LABEL).to(V(DST)).property('weight', WEIGHT) ", map[string]string{"LABEL": "created", "SRC": "4", "DST": "5", "WEIGHT": "1.0"}, map[string]string{})
if err != nil {
log.Fatal("ExecuteBoundStringQuery addV err ", err)
}
}

func main() {
tutorial, err := New("KONISGRAPH_VIP", KONISGRAPH_PORT, "your useranme", "your password")
//KONISGRAPH_VIP 图数据库 KonisGraph 实例的内网地址 vip,如 10.xx.xx.107
//KONISGRAPH_PORT 图数据库 KonisGraph 实例的 Gremlin 端口,如 8186
if err != nil {
log.Fatal("open gremlin connection err ", err)
}

// 创建属性、点、边等元数据
createMetas(tutorial)

// 添加点和边
addVertexAndEdge(tutorial)

// 做一些查询
// 查看 marko 的信息
data, _ := tutorial.ExecuteBoundStringQuery("g.V().hasLabel(LABEL).has('name', NAME).valueMap()", map[string]string{"LABEL": "person", "NAME": "marko"}, map[string]string{})
for _, item := range data {
log.Println(string(item))
}
// 查找 marko 都认识哪些人
data, _ = tutorial.ExecuteBoundStringQuery("g.V().hasLabel(LABEL).has('name', NAME).out(OUT_LABEL)", map[string]string{"LABEL": "person", "NAME": "marko","OUT_LABEL":"knows"}, map[string]string{})
for _, item := range data {
var vertices model.VertexList
if err := vertices.UnmarshalJSON(item); err != nil {
log.Fatal("unmarshal resp err: ", err)
}
var names []interface{}
for _, vertex := range vertices.Vertices {
names = append(names, vertex.Value.Properties["name"][0].Value.Value)
}
log.Print("Who marko knows: ", names)
}

// 查找哪些人创建了软件 lop
data, _ = tutorial.ExecuteBoundStringQuery("g.V().hasLabel(LABEL).has('name', NAME).in(IN_LABEL)", map[string]string{"LABEL": "software", "NAME": "lop","IN_LABEL":"created"}, map[string]string{})
for _, item := range data {
var vertices model.VertexList
if err := vertices.UnmarshalJSON(item); err != nil {
log.Fatal("unmarshal resp err: ", err)
}
var names []interface{}
for _, vertex := range vertices.Vertices {
names = append(names, vertex.Value.Properties["name"][0].Value.Value)
}
log.Println("Who creates software lop: ", names)
}

// 统计点数量, 返回值为数组,转 string 后为 {"@type":"g:List","@value":[{"@type":"g:Int64","@value":9999999999}]},真正的返回值为 value 后的9999999999,需要自己处理,其他非点边返回数据处理方式类似
data, _ = tutorial.ExecuteBoundStringQuery("g.V().count()", map[string]string{}, map[string]string{})
for _, item := range data {
log.Println("Who creates software lop: ", string(item))
}

//关闭客户端,客户端可复用
tutorial.Close()
}

运行示例程序

go run main.go