Skip to content

box: added logic for working with Tarantool schema #446

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.

### Added

- Implemented all box.schema.user operations requests and sugar interface (#426).
- Implemented box.session.su request and sugar interface only for current session granting (#426).

### Changed

### Fixed
Expand Down
6 changes: 6 additions & 0 deletions box/box.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ func New(conn tarantool.Doer) *Box {
}
}

// Schema returns a new Schema instance, providing access to schema-related operations.
// It uses the connection from the Box instance to communicate with Tarantool.
func (b *Box) Schema() *Schema {
return NewSchema(b.conn)
}

// Info retrieves the current information of the Tarantool instance.
// It calls the "box.info" function and parses the result into the Info structure.
func (b *Box) Info() (Info, error) {
Expand Down
26 changes: 23 additions & 3 deletions box/box_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package box_test
package box

Copy link
Collaborator

@oleg-jukovec oleg-jukovec Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
package box
package box_test

Nit: please, test only the public API unless otherwise really necessary.

I know that the connector is currently testing by private API in some cases. It would be nice if we could fix that in the future.

But for now, we could just to avoid the wrong way and test only the public API.

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tarantool/go-tarantool/v2/box"

"github.com/tarantool/go-tarantool/v2/test_helpers"
)

func TestNew(t *testing.T) {
t.Parallel()

// Create a box instance with a nil connection. This should lead to a panic later.
b := box.New(nil)
b := New(nil)

// Ensure the box instance is not nil (which it shouldn't be), but this is not meaningful
// since we will panic when we call the Info method with the nil connection.
Expand All @@ -27,3 +31,19 @@ func TestNew(t *testing.T) {
_, _ = b.Info()
})
}

func TestNew_Schema_PassesDoer(t *testing.T) {
t.Parallel()

doer := test_helpers.NewMockDoer(t)

b := New(&doer)
schemaObject := b.Schema()
assert.Equal(t, &doer, schemaObject.conn)

userSchemaObject := b.Schema().User()
assert.Equal(t, &doer, userSchemaObject.conn)

sessionObject := b.Session()
assert.Equal(t, &doer, sessionObject.conn)
}
Comment on lines +40 to +48
Copy link
Collaborator

@oleg-jukovec oleg-jukovec Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could create a mock of the Doer interface here and make sure that the methods are called exactly from the mock instead of sharing the internal implementation. Or something like that...

189 changes: 186 additions & 3 deletions box/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//
// Terminal 2:
// $ go test -v example_test.go

package box_test

