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

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

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

问题

通过MongoDB控制台,我可以添加文档:

  1. {
  2. "_id": ObjectID(),
  3. "name": "John",
  4. "sign": UUID("32e79135-76c4-4682-80b2-8c813be9b792")
  5. }

然后,我可以读取它:

  1. {
  2. _id: ObjectId('64d4ce25ac7c4327e76a53e3'),
  3. name: "John",
  4. sign: UUID("32e7913576c4468280b28c813be9b792")
  5. }

我可以执行:

  1. db.getCollection('people').find({"sign": UUID("32e79135-76c4-4682-80b2-8c813be9b792")})

然后,我可以找到这个人。

但是,当我尝试在Go中执行时:

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "github.com/google/uuid"
  7. "go.mongodb.org/mongo-driver/mongo"
  8. "go.mongodb.org/mongo-driver/mongo/options"
  9. )
  10. type Person struct {
  11. Name string `bson:"name"`
  12. Sign uuid.UUID `bson:"sign"`
  13. }
  14. func main() {
  15. client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
  16. if err != nil {
  17. log.Fatal(err)
  18. }
  19. defer client.Disconnect(context.Background())
  20. db := client.Database("mydatabase")
  21. collection := db.Collection("people")
  22. signUUID := uuid.New()
  23. person := Person{
  24. Name: "John",
  25. Sign: signUUID,
  26. }
  27. _, err = collection.InsertOne(context.Background(), person)
  28. if err != nil {
  29. log.Fatal(err)
  30. }
  31. fmt.Println("Document inserted successfully")
  32. }

然后,在MongoDB中,我有:

  1. {
  2. _id: ObjectId('64d4cf0a308251580cffee4e'),
  3. name: 'John',
  4. sign: BinData(0, 'wqaX6oYLT1eurEEhIciK9Q==')
  5. }

我希望拥有一个UUID类型的"sign"字段,并能够在MongoDB中轻松地按其搜索。

英文:

By MongoDB console I can add document:

  1. {
  2. "_id": ObjectID(),
  3. "name": "John",
  4. "sign": UUID("32e79135-76c4-4682-80b2-8c813be9b792")
  5. }

And next I can read it:

  1. {
  2. _id: ObjectId('64d4ce25ac7c4327e76a53e3'),
  3. name: "John",
  4. sign: UUID("32e7913576c4468280b28c813be9b792")
  5. }

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:

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "github.com/google/uuid"
  7. "go.mongodb.org/mongo-driver/mongo"
  8. "go.mongodb.org/mongo-driver/mongo/options"
  9. )
  10. type Person struct {
  11. Name string `bson:"name"`
  12. Sign uuid.UUID `bson:"sign"`
  13. }
  14. func main() {
  15. client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
  16. if err != nil {
  17. log.Fatal(err)
  18. }
  19. defer client.Disconnect(context.Background())
  20. db := client.Database("mydatabase")
  21. collection := db.Collection("people")
  22. signUUID := uuid.New()
  23. person := Person{
  24. Name: "John",
  25. Sign: signUUID,
  26. }
  27. _, err = collection.InsertOne(context.Background(), person)
  28. if err != nil {
  29. log.Fatal(err)
  30. }
  31. fmt.Println("Document inserted successfully")
  32. }

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。请参见以下演示:

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "reflect"
  7. "github.com/google/uuid"
  8. "go.mongodb.org/mongo-driver/bson"
  9. "go.mongodb.org/mongo-driver/bson/bsoncodec"
  10. "go.mongodb.org/mongo-driver/bson/bsonrw"
  11. "go.mongodb.org/mongo-driver/mongo"
  12. "go.mongodb.org/mongo-driver/mongo/options"
  13. )
  14. type Person struct {
  15. Name string `bson:"name"`
  16. Sign uuid.UUID `bson:"sign"`
  17. }
  18. func main() {
  19. tUUID := reflect.TypeOf(uuid.Nil)
  20. bson.DefaultRegistry.RegisterTypeEncoder(tUUID, bsoncodec.ValueEncoderFunc(func(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
  21. // borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_encoders.go#L684-L691
  22. if !val.IsValid() || val.Type() != tUUID {
  23. return bsoncodec.ValueEncoderError{Name: "UUIDEncodeValue", Types: []reflect.Type{tUUID}, Received: val}
  24. }
  25. uuid := val.Interface().(uuid.UUID)
  26. return vw.WriteBinaryWithSubtype(uuid[:], bson.TypeBinaryUUID)
  27. }))
  28. bson.DefaultRegistry.RegisterTypeDecoder(tUUID, bsoncodec.ValueDecoderFunc(func(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
  29. // borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_decoders.go#L694-L706
  30. if !val.CanSet() || val.Type() != tUUID {
  31. return bsoncodec.ValueDecoderError{Name: "UUIDDecodeValue", Types: []reflect.Type{tUUID}, Received: val}
  32. }
  33. var data []byte
  34. var subtype byte
  35. var err error
  36. switch vrType := vr.Type(); vrType {
  37. case bson.TypeBinary:
  38. data, subtype, err = vr.ReadBinary()
  39. case bson.TypeNull:
  40. err = vr.ReadNull()
  41. case bson.TypeUndefined:
  42. err = vr.ReadUndefined()
  43. default:
  44. err = fmt.Errorf("cannot decode %v into a Binary", vrType)
  45. }
  46. if err != nil {
  47. return err
  48. }
  49. if subtype != bson.TypeBinaryUUID {
  50. return fmt.Errorf("cannot decode subtype %v into a UUID", subtype)
  51. }
  52. val.Set(reflect.ValueOf(uuid.UUID(data)))
  53. return nil
  54. }))
  55. client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
  56. if err != nil {
  57. log.Fatal(err)
  58. }
  59. defer client.Disconnect(context.Background())
  60. db := client.Database("mydatabase")
  61. collection := db.Collection("people")
  62. signUUID := uuid.New()
  63. person := Person{
  64. Name: "John",
  65. Sign: signUUID,
  66. }
  67. if _, err = collection.InsertOne(context.Background(), person); err != nil {
  68. log.Fatal(err)
  69. }
  70. var p Person
  71. if err := collection.FindOne(context.Background(),
  72. bson.M{"sign": signUUID},
  73. ).Decode(&p); err != nil {
  74. log.Fatal(err)
  75. }
  76. log.Printf("%+v\n", p)
  77. }

