Skip to content

Commit 3155a23

Browse files
author
maksim.konovalov
committed
Added logic for working with Tarantool schema via Box
- Implemented the `box.Schema()` method that returns a `Schema` object for schema-related operations
1 parent 7eae014 commit 3155a23

10 files changed

+409
-55
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
1010

1111
### Added
1212

13+
- Implemented box.schema.user operations requests and sugar interface.
14+
1315
### Changed
1416

17+
- Box Info method now requires context and implements CallRequest type instead custom baseRequest.
18+
1519
### Fixed
1620

1721
## [v2.2.1] - 2024-12-17

box/box.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package box
22

33
import (
4+
"context"
5+
46
"github.com/tarantool/go-tarantool/v2"
57
)
68

@@ -17,13 +19,19 @@ func New(conn tarantool.Doer) *Box {
1719
}
1820
}
1921

22+
// Schema returns a new Schema instance, providing access to schema-related operations.
23+
// It uses the connection from the Box instance to communicate with Tarantool.
24+
func (b *Box) Schema() *Schema {
25+
return NewSchema(b.conn)
26+
}
27+
2028
// Info retrieves the current information of the Tarantool instance.
2129
// It calls the "box.info" function and parses the result into the Info structure.
22-
func (b *Box) Info() (Info, error) {
30+
func (b *Box) Info(ctx context.Context) (Info, error) {
2331
var infoResp InfoResponse
2432

2533
// Call "box.info" to get instance information from Tarantool.
26-
fut := b.conn.Do(NewInfoRequest())
34+
fut := b.conn.Do(NewInfoRequest().Context(ctx))
2735

2836
// Parse the result into the Info structure.
2937
err := fut.GetTyped(&infoResp)

box/box_test.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package box_test
22

33
import (
4+
"context"
45
"testing"
56

67
"github.com/stretchr/testify/require"
@@ -24,6 +25,6 @@ func TestNew(t *testing.T) {
2425

2526
// Calling Info on a box with a nil connection will result in a panic, since the underlying
2627
// connection (Doer) cannot perform the requested action (it's nil).
27-
_, _ = b.Info()
28+
_, _ = b.Info(context.TODO())
2829
})
2930
}

box/example_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func Example() {
4646

4747
b := box.New(client)
4848

49-
info, err := b.Info()
49+
info, err := b.Info(ctx)
5050
if err != nil {
5151
log.Fatalf("Failed get box info: %s", err)
5252
}

box/info.go

+6-10
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,14 @@ func (ir *InfoResponse) DecodeMsgpack(d *msgpack.Decoder) error {
5959
// InfoRequest represents a request to retrieve information about the Tarantool instance.
6060
// It implements the tarantool.Request interface.
6161
type InfoRequest struct {
62-
baseRequest
63-
}
64-
65-
// Body method is used to serialize the request's body.
66-
// It is part of the tarantool.Request interface implementation.
67-
func (i InfoRequest) Body(res tarantool.SchemaResolver, enc *msgpack.Encoder) error {
68-
return i.impl.Body(res, enc)
62+
*tarantool.CallRequest // Underlying Tarantool call request.
6963
}
7064

7165
// NewInfoRequest returns a new empty info request.
7266
func NewInfoRequest() InfoRequest {
73-
req := InfoRequest{}
74-
req.impl = newCall("box.info")
75-
return req
67+
callReq := tarantool.NewCallRequest("box.info")
68+
69+
return InfoRequest{
70+
callReq,
71+
}
7672
}

box/request.go

-38
This file was deleted.

box/schema.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package box
2+
3+
import "github.com/tarantool/go-tarantool/v2"
4+
5+
// Schema represents the schema-related operations in Tarantool.
6+
// It holds a connection to interact with the Tarantool instance.
7+
type Schema struct {
8+
conn tarantool.Doer // Connection interface for interacting with Tarantool.
9+
}
10+
11+
// NewSchema creates a new Schema instance with the provided Tarantool connection.
12+
// It initializes a Schema object that can be used for schema-related operations
13+
// such as managing users, tables, and other schema elements in the Tarantool instance.
14+
func NewSchema(conn tarantool.Doer) *Schema {
15+
return &Schema{conn: conn} // Pass the connection to the Schema.
16+
}
17+
18+
// User returns a new SchemaUser instance, allowing schema-related user operations.
19+
func (s *Schema) User() *SchemaUser {
20+
return NewSchemaUser(s.conn)
21+
}

box/schema_user.go

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
package box
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/tarantool/go-tarantool/v2"
8+
"github.com/vmihailenco/msgpack/v5"
9+
)
10+
11+
// SchemaUser provides methods to interact with schema-related user operations in Tarantool.
12+
type SchemaUser struct {
13+
conn tarantool.Doer // Connection interface for interacting with Tarantool.
14+
}
15+
16+
// NewSchemaUser creates a new SchemaUser instance with the provided Tarantool connection.
17+
// It initializes a SchemaUser object, which provides methods to perform user-related
18+
// schema operations (such as creating, modifying, or deleting users) in the Tarantool instance.
19+
func NewSchemaUser(conn tarantool.Doer) *SchemaUser {
20+
return &SchemaUser{conn: conn}
21+
}
22+
23+
// UserExistsRequest represents a request to check if a user exists in Tarantool.
24+
type UserExistsRequest struct {
25+
*tarantool.CallRequest // Underlying Tarantool call request.
26+
}
27+
28+
// UserExistsResponse represents the response to a user existence check.
29+
type UserExistsResponse struct {
30+
Exists bool // True if the user exists, false otherwise.
31+
}
32+
33+
// DecodeMsgpack decodes the response from a Msgpack-encoded byte slice.
34+
func (uer *UserExistsResponse) DecodeMsgpack(d *msgpack.Decoder) error {
35+
arrayLen, err := d.DecodeArrayLen()
36+
if err != nil {
37+
return err
38+
}
39+
40+
// Ensure that the response array contains exactly 1 element (the "Exists" field).
41+
if arrayLen != 1 {
42+
return fmt.Errorf("protocol violation; expected 1 array entry, got %d", arrayLen)
43+
}
44+
45+
// Decode the boolean value indicating whether the user exists.
46+
uer.Exists, err = d.DecodeBool()
47+
48+
return err
49+
}
50+
51+
// NewUserExistsRequest creates a new request to check if a user exists.
52+
func NewUserExistsRequest(username string) UserExistsRequest {
53+
callReq := tarantool.NewCallRequest("box.schema.user.exists").Args([]interface{}{username})
54+
55+
return UserExistsRequest{
56+
callReq,
57+
}
58+
}
59+
60+
// Exists checks if the specified user exists in Tarantool.
61+
func (u *SchemaUser) Exists(ctx context.Context, username string) (bool, error) {
62+
// Create a request and send it to Tarantool.
63+
req := NewUserExistsRequest(username).Context(ctx)
64+
resp := &UserExistsResponse{}
65+
66+
// Execute the request and parse the response.
67+
err := u.conn.Do(req).GetTyped(resp)
68+
69+
return resp.Exists, err
70+
}
71+
72+
// UserCreateOptions represents options for creating a user in Tarantool.
73+
type UserCreateOptions struct {
74+
// IfNotExists - if true, prevents an error if the user already exists.
75+
IfNotExists bool `msgpack:"if_not_exists"`
76+
// Password for the new user.
77+
Password string `msgpack:"password"`
78+
}
79+
80+
// UserCreateRequest represents a request to create a new user in Tarantool.
81+
type UserCreateRequest struct {
82+
*tarantool.CallRequest // Underlying Tarantool call request.
83+
}
84+
85+
// NewUserCreateRequest creates a new request to create a user with specified options.
86+
func NewUserCreateRequest(username string, options UserCreateOptions) UserCreateRequest {
87+
callReq := tarantool.NewCallRequest("box.schema.user.create").
88+
Args([]interface{}{username, options})
89+
90+
return UserCreateRequest{
91+
callReq,
92+
}
93+
}
94+
95+
// UserCreateResponse represents the response to a user creation request.
96+
type UserCreateResponse struct{}
97+
98+
// DecodeMsgpack decodes the response for a user creation request.
99+
// In this case, the response does not contain any data.
100+
func (uer *UserCreateResponse) DecodeMsgpack(_ *msgpack.Decoder) error {
101+
return nil
102+
}
103+
104+
// Create creates a new user in Tarantool with the given username and options.
105+
func (u *SchemaUser) Create(ctx context.Context, username string, options UserCreateOptions) error {
106+
// Create a request and send it to Tarantool.
107+
req := NewUserCreateRequest(username, options).Context(ctx)
108+
resp := &UserCreateResponse{}
109+
110+
// Execute the request and handle the response.
111+
fut := u.conn.Do(req)
112+
113+
err := fut.GetTyped(resp)
114+
if err != nil {
115+
return err
116+
}
117+
118+
return nil
119+
}
120+
121+
// UserDropOptions represents options for dropping a user in Tarantool.
122+
type UserDropOptions struct {
123+
IfExists bool `msgpack:"if_exists"` // If true, prevents an error if the user does not exist.
124+
}
125+
126+
// UserDropRequest represents a request to drop a user from Tarantool.
127+
type UserDropRequest struct {
128+
*tarantool.CallRequest // Underlying Tarantool call request.
129+
}
130+
131+
// NewUserDropRequest creates a new request to drop a user with specified options.
132+
func NewUserDropRequest(username string, options UserDropOptions) UserDropRequest {
133+
callReq := tarantool.NewCallRequest("box.schema.user.drop").
134+
Args([]interface{}{username, options})
135+
136+
return UserDropRequest{
137+
callReq,
138+
}
139+
}
140+
141+
// UserDropResponse represents the response to a user drop request.
142+
type UserDropResponse struct{}
143+
144+
// Drop drops the specified user from Tarantool, with optional conditions.
145+
func (u *SchemaUser) Drop(ctx context.Context, username string, options UserDropOptions) error {
146+
// Create a request and send it to Tarantool.
147+
req := NewUserDropRequest(username, options).Context(ctx)
148+
resp := &UserCreateResponse{}
149+
150+
// Execute the request and handle the response.
151+
fut := u.conn.Do(req)
152+
153+
err := fut.GetTyped(resp)
154+
if err != nil {
155+
return err
156+
}
157+
158+
return nil
159+
}
160+
161+
// UserPasswordRequest represents a request to retrieve a user's password from Tarantool.
162+
type UserPasswordRequest struct {
163+
*tarantool.CallRequest // Underlying Tarantool call request.
164+
}
165+
166+
// NewUserPasswordRequest creates a new request to fetch the user's password.
167+
// It takes the username and constructs the request to Tarantool.
168+
func NewUserPasswordRequest(username string) UserPasswordRequest {
169+
// Create a request to get the user's password.
170+
callReq := tarantool.NewCallRequest("box.schema.user.password").Args([]interface{}{username})
171+
172+
return UserPasswordRequest{
173+
callReq,
174+
}
175+
}
176+
177+
// UserPasswordResponse represents the response to the user password request.
178+
// It contains the password hash.
179+
type UserPasswordResponse struct {
180+
Hash string // The password hash of the user.
181+
}
182+
183+
// DecodeMsgpack decodes the response from Tarantool in Msgpack format.
184+
// It expects the response to be an array of length 1, containing the password hash string.
185+
func (upr *UserPasswordResponse) DecodeMsgpack(d *msgpack.Decoder) error {
186+
// Decode the array length.
187+
arrayLen, err := d.DecodeArrayLen()
188+
if err != nil {
189+
return err
190+
}
191+
192+
// Ensure the array contains exactly 1 element (the password hash).
193+
if arrayLen != 1 {
194+
return fmt.Errorf("protocol violation; expected 1 array entry, got %d", arrayLen)
195+
}
196+
197+
// Decode the string containing the password hash.
198+
upr.Hash, err = d.DecodeString()
199+
200+
return err
201+
}
202+
203+
// Password sends a request to retrieve the user's password from Tarantool.
204+
// It returns the password hash as a string or an error if the request fails.
205+
func (u *SchemaUser) Password(ctx context.Context, username string) (string, error) {
206+
// Create the request and send it to Tarantool.
207+
req := NewUserPasswordRequest(username).Context(ctx)
208+
resp := &UserPasswordResponse{}
209+
210+
// Execute the request and handle the response.
211+
fut := u.conn.Do(req)
212+
213+
// Get the decoded response.
214+
err := fut.GetTyped(resp)
215+
if err != nil {
216+
return "", err
217+
}
218+
219+
// Return the password hash.
220+
return resp.Hash, nil
221+
}

0 commit comments

Comments
 (0)