import (
Expand All @@ -18,7 +19,7 @@ import (
"github.com/tarantool/go-tarantool/v2/box"
)

func Example() {
func ExampleBox_Info() {
dialer := tarantool.NetDialer{
Address: "127.0.0.1:3013",
User: "test",
Expand Down Expand Up @@ -55,6 +56,188 @@ func Example() {
log.Fatalf("Box info uuids are not equal")
}

fmt.Printf("Box info uuids are equal")
fmt.Printf("Current box info: %+v\n", resp.Info)
fmt.Printf("Box info uuids are equal\n")
fmt.Printf("Current box ro: %+v", resp.Info.RO)
// Output:
// Box info uuids are equal
// Current box ro: false
}

func ExampleSchemaUser_Exists() {
dialer := tarantool.NetDialer{
Address: "127.0.0.1:3013",
User: "test",
Password: "test",
}
ctx := context.Background()

client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})

if err != nil {
log.Fatalf("Failed to connect: %s", err)
}

// You can use UserExistsRequest type and call it directly.
fut := client.Do(box.NewUserExistsRequest("user"))

resp := &box.UserExistsResponse{}

err = fut.GetTyped(resp)
if err != nil {
log.Fatalf("Failed get box schema user exists with error: %s", err)
}

// Or use simple User implementation.
b := box.New(client)
exists, err := b.Schema().User().Exists(ctx, "user")
if err != nil {
log.Fatalf("Failed get box schema user exists with error: %s", err)
}

if exists != resp.Exists {
log.Fatalf("Box schema users exists are not equal")
}

fmt.Printf("Box schema users exists are equal\n")
fmt.Printf("Current exists state: %+v", exists)
// Output:
// Box schema users exists are equal
// Current exists state: false
}

func ExampleSchemaUser_Create() {
// Connect to Tarantool.
dialer := tarantool.NetDialer{
Address: "127.0.0.1:3013",
User: "test",
Password: "test",
}
ctx := context.Background()

client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
if err != nil {
log.Fatalf("Failed to connect: %s", err)
}

// Create SchemaUser.
schemaUser := box.NewSchemaUser(client)

// Create a new user.
username := "new_user"
options := box.UserCreateOptions{
IfNotExists: true,
Password: "secure_password",
}
err = schemaUser.Create(ctx, username, options)
if err != nil {
log.Fatalf("Failed to create user: %s", err)
}

fmt.Printf("User '%s' created successfully\n", username)
// Output:
// User 'new_user' created successfully
}

func ExampleSchemaUser_Drop() {
// Connect to Tarantool.
dialer := tarantool.NetDialer{
Address: "127.0.0.1:3013",
User: "test",
Password: "test",
}
ctx := context.Background()

client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
if err != nil {
log.Fatalf("Failed to connect: %s", err)
}

// Create SchemaUser.
schemaUser := box.NewSchemaUser(client)

// Drop an existing user.
username := "new_user"
options := box.UserDropOptions{
IfExists: true,
}
err = schemaUser.Drop(ctx, username, options)
if err != nil {
log.Fatalf("Failed to drop user: %s", err)
}

exists, err := schemaUser.Exists(ctx, username)
if err != nil {
log.Fatalf("Failed to get user exists: %s", err)
}

fmt.Printf("User '%s' dropped successfully\n", username)
fmt.Printf("User '%s' exists status: %v \n", username, exists)
// Output:
// User 'new_user' dropped successfully
// User 'new_user' exists status: false
}

func ExampleSchemaUser_Password() {
// Connect to Tarantool.
dialer := tarantool.NetDialer{
Address: "127.0.0.1:3013",
User: "test",
Password: "test",
}
ctx := context.Background()

client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
if err != nil {
log.Fatalf("Failed to connect: %s", err)
}

// Create SchemaUser.
schemaUser := box.NewSchemaUser(client)

// Get the password hash.
password := "my-password"
passwordHash, err := schemaUser.Password(ctx, password)
if err != nil {
log.Fatalf("Failed to get password hash: %s", err)
}

fmt.Printf("Password '%s' hash: %s", password, passwordHash)
// Output:
// Password 'my-password' hash: 3PHNAQGFWFo0KRfToxNgDXHj2i8=
}

func ExampleSchemaUser_Info() {
// Connect to Tarantool.
dialer := tarantool.NetDialer{
Address: "127.0.0.1:3013",
User: "test",
Password: "test",
}
ctx := context.Background()

client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
if err != nil {
log.Fatalf("Failed to connect: %s", err)
}

// Create SchemaUser.
schemaUser := box.NewSchemaUser(client)

info, err := schemaUser.Info(ctx, "test")
if err != nil {
log.Fatalf("Failed to get password hash: %s", err)
}

hasSuper := false
for _, i := range info {
if i.Name == "super" && i.Type == box.PrivilegeRole {
hasSuper = true
}
}

if hasSuper {
fmt.Printf("User have super privileges")
}
// Output:
// User have super privileges
}
17 changes: 6 additions & 11 deletions box/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,25 +105,20 @@ func (ir *InfoResponse) DecodeMsgpack(d *msgpack.Decoder) error {
}

ir.Info = i

return nil
}

// InfoRequest represents a request to retrieve information about the Tarantool instance.
// It implements the tarantool.Request interface.
type InfoRequest struct {
baseRequest
}

// Body method is used to serialize the request's body.
// It is part of the tarantool.Request interface implementation.
func (i InfoRequest) Body(res tarantool.SchemaResolver, enc *msgpack.Encoder) error {
return i.impl.Body(res, enc)
*tarantool.CallRequest // Underlying Tarantool call request.
}

// NewInfoRequest returns a new empty info request.
func NewInfoRequest() InfoRequest {
req := InfoRequest{}
req.impl = newCall("box.info")
return req
callReq := tarantool.NewCallRequest("box.info")

return InfoRequest{
callReq,
}
}
38 changes: 0 additions & 38 deletions box/request.go

This file was deleted.

21 changes: 21 additions & 0 deletions box/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package box

import "github.com/tarantool/go-tarantool/v2"

// Schema represents the schema-related operations in Tarantool.
// It holds a connection to interact with the Tarantool instance.
type Schema struct {
conn tarantool.Doer // Connection interface for interacting with Tarantool.
}

// NewSchema creates a new Schema instance with the provided Tarantool connection.
// It initializes a Schema object that can be used for schema-related operations
// such as managing users, tables, and other schema elements in the Tarantool instance.
func NewSchema(conn tarantool.Doer) *Schema {
return &Schema{conn: conn} // Pass the connection to the Schema.
}

// User returns a new SchemaUser instance, allowing schema-related user operations.
func (s *Schema) User() *SchemaUser {
return NewSchemaUser(s.conn)
}
Loading
Loading