Skip to content

Commit 4c78e0a

Browse files
authored
Merge pull request #104 from tarantool/90-uuid-support
Support UUID type in msgpack
2 parents 706ffcd + fd68e12 commit 4c78e0a

File tree

9 files changed

+349
-6
lines changed

9 files changed

+349
-6
lines changed

README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,50 @@ func main() {
284284
}
285285
```
286286

287+
To enable support of UUID in msgpack with [google/uuid](https://github.com/google/uuid),
288+
import tarantool/uuid submodule.
289+
```go
290+
package main
291+
292+
import (
293+
"log"
294+
"time"
295+
296+
"github.com/tarantool/go-tarantool"
297+
_ "github.com/tarantool/go-tarantool/uuid"
298+
"github.com/google/uuid"
299+
)
300+
301+
func main() {
302+
server := "127.0.0.1:3013"
303+
opts := tarantool.Opts{
304+
Timeout: 500 * time.Millisecond,
305+
Reconnect: 1 * time.Second,
306+
MaxReconnects: 3,
307+
User: "test",
308+
Pass: "test",
309+
}
310+
client, err := tarantool.Connect(server, opts)
311+
if err != nil {
312+
log.Fatalf("Failed to connect: %s", err.Error())
313+
}
314+
315+
spaceNo := uint32(524)
316+
317+
id, uuidErr := uuid.Parse("c8f0fa1f-da29-438c-a040-393f1126ad39")
318+
if uuidErr != nil {
319+
log.Fatalf("Failed to prepare uuid: %s", uuidErr)
320+
}
321+
322+
resp, err := client.Replace(spaceNo, []interface{}{ id })
323+
324+
log.Println("UUID tuple replace")
325+
log.Println("Error", err)
326+
log.Println("Code", resp.Code)
327+
log.Println("Data", resp.Data)
328+
}
329+
```
330+
287331
## Schema
288332

289333
```go

config.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,24 @@ console.listen '0.0.0.0:33015'
6161

6262
--box.schema.user.revoke('guest', 'read,write,execute', 'universe')
6363

64+
-- Create space with UUID pk if supported
65+
local uuid = require('uuid')
66+
local msgpack = require('msgpack')
67+
68+
local uuid_msgpack_supported = pcall(msgpack.encode, uuid.new())
69+
if uuid_msgpack_supported then
70+
local suuid = box.schema.space.create('testUUID', {
71+
id = 524,
72+
if_not_exists = true,
73+
})
74+
suuid:create_index('primary', {
75+
type = 'tree',
76+
parts = {{ field = 1, type = 'uuid' }},
77+
if_not_exists = true
78+
})
79+
suuid:truncate()
80+
81+
box.schema.user.grant('test', 'read,write', 'space', 'testUUID', { if_not_exists = true })
82+
83+
suuid:insert({ uuid.fromstr("c8f0fa1f-da29-438c-a040-393f1126ad39") })
84+
end

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.11
44

55
require (
66
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
7-
google.golang.org/appengine v1.6.6 // indirect
7+
google.golang.org/appengine v1.6.7 // indirect
88
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
9-
gopkg.in/vmihailenco/msgpack.v2 v2.9.1
9+
gopkg.in/vmihailenco/msgpack.v2 v2.9.2
1010
)

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
1212
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
1313
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
1414
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
15-
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
16-
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
15+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
16+
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
1717
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
1818
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
19-
gopkg.in/vmihailenco/msgpack.v2 v2.9.1 h1:kb0VV7NuIojvRfzwslQeP3yArBqJHW9tOl4t38VS1jM=
20-
gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
19+
gopkg.in/vmihailenco/msgpack.v2 v2.9.2 h1:gjPqo9orRVlSAH/065qw3MsFCDpH7fa1KpiizXyllY4=
20+
gopkg.in/vmihailenco/msgpack.v2 v2.9.2/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=

uuid/config.lua

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
local uuid = require('uuid')
2+
local msgpack = require('msgpack')
3+
4+
box.cfg{
5+
listen = 3013,
6+
wal_dir = 'xlog',
7+
snap_dir = 'snap',
8+
}
9+
10+
box.schema.user.create('test', { password = 'test' , if_not_exists = true })
11+
box.schema.user.grant('test', 'execute', 'universe', nil, { if_not_exists = true })
12+
13+
local uuid_msgpack_supported = pcall(msgpack.encode, uuid.new())
14+
if not uuid_msgpack_supported then
15+
error('UUID unsupported, use Tarantool 2.4.1 or newer')
16+
end
17+
18+
local s = box.schema.space.create('testUUID', {
19+
id = 524,
20+
if_not_exists = true,
21+
})
22+
s:create_index('primary', {
23+
type = 'tree',
24+
parts = {{ field = 1, type = 'uuid' }},
25+
if_not_exists = true
26+
})
27+
s:truncate()
28+
29+
box.schema.user.grant('test', 'read,write', 'space', 'testUUID', { if_not_exists = true })
30+
31+
s:insert({ uuid.fromstr("c8f0fa1f-da29-438c-a040-393f1126ad39") })

