英文:
How to save something in Mongo in UUID type by Go?
问题 {#heading}
通过MongoDB控制台,我可以添加文档:
{
"_id": ObjectID(),
"name": "John",
"sign": UUID("32e79135-76c4-4682-80b2-8c813be9b792")
}
然后,我可以读取它:
{
_id: ObjectId('64d4ce25ac7c4327e76a53e3'),
name: "John",
sign: UUID("32e7913576c4468280b28c813be9b792")
}
我可以执行:
db.getCollection('people').find({"sign": UUID("32e79135-76c4-4682-80b2-8c813be9b792")})
然后,我可以找到这个人。
但是,当我尝试在Go中执行时:
package main
import (
"context"
"fmt"
"log"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Person struct {
Name string `bson:"name"`
Sign uuid.UUID `bson:"sign"`
}
func main() {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
db := client.Database("mydatabase")
collection := db.Collection("people")
signUUID := uuid.New()
person := Person{
Name: "John",
Sign: signUUID,
}
_, err = collection.InsertOne(context.Background(), person)
if err != nil {
log.Fatal(err)
}
fmt.Println("Document inserted successfully")
}
然后,在MongoDB中,我有:
{
_id: ObjectId('64d4cf0a308251580cffee4e'),
name: 'John',
sign: BinData(0, 'wqaX6oYLT1eurEEhIciK9Q==')
}
我希望拥有一个UUID类型的"sign"字段,并能够在MongoDB中轻松地按其搜索。 英文:
By MongoDB console I can add document:
{
"_id": ObjectID(),
"name": "John",
"sign": UUID("32e79135-76c4-4682-80b2-8c813be9b792")
}
And next I can read it:
{
_id: ObjectId('64d4ce25ac7c4327e76a53e3'),
name: "John",
sign: UUID("32e7913576c4468280b28c813be9b792")
}
I can do:
db.getCollection('people').find({"sign": UUID("32e79135-76c4-4682-80b2-8c813be9b792")})
And I can find the people.
But when I try it in Go:
package main
import (
\"context\"
\"fmt\"
\"log\"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Person struct {
Name string `bson:"name"`
Sign uuid.UUID `bson:"sign"`
}
func main() {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(\"mongodb://localhost:27017\"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
db := client.Database("mydatabase")
collection := db.Collection("people")
signUUID := uuid.New()
person := Person{
Name: "John",
Sign: signUUID,
}
_, err = collection.InsertOne(context.Background(), person)
if err != nil {
log.Fatal(err)
}
fmt.Println("Document inserted successfully")
}
Then in Mongo I have:
{
_id: ObjectId('64d4cf0a308251580cffee4e'),
name: 'John',
sign: BinData(0, 'wqaX6oYLT1eurEEhIciK9Q==')
}
I would like to have a "sign" field of type UUID and be able to easily search by it in Mongo.
答案1 {#1}
得分: 2
The package github.com/google/uuid
不实现 bson.ValueMarshaler 接口来将自身编组为 mongodb UUID,因此它无法正常工作。
我们可以注册我们自己的编解码器来对其进行编码或解码以转换为或从 mongodb UUID。请参见以下演示:
package main
import (
"context"
"fmt"
"log"
"reflect"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Person struct {
Name string `bson:"name"`
Sign uuid.UUID `bson:"sign"`
}
func main() {
tUUID := reflect.TypeOf(uuid.Nil)
bson.DefaultRegistry.RegisterTypeEncoder(tUUID, bsoncodec.ValueEncoderFunc(func(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
// borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_encoders.go#L684-L691
if !val.IsValid() || val.Type() != tUUID {
return bsoncodec.ValueEncoderError{Name: "UUIDEncodeValue", Types: []reflect.Type{tUUID}, Received: val}
}
uuid := val.Interface().(uuid.UUID)
return vw.WriteBinaryWithSubtype(uuid[:], bson.TypeBinaryUUID)
}))
bson.DefaultRegistry.RegisterTypeDecoder(tUUID, bsoncodec.ValueDecoderFunc(func(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
// borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_decoders.go#L694-L706
if !val.CanSet() || val.Type() != tUUID {
return bsoncodec.ValueDecoderError{Name: "UUIDDecodeValue", Types: []reflect.Type{tUUID}, Received: val}
}
var data []byte
var subtype byte
var err error
switch vrType := vr.Type(); vrType {
case bson.TypeBinary:
data, subtype, err = vr.ReadBinary()
case bson.TypeNull:
err = vr.ReadNull()
case bson.TypeUndefined:
err = vr.ReadUndefined()
default:
err = fmt.Errorf("cannot decode %v into a Binary", vrType)
}
if err != nil {
return err
}
if subtype != bson.TypeBinaryUUID {
return fmt.Errorf("cannot decode subtype %v into a UUID", subtype)
}
val.Set(reflect.ValueOf(uuid.UUID(data)))
return nil
}))
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
db := client.Database("mydatabase")
collection := db.Collection("people")
signUUID := uuid.New()
person := Person{
Name: "John",
Sign: signUUID,
}
if _, err = collection.InsertOne(context.Background(), person); err != nil {
log.Fatal(err)
}
var p Person
if err := collection.FindOne(context.Background(),
bson.M{"sign": signUUID},
).Decode(&p); err != nil {
log.Fatal(err)
}
log.Printf("%+v\n", p)
}
请参阅此处的讨论:Add support for mongodb UUID type。
为了完整起见,以下显示了实现 bson.ValueMarshaler
和 bson.ValueUnmarshaler
接口的方法:
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
type myUUID struct {
uuid.UUID
}
var (
_ bson.ValueMarshaler = (*myUUID)(nil)
_ bson.ValueUnmarshaler = (*myUUID)(nil)
)
func (u myUUID) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.TypeBinary, bsoncore.AppendBinary(nil, bson.TypeBinaryUUID, []byte(u.UUID[:])), nil
}
func (u *myUUID) UnmarshalBSONValue(typ bsontype.Type, value []byte) error {
if typ != bson.TypeBinary {
return fmt.Errorf("cannot unmarshal %v into a Binary", typ)
}
subtype, bin, rem, ok := bsoncore.ReadBinary(value)
if subtype != bson.TypeBinaryUUID {
return fmt.Errorf("cannot unmarshal binary subtype %v into a UUID", subtype)
}
if len(rem) > 0 {
return fmt.Errorf("value has extra data: %v", rem)
}
if !ok {
return errors.New("value does not have enough bytes")
}
*u = myUUID{UUID: uuid.UUID(bin)}
return nil
}
type Person struct {
Name string `bson:"name"`
Sign myUUID `bson:"sign"`
}
func main() {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
db := client.Database("mydatabase")
collection := db.Collection("people")
signUUID := myUUID{UUID: uuid.New()}
person := Person{
Name: "John",
Sign: signUUID,
}
if _, err = collection.InsertOne(context.Background(), person); err != nil {
log.Fatal(err)
}
var p Person
if err := collection.FindOne(context.Background(),
bson.M{"sign": signUUID},
).Decode(&p); err != nil {
log.Fatal(err)
}
log.Printf("%+v\n", p)
}
英文:
The package github.com/google/uuid
does not implement the bson.ValueMarshaler interface to marshal itself into a mongodb UUID, that's why it does not work.
We can register our own codec to encode it to or decode it from mongodb UUID. See the demo below:
package main
import (
"context"
"fmt"
"log"
"reflect"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Person struct {
Name string `bson:"name"`
Sign uuid.UUID `bson:"sign"`
}
func main() {
tUUID := reflect.TypeOf(uuid.Nil)
bson.DefaultRegistry.RegisterTypeEncoder(tUUID, bsoncodec.ValueEncoderFunc(func(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
// borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_encoders.go#L684-L691
if !val.IsValid() || val.Type() != tUUID {
return bsoncodec.ValueEncoderError{Name: "UUIDEncodeValue", Types: []reflect.Type{tUUID}, Received: val}
}
uuid := val.Interface().(uuid.UUID)
return vw.WriteBinaryWithSubtype(uuid[:], bson.TypeBinaryUUID)
}))
bson.DefaultRegistry.RegisterTypeDecoder(tUUID, bsoncodec.ValueDecoderFunc(func(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
// borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_decoders.go#L694-L706
if !val.CanSet() || val.Type() != tUUID {
return bsoncodec.ValueDecoderError{Name: "UUIDDecodeValue", Types: []reflect.Type{tUUID}, Received: val}
}
var data []byte
var subtype byte
var err error
switch vrType := vr.Type(); vrType {
case bson.TypeBinary:
data, subtype, err = vr.ReadBinary()
case bson.TypeNull:
err = vr.ReadNull()
case bson.TypeUndefined:
err = vr.ReadUndefined()
default:
err = fmt.Errorf("cannot decode %v into a Binary", vrType)
}
if err != nil {
return err
}
if subtype != bson.TypeBinaryUUID {
return fmt.Errorf("cannot decode subtype %v into a UUID", subtype)
}
val.Set(reflect.ValueOf(uuid.UUID(data)))
return nil
}))
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
db := client.Database("mydatabase")
collection := db.Collection("people")
signUUID := uuid.New()
person := Person{
Name: "John",
Sign: signUUID,
}
if _, err = collection.InsertOne(context.Background(), person); err != nil {
log.Fatal(err)
}
var p Person
if err := collection.FindOne(context.Background(),
bson.M{"sign": signUUID},
).Decode(&p); err != nil {
log.Fatal(err)
}
log.Printf("%+v\n", p)
}
See the discussion here: Add support for mongodb UUID type.
For the sake of completeness, below shows the approach that implements the bson.ValueMarshaler
and bson.ValueUnmarshaler
interfaces:
package main
import (
"context"
"errors"
"fmt"
"log"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsontype"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
)
type myUUID struct {
uuid.UUID
}
var (
_ bson.ValueMarshaler = (*myUUID)(nil)
_ bson.ValueUnmarshaler = (*myUUID)(nil)
)
func (u myUUID) MarshalBSONValue() (bsontype.Type, []byte, error) {
return bson.TypeBinary, bsoncore.AppendBinary(nil, bson.TypeBinaryUUID, []byte(u.UUID[:])), nil
}
func (u *myUUID) UnmarshalBSONValue(typ bsontype.Type, value []byte) error {
if typ != bson.TypeBinary {
return fmt.Errorf("cannot unmarshal %v into a Binary", typ)
}
subtype, bin, rem, ok := bsoncore.ReadBinary(value)
if subtype != bson.TypeBinaryUUID {
return fmt.Errorf("cannot unmarshal binary subtype %v into a UUID", subtype)
}
if len(rem) > 0 {
return fmt.Errorf("value has extra data: %v", rem)
}
if !ok {
return errors.New("value does not have enough bytes")
}
*u = myUUID{UUID: uuid.UUID(bin)}
return nil
}
type Person struct {
Name string `bson:"name"`
Sign myUUID `bson:"sign"`
}
func main() {
client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
if err != nil {
log.Fatal(err)
}
defer client.Disconnect(context.Background())
db := client.Database("mydatabase")
collection := db.Collection("people")
signUUID := myUUID{UUID: uuid.New()}
person := Person{
Name: "John",
Sign: signUUID,
}
if _, err = collection.InsertOne(context.Background(), person); err != nil {
log.Fatal(err)
}
var p Person
if err := collection.FindOne(context.Background(),
bson.M{"sign": signUUID},
).Decode(&p); err != nil {
log.Fatal(err)
}
log.Printf("%+v\n", p)
}