如何在Go中以UUID类型保存Mongo中的数据?

huangapple go评论150阅读模式
英文:

How to save something in Mongo in UUID type by Go?

问题

通过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

得分: 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.ValueMarshalerbson.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)
}

huangapple
  • 本文由 发表于 2023年8月10日 19:57:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/76875547.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定