uuid/go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
module github.com/tarantool/go-tarantool/uuid
2+
3+
go 1.11
4+
5+
require (
6+
github.com/google/uuid v1.3.0
7+
github.com/tarantool/go-tarantool v0.0.0-20211104105631-61f3a41907b6
8+
google.golang.org/appengine v1.6.7 // indirect
9+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
10+
gopkg.in/vmihailenco/msgpack.v2 v2.9.2
11+
)

uuid/go.sum

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
2+
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
3+
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
4+
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
5+
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
6+
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
7+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
8+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
9+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
10+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
11+
github.com/tarantool/go-tarantool v0.0.0-20211104105631-61f3a41907b6 h1:WGaVN8FgSHg3xaiYnJvTk9bnXIgW8nAOUg5aVH4RLX8=
12+
github.com/tarantool/go-tarantool v0.0.0-20211104105631-61f3a41907b6/go.mod h1:m/mppmrDtgvS3tqUvaZRdRtlgzK1Gz/T6uGndkOItmQ=
13+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
14+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
15+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
16+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
17+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
18+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
19+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
20+
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
21+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
22+
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
23+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
24+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
25+
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
26+
gopkg.in/vmihailenco/msgpack.v2 v2.9.1/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=
27+
gopkg.in/vmihailenco/msgpack.v2 v2.9.2 h1:gjPqo9orRVlSAH/065qw3MsFCDpH7fa1KpiizXyllY4=
28+
gopkg.in/vmihailenco/msgpack.v2 v2.9.2/go.mod h1:/3Dn1Npt9+MYyLpYYXjInO/5jvMLamn+AEGwNEOatn8=

uuid/uuid.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package uuid
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
7+
"github.com/google/uuid"
8+
"gopkg.in/vmihailenco/msgpack.v2"
9+
)
10+
11+
// UUID external type
12+
// Supported since Tarantool 2.4.1. See more in commit messages.
13+
// https://github.com/tarantool/tarantool/commit/d68fc29246714eee505bc9bbcd84a02de17972c5
14+
15+
const UUID_extId = 2
16+
17+
func encodeUUID(e *msgpack.Encoder, v reflect.Value) error {
18+
id := v.Interface().(uuid.UUID)
19+
20+
bytes, err := id.MarshalBinary()
21+
if err != nil {
22+
return fmt.Errorf("msgpack: can't marshal binary uuid: %w", err)
23+
}
24+
25+
_, err = e.Writer().Write(bytes)
26+
if err != nil {
27+
return fmt.Errorf("msgpack: can't write bytes to encoder writer: %w", err)
28+
}
29+
30+
return nil
31+
}
32+
33+
func decodeUUID(d *msgpack.Decoder, v reflect.Value) error {
34+
var bytesCount int = 16;
35+
bytes := make([]byte, bytesCount)
36+
37+
n, err := d.Buffered().Read(bytes)
38+
if err != nil {
39+
return fmt.Errorf("msgpack: can't read bytes on uuid decode: %w", err)
40+
}
41+
if n < bytesCount {
42+
return fmt.Errorf("msgpack: unexpected end of stream after %d uuid bytes", n)
43+
}
44+
45+
id, err := uuid.FromBytes(bytes)
46+
if err != nil {
47+
return fmt.Errorf("msgpack: can't create uuid from bytes: %w", err)
48+
}
49+
50+
v.Set(reflect.ValueOf(id))
51+
return nil
52+
}
53+
54+
func init() {
55+
msgpack.Register(reflect.TypeOf((*uuid.UUID)(nil)).Elem(), encodeUUID, decodeUUID)
56+
msgpack.RegisterExt(UUID_extId, (*uuid.UUID)(nil))
57+
}