请参阅此处的讨论:Add support for mongodb UUID type

为了完整起见,以下显示了实现 bson.ValueMarshalerbson.ValueUnmarshaler 接口的方法:

  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "log"
  7. "github.com/google/uuid"
  8. "go.mongodb.org/mongo-driver/bson"
  9. "go.mongodb.org/mongo-driver/bson/bsontype"
  10. "go.mongodb.org/mongo-driver/mongo"
  11. "go.mongodb.org/mongo-driver/mongo/options"
  12. "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
  13. )
  14. type myUUID struct {
  15. uuid.UUID
  16. }
  17. var (
  18. _ bson.ValueMarshaler = (*myUUID)(nil)
  19. _ bson.ValueUnmarshaler = (*myUUID)(nil)
  20. )
  21. func (u myUUID) MarshalBSONValue() (bsontype.Type, []byte, error) {
  22. return bson.TypeBinary, bsoncore.AppendBinary(nil, bson.TypeBinaryUUID, []byte(u.UUID[:])), nil
  23. }
  24. func (u *myUUID) UnmarshalBSONValue(typ bsontype.Type, value []byte) error {
  25. if typ != bson.TypeBinary {
  26. return fmt.Errorf("cannot unmarshal %v into a Binary", typ)
  27. }
  28. subtype, bin, rem, ok := bsoncore.ReadBinary(value)
  29. if subtype != bson.TypeBinaryUUID {
  30. return fmt.Errorf("cannot unmarshal binary subtype %v into a UUID", subtype)
  31. }
  32. if len(rem) > 0 {
  33. return fmt.Errorf("value has extra data: %v", rem)
  34. }
  35. if !ok {
  36. return errors.New("value does not have enough bytes")
  37. }
  38. *u = myUUID{UUID: uuid.UUID(bin)}
  39. return nil
  40. }
  41. type Person struct {
  42. Name string `bson:"name"`
  43. Sign myUUID `bson:"sign"`
  44. }
  45. func main() {
  46. client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
  47. if err != nil {
  48. log.Fatal(err)
  49. }
  50. defer client.Disconnect(context.Background())
  51. db := client.Database("mydatabase")
  52. collection := db.Collection("people")
  53. signUUID := myUUID{UUID: uuid.New()}
  54. person := Person{
  55. Name: "John",
  56. Sign: signUUID,
  57. }
  58. if _, err = collection.InsertOne(context.Background(), person); err != nil {
  59. log.Fatal(err)
  60. }
  61. var p Person
  62. if err := collection.FindOne(context.Background(),
  63. bson.M{"sign": signUUID},
  64. ).Decode(&p); err != nil {
  65. log.Fatal(err)
  66. }
  67. log.Printf("%+v\n", p)
  68. }
