Skip to content

Commit 953a5cf

Browse files
committed
box: added logic for working with Tarantool schema
Implemented the `box.Schema()` method that returns a `Schema` object for schema-related operations. Co-authored-by: maksim.konovalov <[email protected]> Closes #TNTP-3331
1 parent 66ef721 commit 953a5cf

16 files changed

+1739
-82
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1010

1111
### Added
1212

13+
- Implemented all box.schema.user operations requests and sugar interface (#426).
14+
- Implemented box.session.su request and sugar interface only for current session granting (#426).
15+
1316
### Changed
1417

1518
### Fixed

box/box.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,23 @@ type Box struct {
1212

1313
// New returns a new instance of the box structure, which implements the Box interface.
1414
func New(conn tarantool.Doer) *Box {
15+
if conn == nil {
16+
// Check if the provided Tarantool connection is nil, and if it is, panic with an error
17+
// message. panic early helps to catch and fix nil pointer issues in the code.
18+
panic("tarantool connection cannot be nil")
19+
}
20+
1521
return &Box{
1622
conn: conn, // Assigns the provided Tarantool connection.
1723
}
1824
}
1925

26+
// Schema returns a new Schema instance, providing access to schema-related operations.
27+
// It uses the connection from the Box instance to communicate with Tarantool.
28+
func (b *Box) Schema() *Schema {
29+
return newSchema(b.conn)
30+
}
31+
2032
// Info retrieves the current information of the Tarantool instance.
2133
// It calls the "box.info" function and parses the result into the Info structure.
2234
func (b *Box) Info() (Info, error) {

box/box_test.go

Lines changed: 77 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,88 @@
11
package box_test
22

33
import (
4+
"context"
5+
"errors"
46
"testing"
57

8+
"github.com/stretchr/testify/assert"
69
"github.com/stretchr/testify/require"
710
"github.com/tarantool/go-tarantool/v2/box"
11+
"github.com/tarantool/go-tarantool/v2/test_helpers"
812
)
913

1014
func TestNew(t *testing.T) {
11-
// Create a box instance with a nil connection. This should lead to a panic later.
12-
b := box.New(nil)
13-
14-
// Ensure the box instance is not nil (which it shouldn't be), but this is not meaningful
15-
// since we will panic when we call the Info method with the nil connection.
16-
require.NotNil(t, b)
17-
18-
// We expect a panic because we are passing a nil connection (nil Doer) to the By function.
19-
// The library does not control this zone, and the nil connection would cause a runtime error
20-
// when we attempt to call methods (like Info) on it.
21-
// This test ensures that such an invalid state is correctly handled by causing a panic,
22-
// as it's outside the library's responsibility.
23-
require.Panics(t, func() {
24-
25-
// Calling Info on a box with a nil connection will result in a panic, since the underlying
26-
// connection (Doer) cannot perform the requested action (it's nil).
27-
_, _ = b.Info()
28-
})
15+
t.Parallel()
16+
17+
// Create a box instance with a nil connection. This should lead to a panic.
18+
require.Panics(t, func() { box.New(nil) })
19+
}
20+
21+
func TestMocked_BoxInfo(t *testing.T) {
22+
t.Parallel()
23+
24+
data := []interface{}{
25+
map[string]interface{}{
26+
"version": "1.0.0",
27+
"id": nil,
28+
"ro": false,
29+
"uuid": "uuid",
30+
"pid": 456,
31+
"status": "status",
32+
"lsn": 123,
33+
"replication": nil,
34+
},
35+
}
36+
mock := test_helpers.NewMockDoer(t,
37+
test_helpers.NewMockResponse(t, data),
38+
)
39+
b := box.New(&mock)
40+
41+
info, err := b.Info()
42+
require.NoError(t, err)
43+
44+
assert.Equal(t, "1.0.0", info.Version)
45+
assert.Equal(t, 456, info.PID)
46+
}
47+
48+
func TestMocked_BoxSchemaUserInfo(t *testing.T) {
49+
t.Parallel()
50+
51+
data := []interface{}{
52+
[]interface{}{
53+
[]interface{}{"read,write,execute", "universe", ""},
54+
},
55+
}
56+
mock := test_helpers.NewMockDoer(t,
57+
test_helpers.NewMockResponse(t, data),
58+
)
59+
b := box.New(&mock)
60+
61+
privs, err := b.Schema().User().Info(context.Background(), "username")
62+
require.NoError(t, err)
63+
64+
assert.Equal(t, []box.Privilege{
65+
{
66+
Permissions: []box.Permission{
67+
box.PermissionRead,
68+
box.PermissionWrite,
69+
box.PermissionExecute,
70+
},
71+
Type: box.PrivilegeUniverse,
72+
Name: "",
73+
},
74+
}, privs)
75+
}
76+
77+
func TestMocked_BoxSessionSu(t *testing.T) {
78+
t.Parallel()
79+
80+
mock := test_helpers.NewMockDoer(t,
81+
test_helpers.NewMockResponse(t, []interface{}{}),
82+
errors.New("user not found or supplied credentials are invalid"),
83+
)
84+
b := box.New(&mock)
85+
86+
err := b.Session().Su(context.Background(), "admin")
87+
require.NoError(t, err)
2988
}

box/example_test.go

Lines changed: 186 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
// Terminal 2:
88
// $ go test -v example_test.go
9+
910
package box_test
1011

1112
import (
@@ -18,7 +19,7 @@ import (
1819
"github.com/tarantool/go-tarantool/v2/box"
1920
)
2021

21-
func Example() {
22+
func ExampleBox_Info() {
2223
dialer := tarantool.NetDialer{
2324
Address: "127.0.0.1:3013",
2425
User: "test",
@@ -55,6 +56,188 @@ func Example() {
5556
log.Fatalf("Box info uuids are not equal")
5657
}
5758

58-
fmt.Printf("Box info uuids are equal")
59-
fmt.Printf("Current box info: %+v\n", resp.Info)
59+
fmt.Printf("Box info uuids are equal\n")
60+
fmt.Printf("Current box ro: %+v", resp.Info.RO)
61+
// Output:
62+
// Box info uuids are equal
63+
// Current box ro: false
64+
}
65+
66+
func ExampleSchemaUser_Exists() {
67+
dialer := tarantool.NetDialer{
68+
Address: "127.0.0.1:3013",
69+
User: "test",
70+
Password: "test",
71+
}
72+
ctx := context.Background()
73+
74+
client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
75+
76+
if err != nil {
77+
log.Fatalf("Failed to connect: %s", err)
78+
}
79+
80+
// You can use UserExistsRequest type and call it directly.
81+
fut := client.Do(box.NewUserExistsRequest("user"))
82+
83+
resp := &box.UserExistsResponse{}
84+
85+
err = fut.GetTyped(resp)
86+
if err != nil {
87+
log.Fatalf("Failed get box schema user exists with error: %s", err)
88+
}
89+
90+
// Or use simple User implementation.
91+
b := box.New(client)
92+
exists, err := b.Schema().User().Exists(ctx, "user")
93+
if err != nil {
94+
log.Fatalf("Failed get box schema user exists with error: %s", err)
95+
}
96+
97+
if exists != resp.Exists {
98+
log.Fatalf("Box schema users exists are not equal")
99+
}
100+
101+
fmt.Printf("Box schema users exists are equal\n")
102+
fmt.Printf("Current exists state: %+v", exists)
103+
// Output:
104+
// Box schema users exists are equal
105+
// Current exists state: false
106+
}
107+
108+
func ExampleSchemaUser_Create() {
109+
// Connect to Tarantool.
110+
dialer := tarantool.NetDialer{
111+
Address: "127.0.0.1:3013",
112+
User: "test",
113+
Password: "test",
114+
}
115+
ctx := context.Background()
116+
117+
client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
118+
if err != nil {
119+
log.Fatalf("Failed to connect: %s", err)
120+
}
121+
122+
// Create SchemaUser.
123+
schemaUser := box.New(client).Schema().User()
124+
125+
// Create a new user.
126+
username := "new_user"
127+
options := box.UserCreateOptions{
128+
IfNotExists: true,
129+
Password: "secure_password",
130+
}
131+
err = schemaUser.Create(ctx, username, options)
132+
if err != nil {
133+
log.Fatalf("Failed to create user: %s", err)
134+
}
135+
136+
fmt.Printf("User '%s' created successfully\n", username)
137+
// Output:
138+
// User 'new_user' created successfully
139+
}
140+
141+
func ExampleSchemaUser_Drop() {
142+
// Connect to Tarantool.
143+
dialer := tarantool.NetDialer{
144+
Address: "127.0.0.1:3013",
145+
User: "test",
146+
Password: "test",
147+
}
148+
ctx := context.Background()
149+
150+
client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
151+
if err != nil {
152+
log.Fatalf("Failed to connect: %s", err)
153+
}
154+
155+
// Create SchemaUser.
156+
schemaUser := box.New(client).Schema().User()
157+
158+
// Drop an existing user.
159+
username := "new_user"
160+
options := box.UserDropOptions{
161+
IfExists: true,
162+
}
163+
err = schemaUser.Drop(ctx, username, options)
164+
if err != nil {
165+
log.Fatalf("Failed to drop user: %s", err)
166+
}
167+
168+
exists, err := schemaUser.Exists(ctx, username)
169+
if err != nil {
170+
log.Fatalf("Failed to get user exists: %s", err)
171+
}
172+
173+
fmt.Printf("User '%s' dropped successfully\n", username)
174+
fmt.Printf("User '%s' exists status: %v \n", username, exists)
175+
// Output:
176+
// User 'new_user' dropped successfully
177+
// User 'new_user' exists status: false
178+
}
179+
180+
func ExampleSchemaUser_Password() {
181+
// Connect to Tarantool.
182+
dialer := tarantool.NetDialer{
183+
Address: "127.0.0.1:3013",
184+
User: "test",
185+
Password: "test",
186+
}
187+
ctx := context.Background()
188+
189+
client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
190+
if err != nil {
191+
log.Fatalf("Failed to connect: %s", err)
192+
}
193+
194+
// Create SchemaUser.
195+
schemaUser := box.New(client).Schema().User()
196+
197+
// Get the password hash.
198+
password := "my-password"
199+
passwordHash, err := schemaUser.Password(ctx, password)
200+
if err != nil {
201+
log.Fatalf("Failed to get password hash: %s", err)
202+
}
203+
204+
fmt.Printf("Password '%s' hash: %s", password, passwordHash)
205+
// Output:
206+
// Password 'my-password' hash: 3PHNAQGFWFo0KRfToxNgDXHj2i8=
207+
}
208+
209+
func ExampleSchemaUser_Info() {
210+
// Connect to Tarantool.
211+
dialer := tarantool.NetDialer{
212+
Address: "127.0.0.1:3013",
213+
User: "test",
214+
Password: "test",
215+
}
216+
ctx := context.Background()
217+
218+
client, err := tarantool.Connect(ctx, dialer, tarantool.Opts{})
219+
if err != nil {
220+
log.Fatalf("Failed to connect: %s", err)
221+
}
222+
223+
// Create SchemaUser.
224+
schemaUser := box.New(client).Schema().User()
225+
226+
info, err := schemaUser.Info(ctx, "test")
227+
if err != nil {
228+
log.Fatalf("Failed to get password hash: %s", err)
229+
}
230+
231+
hasSuper := false
232+
for _, i := range info {
233+
if i.Name == "super" && i.Type == box.PrivilegeRole {
234+
hasSuper = true
235+
}
236+
}
237+
238+
if hasSuper {
239+
fmt.Printf("User have super privileges")
240+
}
241+
// Output:
242+
// User have super privileges
60243
}

box/info.go

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -105,25 +105,20 @@ func (ir *InfoResponse) DecodeMsgpack(d *msgpack.Decoder) error {
105105
}
106106

107107
ir.Info = i
108-
109108
return nil
110109
}
111110

112111
// InfoRequest represents a request to retrieve information about the Tarantool instance.
113112
// It implements the tarantool.Request interface.
114113
type InfoRequest struct {
115-
baseRequest
116-
}
117-
118-
// Body method is used to serialize the request's body.
119-
// It is part of the tarantool.Request interface implementation.
120-
func (i InfoRequest) Body(res tarantool.SchemaResolver, enc *msgpack.Encoder) error {
121-
return i.impl.Body(res, enc)
114+
*tarantool.CallRequest // Underlying Tarantool call request.
122115
}
123116

124117
// NewInfoRequest returns a new empty info request.
125118
func NewInfoRequest() InfoRequest {
126-
req := InfoRequest{}
127-
req.impl = newCall("box.info")
128-
return req
119+
callReq := tarantool.NewCallRequest("box.info")
120+
121+
return InfoRequest{
122+
callReq,
123+
}
129124
}

0 commit comments

Comments
 (0)