uuid/uuid_test.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package uuid_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
"time"
7+
8+
. "github.com/tarantool/go-tarantool"
9+
_ "github.com/tarantool/go-tarantool/uuid"
10+
"gopkg.in/vmihailenco/msgpack.v2"
11+
"github.com/google/uuid"
12+
)
13+
14+
var server = "127.0.0.1:3013"
15+
var opts = Opts{
16+
Timeout: 500 * time.Millisecond,
17+
User: "test",
18+
Pass: "test",
19+
}
20+
21+
var space = "testUUID"
22+
var index = "primary"
23+
24+
type TupleUUID struct {
25+
id uuid.UUID
26+
}
27+
28+
func (t *TupleUUID) DecodeMsgpack(d *msgpack.Decoder) error {
29+
var err error
30+
var l int
31+
if l, err = d.DecodeSliceLen(); err != nil {
32+
return err
33+
}
34+
if l != 1 {
35+
return fmt.Errorf("array len doesn't match: %d", l)
36+
}
37+
res, err := d.DecodeInterface()
38+
if err != nil {
39+
return err
40+
}
41+
t.id = res.(uuid.UUID)
42+
return nil
43+
}
44+
45+
func connectWithValidation(t *testing.T) *Connection {
46+
conn, err := Connect(server, opts)
47+
if err != nil {
48+
t.Errorf("Failed to connect: %s", err.Error())
49+
}
50+
if conn == nil {
51+
t.Errorf("conn is nil after Connect")
52+
}
53+
return conn
54+
}
55+
56+
func skipIfUUIDUnsupported(t *testing.T, conn *Connection) {
57+
resp, err := conn.Eval("return pcall(require('msgpack').encode, require('uuid').new())", []interface{}{})
58+
if err != nil {
59+
t.Errorf("Failed to Eval: %s", err.Error())
60+
}
61+
if resp == nil {
62+
t.Errorf("Response is nil after Eval")
63+
}
64+
if len(resp.Data) < 1 {
65+
t.Errorf("Response.Data is empty after Eval")
66+
}
67+
val := resp.Data[0].(bool)
68+
if val != true {
69+
t.Skip("Skipping test for Tarantool without UUID support in msgpack")
70+
}
71+
}
72+
73+
func tupleValueIsId(t *testing.T, tuples []interface{}, id uuid.UUID) {
74+
if len(tuples) != 1 {
75+
t.Errorf("Response Data len != 1")
76+
}
77+
78+
if tpl, ok := tuples[0].([]interface{}); !ok {
79+
t.Errorf("Unexpected return value body")
80+
} else {
81+
if len(tpl) != 1 {
82+
t.Errorf("Unexpected return value body (tuple len)")
83+
}
84+
if val, ok := tpl[0].(uuid.UUID); !ok || val != id {
85+
t.Errorf("Unexpected return value body (tuple 0 field)")
86+
}
87+
}
88+
}
89+
90+
func TestSelect(t *testing.T) {
91+
conn := connectWithValidation(t)
92+
defer conn.Close()
93+
94+
skipIfUUIDUnsupported(t, conn)
95+
96+
id, uuidErr := uuid.Parse("c8f0fa1f-da29-438c-a040-393f1126ad39")
97+
if uuidErr != nil {
98+
t.Errorf("Failed to prepare test uuid: %s", uuidErr)
99+
}
100+
101+
resp, errSel := conn.Select(space, index, 0, 1, IterEq, []interface{}{ id })
102+
if errSel != nil {
103+
t.Errorf("UUID select failed: %s", errSel.Error())
104+
}
105+
if resp == nil {
106+
t.Errorf("Response is nil after Select")
107+
}
108+
tupleValueIsId(t, resp.Data, id)
109+
110+
var tuples []TupleUUID
111+
errTyp := conn.SelectTyped(space, index, 0, 1, IterEq, []interface{}{ id }, &tuples)
112+
if errTyp != nil {
113+
t.Errorf("Failed to SelectTyped: %s", errTyp.Error())
114+
}
115+
if len(tuples) != 1 {
116+
t.Errorf("Result len of SelectTyped != 1")
117+
}
118+
if tuples[0].id != id {
119+
t.Errorf("Bad value loaded from SelectTyped: %s", tuples[0].id)
120+
}
121+
}
122+
123+
func TestReplace(t *testing.T) {
124+
conn := connectWithValidation(t)
125+
defer conn.Close()
126+
127+
skipIfUUIDUnsupported(t, conn)
128+
129+
id, uuidErr := uuid.Parse("64d22e4d-ac92-4a23-899a-e59f34af5479")
130+
if uuidErr != nil {
131+
t.Errorf("Failed to prepare test uuid: %s", uuidErr)
132+
}
133+
134+
respRep, errRep := conn.Replace(space, []interface{}{ id })
135+
if errRep != nil {
136+
t.Errorf("UUID replace failed: %s", errRep)
137+
}
138+
if respRep == nil {
139+
t.Errorf("Response is nil after Replace")
140+
}
141+
tupleValueIsId(t, respRep.Data, id)
142+
143+
respSel, errSel := conn.Select(space, index, 0, 1, IterEq, []interface{}{ id })
144+
if errSel != nil {
145+
t.Errorf("UUID select failed: %s", errSel)
146+
}
147+
if respSel == nil {
148+
t.Errorf("Response is nil after Select")
149+
}
150+
tupleValueIsId(t, respSel.Data, id)
151+
}

0 commit comments

Comments
 (0)