英文:

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:

  1. package main
  2. import (
  3. "context"
  4. "fmt"
  5. "log"
  6. "reflect"
  7. "github.com/google/uuid"
  8. "go.mongodb.org/mongo-driver/bson"
  9. "go.mongodb.org/mongo-driver/bson/bsoncodec"
  10. "go.mongodb.org/mongo-driver/bson/bsonrw"
  11. "go.mongodb.org/mongo-driver/mongo"
  12. "go.mongodb.org/mongo-driver/mongo/options"
  13. )
  14. type Person struct {
  15. Name string `bson:"name"`
  16. Sign uuid.UUID `bson:"sign"`
  17. }
  18. func main() {
  19. tUUID := reflect.TypeOf(uuid.Nil)
  20. bson.DefaultRegistry.RegisterTypeEncoder(tUUID, bsoncodec.ValueEncoderFunc(func(ec bsoncodec.EncodeContext, vw bsonrw.ValueWriter, val reflect.Value) error {
  21. // borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_encoders.go#L684-L691
  22. if !val.IsValid() || val.Type() != tUUID {
  23. return bsoncodec.ValueEncoderError{Name: "UUIDEncodeValue", Types: []reflect.Type{tUUID}, Received: val}
  24. }
  25. uuid := val.Interface().(uuid.UUID)
  26. return vw.WriteBinaryWithSubtype(uuid[:], bson.TypeBinaryUUID)
  27. }))
  28. bson.DefaultRegistry.RegisterTypeDecoder(tUUID, bsoncodec.ValueDecoderFunc(func(dc bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
  29. // borrowed from https://github.com/mongodb/mongo-go-driver/blob/89c7e1d1d9a2954472fe852baf27ecca9dbf9bbf/bson/bsoncodec/default_value_decoders.go#L694-L706
  30. if !val.CanSet() || val.Type() != tUUID {
  31. return bsoncodec.ValueDecoderError{Name: "UUIDDecodeValue", Types: []reflect.Type{tUUID}, Received: val}
  32. }
  33. var data []byte
  34. var subtype byte
  35. var err error
  36. switch vrType := vr.Type(); vrType {
  37. case bson.TypeBinary:
  38. data, subtype, err = vr.ReadBinary()
  39. case bson.TypeNull:
  40. err = vr.ReadNull()
  41. case bson.TypeUndefined:
  42. err = vr.ReadUndefined()
  43. default:
  44. err = fmt.Errorf("cannot decode %v into a Binary", vrType)
  45. }
  46. if err != nil {
  47. return err
  48. }
  49. if subtype != bson.TypeBinaryUUID {
  50. return fmt.Errorf("cannot decode subtype %v into a UUID", subtype)
  51. }
  52. val.Set(reflect.ValueOf(uuid.UUID(data)))
  53. return nil
  54. }))
  55. client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
  56. if err != nil {
  57. log.Fatal(err)
  58. }
  59. defer client.Disconnect(context.Background())
  60. db := client.Database("mydatabase")
  61. collection := db.Collection("people")
  62. signUUID := uuid.New()
  63. person := Person{
  64. Name: "John",
  65. Sign: signUUID,
  66. }
  67. if _, err = collection.InsertOne(context.Background(), person); err != nil {
  68. log.Fatal(err)
  69. }
  70. var p Person
  71. if err := collection.FindOne(context.Background(),
  72. bson.M{"sign": signUUID},
  73. ).Decode(&p); err != nil {
  74. log.Fatal(err)
  75. }
  76. log.Printf("%+v\n", p)
  77. }

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:

  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "log"
  7. "github.com/google/uuid"
  8. "go.mongodb.org/mongo-driver/bson"
  9. "go.mongodb.org/mongo-driver/bson/bsontype"
  10. "go.mongodb.org/mongo-driver/mongo"
  11. "go.mongodb.org/mongo-driver/mongo/options"
  12. "go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
  13. )
  14. type myUUID struct {
  15. uuid.UUID
  16. }
  17. var (
  18. _ bson.ValueMarshaler = (*myUUID)(nil)
  19. _ bson.ValueUnmarshaler = (*myUUID)(nil)
  20. )
  21. func (u myUUID) MarshalBSONValue() (bsontype.Type, []byte, error) {
  22. return bson.TypeBinary, bsoncore.AppendBinary(nil, bson.TypeBinaryUUID, []byte(u.UUID[:])), nil
  23. }
  24. func (u *myUUID) UnmarshalBSONValue(typ bsontype.Type, value []byte) error {
  25. if typ != bson.TypeBinary {
  26. return fmt.Errorf("cannot unmarshal %v into a Binary", typ)
  27. }
  28. subtype, bin, rem, ok := bsoncore.ReadBinary(value)
  29. if subtype != bson.TypeBinaryUUID {
  30. return fmt.Errorf("cannot unmarshal binary subtype %v into a UUID", subtype)
  31. }
  32. if len(rem) > 0 {
  33. return fmt.Errorf("value has extra data: %v", rem)
  34. }
  35. if !ok {
  36. return errors.New("value does not have enough bytes")
  37. }
  38. *u = myUUID{UUID: uuid.UUID(bin)}
  39. return nil
  40. }
  41. type Person struct {
  42. Name string `bson:"name"`
  43. Sign myUUID `bson:"sign"`
  44. }
  45. func main() {
  46. client, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://localhost:27017"))
  47. if err != nil {
  48. log.Fatal(err)
  49. }
  50. defer client.Disconnect(context.Background())
  51. db := client.Database("mydatabase")
  52. collection := db.Collection("people")
  53. signUUID := myUUID{UUID: uuid.New()}
  54. person := Person{
  55. Name: "John",
  56. Sign: signUUID,
  57. }
  58. if _, err = collection.InsertOne(context.Background(), person); err != nil {
  59. log.Fatal(err)
  60. }
  61. var p Person
  62. if err := collection.FindOne(context.Background(),
  63. bson.M{"sign": signUUID},
  64. ).Decode(&p); err != nil {
  65. log.Fatal(err)
  66. }
  67. log.Printf("%+v\n", p)
  68. }

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:

确定