From 2efd18a2f0c906344fe57b89915d480926df8c42 Mon Sep 17 00:00:00 2001 From: DerekBum Date: Sat, 18 Nov 2023 17:41:38 +0300 Subject: [PATCH] api: write a connection schema getter Write a helper function to load the actual schema for the user. Previously we stored actual schema in a private `schemaResolver` field and `Schema` field was used only to get a current schema. But now because of the new function, we don't need to store the `Schema` as a different field. So `Schema` was also removed. To update the schema, used needs to use `GetSchema` + `SetSchema` in pair. `SetSchema(Schema)` replacing the `OverrideSchema(*Schema)`. `Schema` structure no longer implements `SchemaResolver` interface. `Spaces` and `SpacesById` fields of the `Schema` struct store spaces by value. `Fields` and `FieldsById` fields of the `Space` struct store fields by value. `Index` and `IndexById` fields of the `Space` struct store indexes by value. `Fields` field of the `Index` struct store `IndexField` by value. Closes #7 --- CHANGELOG.md | 5 ++++ README.md | 11 ++++++++ connection.go | 27 +++++++++++-------- example_test.go | 26 ++++++++++++++++--- schema.go | 66 +++++++++++++++++++---------------------------- tarantool_test.go | 47 +++++++++++++++++++++++---------- 6 files changed, 115 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 182161846..e585d8e37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - Support `IPROTO_FEATURE_SPACE_AND_INDEX_NAMES` for Tarantool version >= 3.0.0-alpha1 (#338). It allows to use space and index names in requests instead of their IDs. +- `GetSchema` function to get the actual schema (#7) ### Changed @@ -51,6 +52,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. instead of `crud.OptUint` (#342) - Change all `Upsert` and `Update` requests to accept `*tarantool.Operations` as `ops` parameters instead of `interface{}` (#348) +- Change `OverrideSchema(*Schema)` to `SetSchema(Schema)` (#7) +- Change values, stored by pointers in the `Schema`, `Space`, `Index` structs, + to be stored by their values (#7) ### Deprecated @@ -70,6 +74,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release. - UUID_extId (#158) - IPROTO constants (#158) - Code() method from the Request interface (#158) +- `Schema` field from the `Connection` struct (#7) ### Fixed diff --git a/README.md b/README.md index a0a470a86..6a7a51f0c 100644 --- a/README.md +++ b/README.md @@ -248,6 +248,13 @@ now does not attempt to reconnect and tries to establish a connection only once. Function might be canceled via context. Context accepted as first argument, and user may cancel it in process. +#### Connection schema + +* Removed `Schema` field from the `Connection` struct. Instead, new +`GetSchema(Connector)` function was added to get the actual connection +schema on demand. +* `OverrideSchema(*Schema)` method replaced with the `SetSchema(Schema)`. + #### Protocol changes * `iproto.Feature` type used instead of `ProtocolFeature`. @@ -260,6 +267,10 @@ and user may cancel it in process. interface to get information if the usage of space and index names in requests is supported. * `Schema` structure no longer implements `SchemaResolver` interface. +* `Spaces` and `SpacesById` fields of the `Schema` struct store spaces by value. +* `Fields` and `FieldsById` fields of the `Space` struct store fields by value. +`Index` and `IndexById` fields of the `Space` struct store indexes by value. +* `Fields` field of the `Index` struct store `IndexField` by value. ## Contributing diff --git a/connection.go b/connection.go index 217c153dd..e215263ef 100644 --- a/connection.go +++ b/connection.go @@ -160,8 +160,6 @@ type Connection struct { c Conn mutex sync.Mutex cond *sync.Cond - // Schema contains schema loaded on connection. - Schema *Schema // schemaResolver contains a SchemaResolver implementation. schemaResolver SchemaResolver // requestId contains the last request ID for requests with nil context. @@ -436,12 +434,14 @@ func Connect(ctx context.Context, addr string, opts Opts) (conn *Connection, err // TODO: reload schema after reconnect. if !conn.opts.SkipSchema { - if err = conn.loadSchema(); err != nil { + schema, err := GetSchema(conn) + if err != nil { conn.mutex.Lock() defer conn.mutex.Unlock() conn.closeConnection(err, true) return nil, err } + conn.SetSchema(schema) } return conn, err @@ -1302,15 +1302,20 @@ func (conn *Connection) ConfiguredTimeout() time.Duration { return conn.opts.Timeout } -// OverrideSchema sets Schema for the connection. -func (conn *Connection) OverrideSchema(s *Schema) { - if s != nil { - conn.mutex.Lock() - defer conn.mutex.Unlock() - conn.lockShards() - defer conn.unlockShards() +// SetSchema sets Schema for the connection. +func (conn *Connection) SetSchema(s Schema) { + spaceAndIndexNamesSupported := + isFeatureInSlice(iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES, + conn.serverProtocolInfo.Features) - conn.Schema = s + conn.mutex.Lock() + defer conn.mutex.Unlock() + conn.lockShards() + defer conn.unlockShards() + + conn.schemaResolver = &loadedSchemaResolver{ + Schema: s, + SpaceAndIndexNamesSupported: spaceAndIndexNamesSupported, } } diff --git a/example_test.go b/example_test.go index 7e689bcf3..8e9cbf3a7 100644 --- a/example_test.go +++ b/example_test.go @@ -1063,7 +1063,10 @@ func ExampleSchema() { conn := exampleConnect(opts) defer conn.Close() - schema := conn.Schema + schema, err := tarantool.GetSchema(conn) + if err != nil { + fmt.Printf("unexpected error: %s\n", err.Error()) + } if schema.SpacesById == nil { fmt.Println("schema.SpacesById is nil") } @@ -1080,13 +1083,30 @@ func ExampleSchema() { // Space 2 ID 616 schematest } +// Example demonstrates how to update the connection schema. +func ExampleConnection_SetSchema() { + conn := exampleConnect(opts) + defer conn.Close() + + // Get the actual schema. + schema, err := tarantool.GetSchema(conn) + if err != nil { + fmt.Printf("unexpected error: %s\n", err.Error()) + } + // Update the current schema to match the actual one. + conn.SetSchema(schema) +} + // Example demonstrates how to retrieve information with space schema. func ExampleSpace() { conn := exampleConnect(opts) defer conn.Close() // Save Schema to a local variable to avoid races - schema := conn.Schema + schema, err := tarantool.GetSchema(conn) + if err != nil { + fmt.Printf("unexpected error: %s\n", err.Error()) + } if schema.SpacesById == nil { fmt.Println("schema.SpacesById is nil") } @@ -1120,7 +1140,7 @@ func ExampleSpace() { // Space 1 ID 617 test memtx // Space 1 ID 0 false // Index 0 primary - // &{0 unsigned} &{2 string} + // {0 unsigned} {2 string} // SpaceField 1 name0 unsigned // SpaceField 2 name3 unsigned } diff --git a/schema.go b/schema.go index f0be7a162..2684effed 100644 --- a/schema.go +++ b/schema.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" - "github.com/tarantool/go-iproto" "github.com/vmihailenco/msgpack/v5" "github.com/vmihailenco/msgpack/v5/msgpcode" ) @@ -58,9 +57,9 @@ type SchemaResolver interface { type Schema struct { Version uint // Spaces is map from space names to spaces. - Spaces map[string]*Space + Spaces map[string]Space // SpacesById is map from space numbers to spaces. - SpacesById map[uint32]*Space + SpacesById map[uint32]Space } // Space contains information about Tarantool's space. @@ -72,12 +71,12 @@ type Space struct { Temporary bool // Is this space temporary? // Field configuration is not mandatory and not checked by Tarantool. FieldsCount uint32 - Fields map[string]*Field - FieldsById map[uint32]*Field + Fields map[string]Field + FieldsById map[uint32]Field // Indexes is map from index names to indexes. - Indexes map[string]*Index + Indexes map[string]Index // IndexesById is map from index numbers to indexes. - IndexesById map[uint32]*Index + IndexesById map[uint32]Index } func (space *Space) DecodeMsgpack(d *msgpack.Decoder) error { @@ -135,17 +134,17 @@ func (space *Space) DecodeMsgpack(d *msgpack.Decoder) error { return errors.New("unexpected schema format (space flags)") } } - space.FieldsById = make(map[uint32]*Field) - space.Fields = make(map[string]*Field) - space.IndexesById = make(map[uint32]*Index) - space.Indexes = make(map[string]*Index) + space.FieldsById = make(map[uint32]Field) + space.Fields = make(map[string]Field) + space.IndexesById = make(map[uint32]Index) + space.Indexes = make(map[string]Index) if arrayLen >= vspaceSpFormatFieldNum { fieldCount, err := d.DecodeArrayLen() if err != nil { return err } for i := 0; i < fieldCount; i++ { - field := &Field{} + field := Field{} if err := field.DecodeMsgpack(d); err != nil { return err } @@ -206,7 +205,7 @@ type Index struct { Name string Type string Unique bool - Fields []*IndexField + Fields []IndexField } func (index *Index) DecodeMsgpack(d *msgpack.Decoder) error { @@ -261,9 +260,9 @@ func (index *Index) DecodeMsgpack(d *msgpack.Decoder) error { if err != nil { return err } - index.Fields = make([]*IndexField, fieldCount) + index.Fields = make([]IndexField, fieldCount) for i := 0; i < int(fieldCount); i++ { - index.Fields[i] = new(IndexField) + index.Fields[i] = IndexField{} if index.Fields[i].Id, err = d.DecodeUint32(); err != nil { return err } @@ -340,16 +339,17 @@ func (indexField *IndexField) DecodeMsgpack(d *msgpack.Decoder) error { return errors.New("unexpected schema format (index fields)") } -func (conn *Connection) loadSchema() (err error) { - schema := new(Schema) - schema.SpacesById = make(map[uint32]*Space) - schema.Spaces = make(map[string]*Space) +// GetSchema returns the actual schema for the connection. +func GetSchema(conn Connector) (Schema, error) { + schema := Schema{} + schema.SpacesById = make(map[uint32]Space) + schema.Spaces = make(map[string]Space) // Reload spaces. - var spaces []*Space - err = conn.SelectTyped(vspaceSpId, 0, 0, maxSchemas, IterAll, []interface{}{}, &spaces) + var spaces []Space + err := conn.SelectTyped(vspaceSpId, 0, 0, maxSchemas, IterAll, []interface{}{}, &spaces) if err != nil { - return err + return Schema{}, err } for _, space := range spaces { schema.SpacesById[space.Id] = space @@ -357,10 +357,10 @@ func (conn *Connection) loadSchema() (err error) { } // Reload indexes. - var indexes []*Index + var indexes []Index err = conn.SelectTyped(vindexSpId, 0, 0, maxSchemas, IterAll, []interface{}{}, &indexes) if err != nil { - return err + return Schema{}, err } for _, index := range indexes { spaceId := index.SpaceId @@ -368,23 +368,11 @@ func (conn *Connection) loadSchema() (err error) { schema.SpacesById[spaceId].IndexesById[index.Id] = index schema.SpacesById[spaceId].Indexes[index.Name] = index } else { - return errors.New("concurrent schema update") + return Schema{}, errors.New("concurrent schema update") } } - spaceAndIndexNamesSupported := - isFeatureInSlice(iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES, - conn.serverProtocolInfo.Features) - - conn.lockShards() - conn.Schema = schema - conn.schemaResolver = &loadedSchemaResolver{ - Schema: schema, - SpaceAndIndexNamesSupported: spaceAndIndexNamesSupported, - } - conn.unlockShards() - - return nil + return schema, nil } // resolveSpaceNumber tries to resolve a space number. @@ -462,7 +450,7 @@ func resolveIndexNumber(i interface{}) (uint32, error) { } type loadedSchemaResolver struct { - Schema *Schema + Schema Schema // SpaceAndIndexNamesSupported shows if a current Tarantool version supports // iproto.IPROTO_FEATURE_SPACE_AND_INDEX_NAMES. SpaceAndIndexNamesSupported bool diff --git a/tarantool_test.go b/tarantool_test.go index f8da2bdb5..e49c8f666 100644 --- a/tarantool_test.go +++ b/tarantool_test.go @@ -1851,6 +1851,19 @@ func TestConnection_DoWithStrangerConn(t *testing.T) { } } +func TestGetSchema(t *testing.T) { + conn := test_helpers.ConnectWithValidation(t, server, opts) + defer conn.Close() + + s, err := GetSchema(conn) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } + if s.Version != 0 || s.Spaces[spaceName].Id != spaceNo { + t.Errorf("GetSchema() returns incorrect schema") + } +} + func TestNewPreparedFromResponse(t *testing.T) { var ( ErrNilResponsePassed = fmt.Errorf("passed nil response") @@ -1882,14 +1895,17 @@ func TestSchema(t *testing.T) { defer conn.Close() // Schema - schema := conn.Schema + schema, err := GetSchema(conn) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } if schema.SpacesById == nil { t.Errorf("schema.SpacesById is nil") } if schema.Spaces == nil { t.Errorf("schema.Spaces is nil") } - var space, space2 *Space + var space, space2 Space var ok bool if space, ok = schema.SpacesById[616]; !ok { t.Errorf("space with id = 616 was not found in schema.SpacesById") @@ -1897,9 +1913,8 @@ func TestSchema(t *testing.T) { if space2, ok = schema.Spaces["schematest"]; !ok { t.Errorf("space with name 'schematest' was not found in schema.SpacesById") } - if space != space2 { - t.Errorf("space with id = 616 and space with name schematest are different") - } + assert.Equal(t, space, space2, + "space with id = 616 and space with name schematest are different") if space.Id != 616 { t.Errorf("space 616 has incorrect Id") } @@ -1929,7 +1944,7 @@ func TestSchema(t *testing.T) { t.Errorf("space.Fields len is incorrect") } - var field1, field2, field5, field1n, field5n *Field + var field1, field2, field5, field1n, field5n Field if field1, ok = space.FieldsById[1]; !ok { t.Errorf("field id = 1 was not found") } @@ -1975,7 +1990,7 @@ func TestSchema(t *testing.T) { t.Errorf("space.Indexes len is incorrect") } - var index0, index3, index0n, index3n *Index + var index0, index3, index0n, index3n Index if index0, ok = space.IndexesById[0]; !ok { t.Errorf("index id = 0 was not found") } @@ -1988,9 +2003,10 @@ func TestSchema(t *testing.T) { if index3n, ok = space.Indexes["secondary"]; !ok { t.Errorf("index name = secondary was not found") } - if index0 != index0n || index3 != index3n { - t.Errorf("index with id = 3 and index with name 'secondary' are different") - } + assert.Equal(t, index0, index0n, + "index with id = 0 and index with name 'primary' are different") + assert.Equal(t, index3, index3n, + "index with id = 3 and index with name 'secondary' are different") if index3.Id != 3 { t.Errorf("index has incorrect Id") } @@ -2012,7 +2028,7 @@ func TestSchema(t *testing.T) { ifield1 := index3.Fields[0] ifield2 := index3.Fields[1] - if ifield1 == nil || ifield2 == nil { + if ifield1 == (IndexField{}) || ifield2 == (IndexField{}) { t.Fatalf("index field is nil") } if ifield1.Id != 1 || ifield2.Id != 2 { @@ -2028,18 +2044,21 @@ func TestSchema_IsNullable(t *testing.T) { conn := test_helpers.ConnectWithValidation(t, server, opts) defer conn.Close() - schema := conn.Schema + schema, err := GetSchema(conn) + if err != nil { + t.Errorf("unexpected error: %s", err.Error()) + } if schema.Spaces == nil { t.Errorf("schema.Spaces is nil") } - var space *Space + var space Space var ok bool if space, ok = schema.SpacesById[616]; !ok { t.Errorf("space with id = 616 was not found in schema.SpacesById") } - var field, field_nullable *Field + var field, field_nullable Field for i := 0; i <= 5; i++ { name := fmt.Sprintf("name%d", i) if field, ok = space.Fields[name]; !ok {