From b498aea04dd6e3fbd806ca407ee2ea9c0348b756 Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Thu, 15 Feb 2024 17:15:13 +0800 Subject: [PATCH 01/13] WIP - ApplyTag --- session_pool.go | 17 +++++++++ session_pool_test.go | 89 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 102 insertions(+), 4 deletions(-) diff --git a/session_pool.go b/session_pool.go index 43f1aef9..ea4f96af 100644 --- a/session_pool.go +++ b/session_pool.go @@ -12,6 +12,7 @@ import ( "container/list" "fmt" "strconv" + "strings" "sync" "time" @@ -320,6 +321,22 @@ func (pool *SessionPool) CreateTag(tag LabelSchema) (*ResultSet, error) { return rs, nil } +func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { + // 1. Check if the tag exists + _, err := pool.DescTag(tag.Name) + fmt.Println("DEBUG: apply tag") + fmt.Println(err) + if err != nil { + // 2. If the tag does not exist, create it + if strings.Contains(strings.ToLower(err.Error()), "not exist") { + return pool.CreateTag(tag) + } + return nil, err + } + + return nil, nil +} + func (pool *SessionPool) DescTag(tagName string) ([]Label, error) { q := fmt.Sprintf("DESC TAG %s;", tagName) rs, err := pool.ExecuteAndCheck(q) diff --git a/session_pool_test.go b/session_pool_test.go index 1fd3e166..b1379eb9 100644 --- a/session_pool_test.go +++ b/session_pool_test.go @@ -359,18 +359,19 @@ func TestSessionPoolSpaceChange(t *testing.T) { } func TestSessionPoolApplySchema(t *testing.T) { - err := prepareSpace("test_space_schema") + spaceName := "test_space_schema" + err := prepareSpace(spaceName) if err != nil { t.Fatal(err) } - defer dropSpace("test_space_schema") + defer dropSpace(spaceName) hostAddress := HostAddress{Host: address, Port: port} config, err := NewSessionPoolConf( "root", "nebula", []HostAddress{hostAddress}, - "test_space_schema") + spaceName) if err != nil { t.Errorf("failed to create session pool config, %s", err.Error()) } @@ -394,7 +395,7 @@ func TestSessionPoolApplySchema(t *testing.T) { for _, space := range spaces { spaceNames = append(spaceNames, space.Name) } - assert.Contains(t, spaceNames, "test_space_schema", "should have test_space_schema") + assert.Contains(t, spaceNames, spaceName) tagSchema := LabelSchema{ Name: "account", @@ -464,6 +465,86 @@ func TestSessionPoolApplySchema(t *testing.T) { assert.Equal(t, "string", labels[0].Type, "field type should be string") } +func TestSessionPoolApplyTag(t *testing.T) { + spaceName := "test_space_apply_tag" + err := prepareSpace(spaceName) + if err != nil { + t.Fatal(err) + } + defer dropSpace(spaceName) + + hostAddress := HostAddress{Host: address, Port: port} + config, err := NewSessionPoolConf( + "root", + "nebula", + []HostAddress{hostAddress}, + spaceName) + if err != nil { + t.Errorf("failed to create session pool config, %s", err.Error()) + } + + // allow only one session in the pool so it is easier to test + config.maxSize = 1 + + // create session pool + sessionPool, err := NewSessionPool(*config, DefaultLogger{}) + if err != nil { + t.Fatal(err) + } + defer sessionPool.Close() + + spaces, err := sessionPool.ShowSpaces() + if err != nil { + t.Fatal(err) + } + assert.LessOrEqual(t, 1, len(spaces), "should have at least 1 space") + var spaceNames []string + for _, space := range spaces { + spaceNames = append(spaceNames, space.Name) + } + assert.Contains(t, spaceNames, spaceName) + + tagSchema := LabelSchema{ + Name: "account", + Fields: []LabelFieldSchema{ + { + Field: "name", + Nullable: false, + }, + // { + // Field: "email", + // Nullable: true, + // }, + // { + // Field: "phone", + // Type: "int64", + // Nullable: true, + // }, + }, + } + _, err = sessionPool.ApplyTag(tagSchema) + if err != nil { + t.Fatal(err) + } + tags, err := sessionPool.ShowTags() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(tags), "should have 1 tags") + assert.Equal(t, "account", tags[0].Name, "tag name should be account") + labels, err := sessionPool.DescTag("account") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(labels), "should have 1 labels") + assert.Equal(t, "name", labels[0].Field, "field name should be name") + assert.Equal(t, "string", labels[0].Type, "field type should be string") + // assert.Equal(t, "email", labels[1].Field, "field name should be email") + // assert.Equal(t, "string", labels[1].Type, "field type should be string") + // assert.Equal(t, "phone", labels[2].Field, "field name should be phone") + // assert.Equal(t, "int64", labels[2].Type, "field type should be int64") +} + func TestIdleSessionCleaner(t *testing.T) { err := prepareSpace("client_test") if err != nil { From 34befe9bd93f167598b6336035612a9d0a61c998 Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Thu, 15 Feb 2024 18:02:35 +0800 Subject: [PATCH 02/13] Add new fields --- const.go | 13 +++++++++++++ label.go | 8 ++++++++ label_test.go | 11 +++++++++++ session_pool.go | 33 +++++++++++++++++++++++++++++--- session_pool_test.go | 45 +++++++++++++++++++++++++++++++++++--------- 5 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 const.go diff --git a/const.go b/const.go new file mode 100644 index 00000000..bdc54cdc --- /dev/null +++ b/const.go @@ -0,0 +1,13 @@ +/* + * + * Copyright (c) 2024 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + * + */ + +package nebula_go + +const ( + ErrorTagNotFound = "TagNotFound: Tag not existed!" +) diff --git a/label.go b/label.go index ac89a160..8ee4a9b1 100644 --- a/label.go +++ b/label.go @@ -84,6 +84,14 @@ func (edge LabelSchema) BuildDropEdgeQL() string { return q } +func (field LabelFieldSchema) BuildAddFieldQL(labelName string) string { + q := "ALTER TAG " + labelName + " ADD (" + field.Field + " " + field.Type + if !field.Nullable { + q += " NOT NULL" + } + return q + ");" +} + type LabelName struct { Name string `nebula:"Name"` } diff --git a/label_test.go b/label_test.go index 45a43968..9f993baa 100644 --- a/label_test.go +++ b/label_test.go @@ -48,3 +48,14 @@ func TestBuildCreateEdgeQL(t *testing.T) { assert.Equal(t, "CREATE EDGE IF NOT EXISTS account_email (email string NOT NULL);", edge.BuildCreateEdgeQL()) assert.Equal(t, "DROP EDGE IF EXISTS account_email;", edge.BuildDropEdgeQL()) } + +func TestBuildAddFieldQL(t *testing.T) { + field := LabelFieldSchema{ + Field: "name", + Type: "string", + Nullable: false, + } + assert.Equal(t, "ALTER TAG account ADD (name string NOT NULL);", field.BuildAddFieldQL("account")) + field.Nullable = true + assert.Equal(t, "ALTER TAG account ADD (name string);", field.BuildAddFieldQL("account")) +} diff --git a/session_pool.go b/session_pool.go index ea4f96af..5e3dc75f 100644 --- a/session_pool.go +++ b/session_pool.go @@ -324,16 +324,43 @@ func (pool *SessionPool) CreateTag(tag LabelSchema) (*ResultSet, error) { func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { // 1. Check if the tag exists _, err := pool.DescTag(tag.Name) - fmt.Println("DEBUG: apply tag") - fmt.Println(err) if err != nil { // 2. If the tag does not exist, create it - if strings.Contains(strings.ToLower(err.Error()), "not exist") { + if strings.Contains(err.Error(), ErrorTagNotFound) { return pool.CreateTag(tag) } return nil, err } + // 3. If the tag exists, check if the fields are the same + fields, err := pool.DescTag(tag.Name) + if err != nil { + return nil, err + } + + // 4. Add new fields + for _, expected := range tag.Fields { + found := false + for _, actual := range fields { + if expected.Field == actual.Field { + found = true + break + } + } + if !found { + // 4.1 Add the field + q := expected.BuildAddFieldQL(tag.Name) + rs, err := pool.ExecuteAndCheck(q) + if err != nil { + fmt.Println("DEBUG: create field") + fmt.Println(rs) + return nil, err + } + } + } + fmt.Println("DEBUG: apply tag") + fmt.Println(err) + return nil, nil } diff --git a/session_pool_test.go b/session_pool_test.go index b1379eb9..1c1abee6 100644 --- a/session_pool_test.go +++ b/session_pool_test.go @@ -511,10 +511,37 @@ func TestSessionPoolApplyTag(t *testing.T) { Field: "name", Nullable: false, }, - // { - // Field: "email", - // Nullable: true, - // }, + }, + } + _, err = sessionPool.ApplyTag(tagSchema) + if err != nil { + t.Fatal(err) + } + tags, err := sessionPool.ShowTags() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(tags), "should have 1 tags") + assert.Equal(t, "account", tags[0].Name, "tag name should be account") + labels, err := sessionPool.DescTag("account") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(labels), "should have 1 labels") + assert.Equal(t, "name", labels[0].Field, "field name should be name") + assert.Equal(t, "string", labels[0].Type, "field type should be string") + + tagSchema = LabelSchema{ + Name: "account", + Fields: []LabelFieldSchema{ + { + Field: "name", + Nullable: false, + }, + { + Field: "email", + Nullable: true, + }, // { // Field: "phone", // Type: "int64", @@ -526,21 +553,21 @@ func TestSessionPoolApplyTag(t *testing.T) { if err != nil { t.Fatal(err) } - tags, err := sessionPool.ShowTags() + tags, err = sessionPool.ShowTags() if err != nil { t.Fatal(err) } assert.Equal(t, 1, len(tags), "should have 1 tags") assert.Equal(t, "account", tags[0].Name, "tag name should be account") - labels, err := sessionPool.DescTag("account") + labels, err = sessionPool.DescTag("account") if err != nil { t.Fatal(err) } - assert.Equal(t, 1, len(labels), "should have 1 labels") + assert.Equal(t, 2, len(labels), "should have 2 labels") assert.Equal(t, "name", labels[0].Field, "field name should be name") assert.Equal(t, "string", labels[0].Type, "field type should be string") - // assert.Equal(t, "email", labels[1].Field, "field name should be email") - // assert.Equal(t, "string", labels[1].Type, "field type should be string") + assert.Equal(t, "email", labels[1].Field, "field name should be email") + assert.Equal(t, "string", labels[1].Type, "field type should be string") // assert.Equal(t, "phone", labels[2].Field, "field name should be phone") // assert.Equal(t, "int64", labels[2].Type, "field type should be int64") } From 04b228074dae55b5adcff73d96acd0cbac9580a5 Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Thu, 15 Feb 2024 20:28:22 +0800 Subject: [PATCH 03/13] Drop field --- label.go | 4 ++++ label_test.go | 7 +++++++ session_pool.go | 49 ++++++++++++++++++++++++++++++++++++++------ session_pool_test.go | 26 ++++++++++++----------- 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/label.go b/label.go index 8ee4a9b1..f3a9b6f8 100644 --- a/label.go +++ b/label.go @@ -92,6 +92,10 @@ func (field LabelFieldSchema) BuildAddFieldQL(labelName string) string { return q + ");" } +func (field Label) BuildDropFieldQL(labelName string) string { + return "ALTER TAG " + labelName + " DROP " + field.Field + ";" +} + type LabelName struct { Name string `nebula:"Name"` } diff --git a/label_test.go b/label_test.go index 9f993baa..77483b7e 100644 --- a/label_test.go +++ b/label_test.go @@ -59,3 +59,10 @@ func TestBuildAddFieldQL(t *testing.T) { field.Nullable = true assert.Equal(t, "ALTER TAG account ADD (name string);", field.BuildAddFieldQL("account")) } + +func TestBuildDropFieldQL(t *testing.T) { + field := Label{ + Field: "name", + } + assert.Equal(t, "ALTER TAG account DROP name;", field.BuildDropFieldQL("account")) +} diff --git a/session_pool.go b/session_pool.go index 5e3dc75f..963be2db 100644 --- a/session_pool.go +++ b/session_pool.go @@ -321,6 +321,18 @@ func (pool *SessionPool) CreateTag(tag LabelSchema) (*ResultSet, error) { return rs, nil } +// ApplyTag applies the given tag to the graph. +// 1. If the tag does not exist, it will be created. +// 2. If the tag exists, it will be checked if the fields are the same. +// 2.1 If not, the new fields will be added. +// 2.2 If the field type is different, it will return an error. +// 2.3 If a field exists in the graph but not in the given tag, +// +// it will be removed. +// +// Notice: +// We won't change the field type because it has +// unexpected behavior for the data. func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { // 1. Check if the tag exists _, err := pool.DescTag(tag.Name) @@ -344,22 +356,47 @@ func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { for _, actual := range fields { if expected.Field == actual.Field { found = true + // 4.1 Check if the field type is different + if expected.Type != actual.Type { + return nil, fmt.Errorf("field type is different. "+ + "Expected: %s, Actual: %s", expected.Type, actual.Type) + } break } } if !found { - // 4.1 Add the field + // 4.2 Add the not exists field q := expected.BuildAddFieldQL(tag.Name) - rs, err := pool.ExecuteAndCheck(q) + fmt.Println("DEBUG: Add field") + fmt.Println(q) + _, err := pool.ExecuteAndCheck(q) if err != nil { - fmt.Println("DEBUG: create field") - fmt.Println(rs) return nil, err } } } - fmt.Println("DEBUG: apply tag") - fmt.Println(err) + + // 5. Remove the not expected field + for _, actual := range fields { + redundant := true + for _, expected := range tag.Fields { + if expected.Field == actual.Field { + redundant = false + break + } + } + if redundant { + // 5.1 Remove the not expected field + q := actual.BuildDropFieldQL(tag.Name) + fmt.Println("DEBUG: Remove field") + fmt.Println(q) + _, err := pool.ExecuteAndCheck(q) + if err != nil { + return nil, err + } + } + + } return nil, nil } diff --git a/session_pool_test.go b/session_pool_test.go index 1c1abee6..85c5fbb5 100644 --- a/session_pool_test.go +++ b/session_pool_test.go @@ -497,7 +497,7 @@ func TestSessionPoolApplyTag(t *testing.T) { if err != nil { t.Fatal(err) } - assert.LessOrEqual(t, 1, len(spaces), "should have at least 1 space") + assert.LessOrEqual(t, 1, len(spaces)) var spaceNames []string for _, space := range spaces { spaceNames = append(spaceNames, space.Name) @@ -521,13 +521,13 @@ func TestSessionPoolApplyTag(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, 1, len(tags), "should have 1 tags") + assert.Equal(t, 1, len(tags)) assert.Equal(t, "account", tags[0].Name, "tag name should be account") labels, err := sessionPool.DescTag("account") if err != nil { t.Fatal(err) } - assert.Equal(t, 1, len(labels), "should have 1 labels") + assert.Equal(t, 1, len(labels)) assert.Equal(t, "name", labels[0].Field, "field name should be name") assert.Equal(t, "string", labels[0].Type, "field type should be string") @@ -536,17 +536,19 @@ func TestSessionPoolApplyTag(t *testing.T) { Fields: []LabelFieldSchema{ { Field: "name", + Type: "string", Nullable: false, }, { Field: "email", + Type: "string", + Nullable: true, + }, + { + Field: "phone", + Type: "int64", Nullable: true, }, - // { - // Field: "phone", - // Type: "int64", - // Nullable: true, - // }, }, } _, err = sessionPool.ApplyTag(tagSchema) @@ -557,19 +559,19 @@ func TestSessionPoolApplyTag(t *testing.T) { if err != nil { t.Fatal(err) } - assert.Equal(t, 1, len(tags), "should have 1 tags") + assert.Equal(t, 1, len(tags)) assert.Equal(t, "account", tags[0].Name, "tag name should be account") labels, err = sessionPool.DescTag("account") if err != nil { t.Fatal(err) } - assert.Equal(t, 2, len(labels), "should have 2 labels") + assert.Equal(t, 3, len(labels)) assert.Equal(t, "name", labels[0].Field, "field name should be name") assert.Equal(t, "string", labels[0].Type, "field type should be string") assert.Equal(t, "email", labels[1].Field, "field name should be email") assert.Equal(t, "string", labels[1].Type, "field type should be string") - // assert.Equal(t, "phone", labels[2].Field, "field name should be phone") - // assert.Equal(t, "int64", labels[2].Type, "field type should be int64") + assert.Equal(t, "phone", labels[2].Field, "field name should be phone") + assert.Equal(t, "int64", labels[2].Type, "field type should be int64") } func TestIdleSessionCleaner(t *testing.T) { From dc0f5068fa05b33eb7693739e4cdb3cb50c27a48 Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Thu, 15 Feb 2024 20:35:01 +0800 Subject: [PATCH 04/13] Add more tests --- session_pool_test.go | 54 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/session_pool_test.go b/session_pool_test.go index 85c5fbb5..53b59d50 100644 --- a/session_pool_test.go +++ b/session_pool_test.go @@ -522,14 +522,14 @@ func TestSessionPoolApplyTag(t *testing.T) { t.Fatal(err) } assert.Equal(t, 1, len(tags)) - assert.Equal(t, "account", tags[0].Name, "tag name should be account") + assert.Equal(t, "account", tags[0].Name) labels, err := sessionPool.DescTag("account") if err != nil { t.Fatal(err) } assert.Equal(t, 1, len(labels)) - assert.Equal(t, "name", labels[0].Field, "field name should be name") - assert.Equal(t, "string", labels[0].Type, "field type should be string") + assert.Equal(t, "name", labels[0].Field) + assert.Equal(t, "string", labels[0].Type) tagSchema = LabelSchema{ Name: "account", @@ -560,18 +560,52 @@ func TestSessionPoolApplyTag(t *testing.T) { t.Fatal(err) } assert.Equal(t, 1, len(tags)) - assert.Equal(t, "account", tags[0].Name, "tag name should be account") + assert.Equal(t, "account", tags[0].Name) labels, err = sessionPool.DescTag("account") if err != nil { t.Fatal(err) } assert.Equal(t, 3, len(labels)) - assert.Equal(t, "name", labels[0].Field, "field name should be name") - assert.Equal(t, "string", labels[0].Type, "field type should be string") - assert.Equal(t, "email", labels[1].Field, "field name should be email") - assert.Equal(t, "string", labels[1].Type, "field type should be string") - assert.Equal(t, "phone", labels[2].Field, "field name should be phone") - assert.Equal(t, "int64", labels[2].Type, "field type should be int64") + assert.Equal(t, "name", labels[0].Field) + assert.Equal(t, "string", labels[0].Type) + assert.Equal(t, "email", labels[1].Field) + assert.Equal(t, "string", labels[1].Type) + assert.Equal(t, "phone", labels[2].Field) + assert.Equal(t, "int64", labels[2].Type) + tagSchema = LabelSchema{ + Name: "account", + Fields: []LabelFieldSchema{ + { + Field: "name", + Type: "string", + Nullable: false, + }, + { + Field: "phone", + Type: "int64", + Nullable: true, + }, + }, + } + _, err = sessionPool.ApplyTag(tagSchema) + if err != nil { + t.Fatal(err) + } + tags, err = sessionPool.ShowTags() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(tags)) + assert.Equal(t, "account", tags[0].Name) + labels, err = sessionPool.DescTag("account") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 2, len(labels)) + assert.Equal(t, "name", labels[0].Field) + assert.Equal(t, "string", labels[0].Type) + assert.Equal(t, "phone", labels[1].Field) + assert.Equal(t, "int64", labels[1].Type) } func TestIdleSessionCleaner(t *testing.T) { From 3f4ce0e318b5f4031218d7006490f3f0f947bd7f Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Thu, 15 Feb 2024 20:43:55 +0800 Subject: [PATCH 05/13] Fix --- label.go | 2 +- label_test.go | 2 +- session_pool.go | 7 +------ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/label.go b/label.go index f3a9b6f8..5ec95ca1 100644 --- a/label.go +++ b/label.go @@ -93,7 +93,7 @@ func (field LabelFieldSchema) BuildAddFieldQL(labelName string) string { } func (field Label) BuildDropFieldQL(labelName string) string { - return "ALTER TAG " + labelName + " DROP " + field.Field + ";" + return "ALTER TAG " + labelName + " DROP (" + field.Field + ");" } type LabelName struct { diff --git a/label_test.go b/label_test.go index 77483b7e..bf355bc5 100644 --- a/label_test.go +++ b/label_test.go @@ -64,5 +64,5 @@ func TestBuildDropFieldQL(t *testing.T) { field := Label{ Field: "name", } - assert.Equal(t, "ALTER TAG account DROP name;", field.BuildDropFieldQL("account")) + assert.Equal(t, "ALTER TAG account DROP (name);", field.BuildDropFieldQL("account")) } diff --git a/session_pool.go b/session_pool.go index 963be2db..33f01ad2 100644 --- a/session_pool.go +++ b/session_pool.go @@ -327,8 +327,7 @@ func (pool *SessionPool) CreateTag(tag LabelSchema) (*ResultSet, error) { // 2.1 If not, the new fields will be added. // 2.2 If the field type is different, it will return an error. // 2.3 If a field exists in the graph but not in the given tag, -// -// it will be removed. +// it will be removed. // // Notice: // We won't change the field type because it has @@ -367,8 +366,6 @@ func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { if !found { // 4.2 Add the not exists field q := expected.BuildAddFieldQL(tag.Name) - fmt.Println("DEBUG: Add field") - fmt.Println(q) _, err := pool.ExecuteAndCheck(q) if err != nil { return nil, err @@ -388,8 +385,6 @@ func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { if redundant { // 5.1 Remove the not expected field q := actual.BuildDropFieldQL(tag.Name) - fmt.Println("DEBUG: Remove field") - fmt.Println(q) _, err := pool.ExecuteAndCheck(q) if err != nil { return nil, err From ada9aa430a6855e37b998e85a5d49adc2ba8479a Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Sun, 18 Feb 2024 15:27:04 +0800 Subject: [PATCH 06/13] naming --- label.go | 4 ++-- label_test.go | 6 +++--- session_pool.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/label.go b/label.go index 5ec95ca1..6eb64203 100644 --- a/label.go +++ b/label.go @@ -84,7 +84,7 @@ func (edge LabelSchema) BuildDropEdgeQL() string { return q } -func (field LabelFieldSchema) BuildAddFieldQL(labelName string) string { +func (field LabelFieldSchema) BuildAddTagFieldQL(labelName string) string { q := "ALTER TAG " + labelName + " ADD (" + field.Field + " " + field.Type if !field.Nullable { q += " NOT NULL" @@ -92,7 +92,7 @@ func (field LabelFieldSchema) BuildAddFieldQL(labelName string) string { return q + ");" } -func (field Label) BuildDropFieldQL(labelName string) string { +func (field Label) BuildDropTagFieldQL(labelName string) string { return "ALTER TAG " + labelName + " DROP (" + field.Field + ");" } diff --git a/label_test.go b/label_test.go index bf355bc5..d74b8c2b 100644 --- a/label_test.go +++ b/label_test.go @@ -55,14 +55,14 @@ func TestBuildAddFieldQL(t *testing.T) { Type: "string", Nullable: false, } - assert.Equal(t, "ALTER TAG account ADD (name string NOT NULL);", field.BuildAddFieldQL("account")) + assert.Equal(t, "ALTER TAG account ADD (name string NOT NULL);", field.BuildAddTagFieldQL("account")) field.Nullable = true - assert.Equal(t, "ALTER TAG account ADD (name string);", field.BuildAddFieldQL("account")) + assert.Equal(t, "ALTER TAG account ADD (name string);", field.BuildAddTagFieldQL("account")) } func TestBuildDropFieldQL(t *testing.T) { field := Label{ Field: "name", } - assert.Equal(t, "ALTER TAG account DROP (name);", field.BuildDropFieldQL("account")) + assert.Equal(t, "ALTER TAG account DROP (name);", field.BuildDropTagFieldQL("account")) } diff --git a/session_pool.go b/session_pool.go index 33f01ad2..2e192826 100644 --- a/session_pool.go +++ b/session_pool.go @@ -365,7 +365,7 @@ func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { } if !found { // 4.2 Add the not exists field - q := expected.BuildAddFieldQL(tag.Name) + q := expected.BuildAddTagFieldQL(tag.Name) _, err := pool.ExecuteAndCheck(q) if err != nil { return nil, err @@ -384,7 +384,7 @@ func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { } if redundant { // 5.1 Remove the not expected field - q := actual.BuildDropFieldQL(tag.Name) + q := actual.BuildDropTagFieldQL(tag.Name) _, err := pool.ExecuteAndCheck(q) if err != nil { return nil, err From 4c1fa74c456f04e43ee5c30173f18065c0f6f4e7 Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Tue, 20 Feb 2024 17:35:52 +0800 Subject: [PATCH 07/13] restructure --- schema_manager.go | 97 +++++++++++++++++++++++++ schema_manager_test.go | 160 +++++++++++++++++++++++++++++++++++++++++ session_pool.go | 76 -------------------- session_pool_test.go | 143 ------------------------------------ 4 files changed, 257 insertions(+), 219 deletions(-) create mode 100644 schema_manager.go create mode 100644 schema_manager_test.go diff --git a/schema_manager.go b/schema_manager.go new file mode 100644 index 00000000..a7c45ef9 --- /dev/null +++ b/schema_manager.go @@ -0,0 +1,97 @@ +/* + * + * Copyright (c) 2024 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + * + */ + +package nebula_go + +import ( + "fmt" + "strings" +) + +type SchemaManager struct { + pool *SessionPool +} + +func NewSchemaManager(pool *SessionPool) *SchemaManager { + return &SchemaManager{pool: pool} +} + +// ApplyTag applies the given tag to the graph. +// 1. If the tag does not exist, it will be created. +// 2. If the tag exists, it will be checked if the fields are the same. +// 2.1 If not, the new fields will be added. +// 2.2 If the field type is different, it will return an error. +// 2.3 If a field exists in the graph but not in the given tag, +// it will be removed. +// +// Notice: +// We won't change the field type because it has +// unexpected behavior for the data. +func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { + // 1. Check if the tag exists + _, err := mgr.pool.DescTag(tag.Name) + if err != nil { + // 2. If the tag does not exist, create it + if strings.Contains(err.Error(), ErrorTagNotFound) { + return mgr.pool.CreateTag(tag) + } + return nil, err + } + + // 3. If the tag exists, check if the fields are the same + fields, err := mgr.pool.DescTag(tag.Name) + if err != nil { + return nil, err + } + + // 4. Add new fields + for _, expected := range tag.Fields { + found := false + for _, actual := range fields { + if expected.Field == actual.Field { + found = true + // 4.1 Check if the field type is different + if expected.Type != actual.Type { + return nil, fmt.Errorf("field type is different. "+ + "Expected: %s, Actual: %s", expected.Type, actual.Type) + } + break + } + } + if !found { + // 4.2 Add the not exists field + q := expected.BuildAddTagFieldQL(tag.Name) + _, err := mgr.pool.ExecuteAndCheck(q) + if err != nil { + return nil, err + } + } + } + + // 5. Remove the not expected field + for _, actual := range fields { + redundant := true + for _, expected := range tag.Fields { + if expected.Field == actual.Field { + redundant = false + break + } + } + if redundant { + // 5.1 Remove the not expected field + q := actual.BuildDropTagFieldQL(tag.Name) + _, err := mgr.pool.ExecuteAndCheck(q) + if err != nil { + return nil, err + } + } + + } + + return nil, nil +} diff --git a/schema_manager_test.go b/schema_manager_test.go new file mode 100644 index 00000000..5b04caf6 --- /dev/null +++ b/schema_manager_test.go @@ -0,0 +1,160 @@ +/* + * + * Copyright (c) 2024 vesoft inc. All rights reserved. + * + * This source code is licensed under Apache 2.0 License. + * + */ + +package nebula_go + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSessionPoolApplyTag(t *testing.T) { + spaceName := "test_space_apply_tag" + err := prepareSpace(spaceName) + if err != nil { + t.Fatal(err) + } + defer dropSpace(spaceName) + + hostAddress := HostAddress{Host: address, Port: port} + config, err := NewSessionPoolConf( + "root", + "nebula", + []HostAddress{hostAddress}, + spaceName) + if err != nil { + t.Errorf("failed to create session pool config, %s", err.Error()) + } + + // allow only one session in the pool so it is easier to test + config.maxSize = 1 + + // create session pool + sessionPool, err := NewSessionPool(*config, DefaultLogger{}) + if err != nil { + t.Fatal(err) + } + defer sessionPool.Close() + + schemaManager := NewSchemaManager(sessionPool) + + spaces, err := sessionPool.ShowSpaces() + if err != nil { + t.Fatal(err) + } + assert.LessOrEqual(t, 1, len(spaces)) + var spaceNames []string + for _, space := range spaces { + spaceNames = append(spaceNames, space.Name) + } + assert.Contains(t, spaceNames, spaceName) + + tagSchema := LabelSchema{ + Name: "account", + Fields: []LabelFieldSchema{ + { + Field: "name", + Nullable: false, + }, + }, + } + _, err = schemaManager.ApplyTag(tagSchema) + if err != nil { + t.Fatal(err) + } + tags, err := sessionPool.ShowTags() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(tags)) + assert.Equal(t, "account", tags[0].Name) + labels, err := sessionPool.DescTag("account") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(labels)) + assert.Equal(t, "name", labels[0].Field) + assert.Equal(t, "string", labels[0].Type) + + tagSchema = LabelSchema{ + Name: "account", + Fields: []LabelFieldSchema{ + { + Field: "name", + Type: "string", + Nullable: false, + }, + { + Field: "email", + Type: "string", + Nullable: true, + }, + { + Field: "phone", + Type: "int64", + Nullable: true, + }, + }, + } + _, err = schemaManager.ApplyTag(tagSchema) + if err != nil { + t.Fatal(err) + } + tags, err = sessionPool.ShowTags() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(tags)) + assert.Equal(t, "account", tags[0].Name) + labels, err = sessionPool.DescTag("account") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 3, len(labels)) + assert.Equal(t, "name", labels[0].Field) + assert.Equal(t, "string", labels[0].Type) + assert.Equal(t, "email", labels[1].Field) + assert.Equal(t, "string", labels[1].Type) + assert.Equal(t, "phone", labels[2].Field) + assert.Equal(t, "int64", labels[2].Type) + tagSchema = LabelSchema{ + Name: "account", + Fields: []LabelFieldSchema{ + { + Field: "name", + Type: "string", + Nullable: false, + }, + { + Field: "phone", + Type: "int64", + Nullable: true, + }, + }, + } + _, err = schemaManager.ApplyTag(tagSchema) + if err != nil { + t.Fatal(err) + } + tags, err = sessionPool.ShowTags() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 1, len(tags)) + assert.Equal(t, "account", tags[0].Name) + labels, err = sessionPool.DescTag("account") + if err != nil { + t.Fatal(err) + } + assert.Equal(t, 2, len(labels)) + assert.Equal(t, "name", labels[0].Field) + assert.Equal(t, "string", labels[0].Type) + assert.Equal(t, "phone", labels[1].Field) + assert.Equal(t, "int64", labels[1].Type) +} diff --git a/session_pool.go b/session_pool.go index 2e192826..43f1aef9 100644 --- a/session_pool.go +++ b/session_pool.go @@ -12,7 +12,6 @@ import ( "container/list" "fmt" "strconv" - "strings" "sync" "time" @@ -321,81 +320,6 @@ func (pool *SessionPool) CreateTag(tag LabelSchema) (*ResultSet, error) { return rs, nil } -// ApplyTag applies the given tag to the graph. -// 1. If the tag does not exist, it will be created. -// 2. If the tag exists, it will be checked if the fields are the same. -// 2.1 If not, the new fields will be added. -// 2.2 If the field type is different, it will return an error. -// 2.3 If a field exists in the graph but not in the given tag, -// it will be removed. -// -// Notice: -// We won't change the field type because it has -// unexpected behavior for the data. -func (pool *SessionPool) ApplyTag(tag LabelSchema) (*ResultSet, error) { - // 1. Check if the tag exists - _, err := pool.DescTag(tag.Name) - if err != nil { - // 2. If the tag does not exist, create it - if strings.Contains(err.Error(), ErrorTagNotFound) { - return pool.CreateTag(tag) - } - return nil, err - } - - // 3. If the tag exists, check if the fields are the same - fields, err := pool.DescTag(tag.Name) - if err != nil { - return nil, err - } - - // 4. Add new fields - for _, expected := range tag.Fields { - found := false - for _, actual := range fields { - if expected.Field == actual.Field { - found = true - // 4.1 Check if the field type is different - if expected.Type != actual.Type { - return nil, fmt.Errorf("field type is different. "+ - "Expected: %s, Actual: %s", expected.Type, actual.Type) - } - break - } - } - if !found { - // 4.2 Add the not exists field - q := expected.BuildAddTagFieldQL(tag.Name) - _, err := pool.ExecuteAndCheck(q) - if err != nil { - return nil, err - } - } - } - - // 5. Remove the not expected field - for _, actual := range fields { - redundant := true - for _, expected := range tag.Fields { - if expected.Field == actual.Field { - redundant = false - break - } - } - if redundant { - // 5.1 Remove the not expected field - q := actual.BuildDropTagFieldQL(tag.Name) - _, err := pool.ExecuteAndCheck(q) - if err != nil { - return nil, err - } - } - - } - - return nil, nil -} - func (pool *SessionPool) DescTag(tagName string) ([]Label, error) { q := fmt.Sprintf("DESC TAG %s;", tagName) rs, err := pool.ExecuteAndCheck(q) diff --git a/session_pool_test.go b/session_pool_test.go index 53b59d50..1113ca2f 100644 --- a/session_pool_test.go +++ b/session_pool_test.go @@ -465,149 +465,6 @@ func TestSessionPoolApplySchema(t *testing.T) { assert.Equal(t, "string", labels[0].Type, "field type should be string") } -func TestSessionPoolApplyTag(t *testing.T) { - spaceName := "test_space_apply_tag" - err := prepareSpace(spaceName) - if err != nil { - t.Fatal(err) - } - defer dropSpace(spaceName) - - hostAddress := HostAddress{Host: address, Port: port} - config, err := NewSessionPoolConf( - "root", - "nebula", - []HostAddress{hostAddress}, - spaceName) - if err != nil { - t.Errorf("failed to create session pool config, %s", err.Error()) - } - - // allow only one session in the pool so it is easier to test - config.maxSize = 1 - - // create session pool - sessionPool, err := NewSessionPool(*config, DefaultLogger{}) - if err != nil { - t.Fatal(err) - } - defer sessionPool.Close() - - spaces, err := sessionPool.ShowSpaces() - if err != nil { - t.Fatal(err) - } - assert.LessOrEqual(t, 1, len(spaces)) - var spaceNames []string - for _, space := range spaces { - spaceNames = append(spaceNames, space.Name) - } - assert.Contains(t, spaceNames, spaceName) - - tagSchema := LabelSchema{ - Name: "account", - Fields: []LabelFieldSchema{ - { - Field: "name", - Nullable: false, - }, - }, - } - _, err = sessionPool.ApplyTag(tagSchema) - if err != nil { - t.Fatal(err) - } - tags, err := sessionPool.ShowTags() - if err != nil { - t.Fatal(err) - } - assert.Equal(t, 1, len(tags)) - assert.Equal(t, "account", tags[0].Name) - labels, err := sessionPool.DescTag("account") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, 1, len(labels)) - assert.Equal(t, "name", labels[0].Field) - assert.Equal(t, "string", labels[0].Type) - - tagSchema = LabelSchema{ - Name: "account", - Fields: []LabelFieldSchema{ - { - Field: "name", - Type: "string", - Nullable: false, - }, - { - Field: "email", - Type: "string", - Nullable: true, - }, - { - Field: "phone", - Type: "int64", - Nullable: true, - }, - }, - } - _, err = sessionPool.ApplyTag(tagSchema) - if err != nil { - t.Fatal(err) - } - tags, err = sessionPool.ShowTags() - if err != nil { - t.Fatal(err) - } - assert.Equal(t, 1, len(tags)) - assert.Equal(t, "account", tags[0].Name) - labels, err = sessionPool.DescTag("account") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, 3, len(labels)) - assert.Equal(t, "name", labels[0].Field) - assert.Equal(t, "string", labels[0].Type) - assert.Equal(t, "email", labels[1].Field) - assert.Equal(t, "string", labels[1].Type) - assert.Equal(t, "phone", labels[2].Field) - assert.Equal(t, "int64", labels[2].Type) - tagSchema = LabelSchema{ - Name: "account", - Fields: []LabelFieldSchema{ - { - Field: "name", - Type: "string", - Nullable: false, - }, - { - Field: "phone", - Type: "int64", - Nullable: true, - }, - }, - } - _, err = sessionPool.ApplyTag(tagSchema) - if err != nil { - t.Fatal(err) - } - tags, err = sessionPool.ShowTags() - if err != nil { - t.Fatal(err) - } - assert.Equal(t, 1, len(tags)) - assert.Equal(t, "account", tags[0].Name) - labels, err = sessionPool.DescTag("account") - if err != nil { - t.Fatal(err) - } - assert.Equal(t, 2, len(labels)) - assert.Equal(t, "name", labels[0].Field) - assert.Equal(t, "string", labels[0].Type) - assert.Equal(t, "phone", labels[1].Field) - assert.Equal(t, "int64", labels[1].Type) -} - func TestIdleSessionCleaner(t *testing.T) { err := prepareSpace("client_test") if err != nil { From 8635e322ed3248c555b79ad8c8915f049f128cd1 Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Tue, 20 Feb 2024 17:37:01 +0800 Subject: [PATCH 08/13] naming --- schema_manager_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/schema_manager_test.go b/schema_manager_test.go index 5b04caf6..358683cf 100644 --- a/schema_manager_test.go +++ b/schema_manager_test.go @@ -14,7 +14,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestSessionPoolApplyTag(t *testing.T) { +func TestSchemaManagerApplyTag(t *testing.T) { spaceName := "test_space_apply_tag" err := prepareSpace(spaceName) if err != nil { From ea09335d5675145b4b5792928c779dd52fb42b2c Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Wed, 21 Feb 2024 11:08:28 +0800 Subject: [PATCH 09/13] fix --- schema_manager.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/schema_manager.go b/schema_manager.go index a7c45ef9..8e107648 100644 --- a/schema_manager.go +++ b/schema_manager.go @@ -34,7 +34,7 @@ func NewSchemaManager(pool *SessionPool) *SchemaManager { // unexpected behavior for the data. func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { // 1. Check if the tag exists - _, err := mgr.pool.DescTag(tag.Name) + fields, err := mgr.pool.DescTag(tag.Name) if err != nil { // 2. If the tag does not exist, create it if strings.Contains(err.Error(), ErrorTagNotFound) { @@ -44,7 +44,6 @@ func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { } // 3. If the tag exists, check if the fields are the same - fields, err := mgr.pool.DescTag(tag.Name) if err != nil { return nil, err } From 34775325bb0896364e1f34457d2ba58cc73b1c63 Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Wed, 21 Feb 2024 11:18:11 +0800 Subject: [PATCH 10/13] enhance the create new fields logic --- schema_manager.go | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/schema_manager.go b/schema_manager.go index 8e107648..16ff2670 100644 --- a/schema_manager.go +++ b/schema_manager.go @@ -49,12 +49,14 @@ func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { } // 4. Add new fields + // 4.1 Prepare the new fields + addFieldQLs := []string{} for _, expected := range tag.Fields { found := false for _, actual := range fields { if expected.Field == actual.Field { found = true - // 4.1 Check if the field type is different + // 4.2 Check if the field type is different if expected.Type != actual.Type { return nil, fmt.Errorf("field type is different. "+ "Expected: %s, Actual: %s", expected.Type, actual.Type) @@ -63,14 +65,17 @@ func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { } } if !found { - // 4.2 Add the not exists field + // 4.3 Add the not exists field QL q := expected.BuildAddTagFieldQL(tag.Name) - _, err := mgr.pool.ExecuteAndCheck(q) - if err != nil { - return nil, err - } + addFieldQLs = append(addFieldQLs, q) } } + // 4.4 Execute the add field QLs + queries := strings.Join(addFieldQLs, "") + _, err = mgr.pool.ExecuteAndCheck(queries) + if err != nil { + return nil, err + } // 5. Remove the not expected field for _, actual := range fields { From 72e08122951be35915bfe485c2c72c7e555b933f Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Wed, 21 Feb 2024 11:25:37 +0800 Subject: [PATCH 11/13] fix --- schema_manager.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/schema_manager.go b/schema_manager.go index 16ff2670..534ecea2 100644 --- a/schema_manager.go +++ b/schema_manager.go @@ -70,11 +70,13 @@ func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { addFieldQLs = append(addFieldQLs, q) } } - // 4.4 Execute the add field QLs - queries := strings.Join(addFieldQLs, "") - _, err = mgr.pool.ExecuteAndCheck(queries) - if err != nil { - return nil, err + // 4.4 Execute the add field QLs if needed + if len(addFieldQLs) > 0 { + queries := strings.Join(addFieldQLs, "") + _, err = mgr.pool.ExecuteAndCheck(queries) + if err != nil { + return nil, err + } } // 5. Remove the not expected field From 4bff3a4a76cace2f0d9724fe0b59feefb840d55e Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Wed, 21 Feb 2024 11:26:16 +0800 Subject: [PATCH 12/13] chore --- schema_manager.go | 1 - 1 file changed, 1 deletion(-) diff --git a/schema_manager.go b/schema_manager.go index 534ecea2..30d12081 100644 --- a/schema_manager.go +++ b/schema_manager.go @@ -96,7 +96,6 @@ func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { return nil, err } } - } return nil, nil From 747dec292fe30048ba27abd1cefd0bc37b07e6a2 Mon Sep 17 00:00:00 2001 From: Xin Hao Date: Wed, 21 Feb 2024 13:59:27 +0800 Subject: [PATCH 13/13] fix --- schema_manager.go | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/schema_manager.go b/schema_manager.go index 30d12081..43384ebd 100644 --- a/schema_manager.go +++ b/schema_manager.go @@ -80,6 +80,8 @@ func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { } // 5. Remove the not expected field + // 5.1 Prepare the not expected fields + dropFieldQLs := []string{} for _, actual := range fields { redundant := true for _, expected := range tag.Fields { @@ -89,12 +91,17 @@ func (mgr *SchemaManager) ApplyTag(tag LabelSchema) (*ResultSet, error) { } } if redundant { - // 5.1 Remove the not expected field + // 5.2 Remove the not expected field q := actual.BuildDropTagFieldQL(tag.Name) - _, err := mgr.pool.ExecuteAndCheck(q) - if err != nil { - return nil, err - } + dropFieldQLs = append(dropFieldQLs, q) + } + } + // 5.3 Execute the drop field QLs if needed + if len(dropFieldQLs) > 0 { + queries := strings.Join(dropFieldQLs, "") + _, err := mgr.pool.ExecuteAndCheck(queries) + if err != nil { + return nil, err } }