在没有 _id 字段的情况下将文档插入 MongoDB 似乎有问题

Inserting a document into MongoDB wthout _id field seems bugged

我可能发现了一个奇怪的错误.. 我偶尔会更新我的 mongodb-驱动程序包,因为我使用了一年,我分层了很久以前制作的。

但是,今天,由于一些个人原因,我重构了我的测试,现在我的层上有 e2e 完整的测试套件。问题是,在进行测试时,我可以重现我过去遇到的错误,即我没有为我的文档设置 ID(_id bson 字段)

情况:

For obvious reason, some piece of code are missing but I am quite sure you will be able to guess really easily ;)

The commented out code part is the tests that are creating the weird behavior

代码:

要重现那个错误,有我的测试和我的层的源代码

测试:

type e2eMongoDBSuite struct {
    suite.Suite
    _mongodb mongodb.IMongoDB
}

type e2eTestDocument struct {
    ID         *primitive.ObjectID `json:"_id" bson:"_id"`
    ValueStr   string              `json:"value_str" bson:"value_str"`
    ValueInt32 int32               `json:"value_int32" bson:"value_int32"`
}

const (
    //// ci
    connectionString = "mongodb://localhost:27017"
    databaseName     = "mongodb-database-e2e-tests"
    collection       = "mongodb-collection-e2e-tests"
    defaultTimeout   = 5 * time.Second
)

func (e2eSuite *e2eMongoDBSuite) SetupSuite() {
    var err error
    if e2eSuite._mongodb, err = mongodb.New(&mongodb.Config{
        ConnectionString: connectionString,
        DatabaseName:     databaseName,
        DefaultTimeout:   defaultTimeout,
    }); err != nil {
        panic(fmt.Errorf("couldn't setup suite: %w", err).Error())
    }
}

func (e2eSuite *e2eMongoDBSuite) TestInsertOneWithID_OK() {
    a := assert.New(e2eSuite.T())

    // given
    docID := e2eSuite._mongodb.GenerateNewObjectID()
    doc := &e2eTestDocument{ID: docID}

    // when
    insertedID, err := e2eSuite._mongodb.InsertOne(collection, doc)

    // expected
    a.NotNil(insertedID, "the inserted document ID should be returned")
    a.Equal(docID, insertedID, "the inserted document ID should be the same as the given one at creation")
    a.Nil(err, "there should be no error for this insert")

    // then
    _ = e2eSuite._mongodb.DeleteMany(collection, bson.D{})
}

//func (e2eSuite *e2eMongoDBSuite) TestInsertOneWithNoID_OK() {
//  a := assert.New(e2eSuite.T())
//
//  // given
//  doc := &e2eTestDocument{}
//
//  // when
//  insertedID, err := e2eSuite._mongodb.InsertOne(collection, doc)
//
//  // expected
//  a.NotNil(insertedID, "the inserted document ID should be returned")
//  //a.Equal(docID, insertedID, "the inserted document ID should be the same as the given one at creation")
//  a.Nil(err, "there should be no error for this insert")
//
//   // then
//   _ = e2eSuite._mongodb.DeleteMany(collection, bson.D{})
//}

func (e2eSuite *e2eMongoDBSuite) TestInsertManyWithIDs_OK() {
    a := assert.New(e2eSuite.T())

    // given
    docID1 := e2eSuite._mongodb.GenerateNewObjectID()
    docID2 := e2eSuite._mongodb.GenerateNewObjectID()
    doc1 := &e2eTestDocument{ID: docID1}
    doc2 := &e2eTestDocument{ID: docID2}

    // when
    insertedIDs, err := e2eSuite._mongodb.InsertMany(collection, []*e2eTestDocument{doc1, doc2})

    // expected
    a.NotNil(insertedIDs, "the inserted document IDs should be returned")
    a.Equal(2, len(insertedIDs), "the inserted document IDs amount should be of two")
    a.EqualValues([]*primitive.ObjectID{docID1, docID2}, insertedIDs, "the inserted document IDs should be the same as the given ones at creation")
    a.Nil(err, "there should be no error for this insert")

    // then
    _ = e2eSuite._mongodb.DeleteMany(collection, bson.D{})
}

//func (e2eSuite *e2eMongoDBSuite) TestInsertManyWithNoIDs_OK() {
//  a := assert.New(e2eSuite.T())
//
//  // given
//  doc1 := &e2eTestDocument{}
//  doc2 := &e2eTestDocument{}
//
//  // when
//  insertedIDs, err := e2eSuite._mongodb.InsertMany(collection, []*e2eTestDocument{doc1, doc2})
//
//  // expected
//  a.NotNil(insertedIDs, "the inserted document IDs should be returned")
//  a.Equal(2, len(insertedIDs), "the inserted document IDs amount should be of two")
//  a.Nil(err, "there should be no error for this insert")
//
//// then
//_ = e2eSuite._mongodb.DeleteMany(collection, bson.D{})
//}

func (e2eSuite *e2eMongoDBSuite) TearDownSuite() {
    _ = e2eSuite._mongodb.DropDatabase()
}

func TestE2eMongoDBSuite(t *testing.T) {
    suite.Run(t, new(e2eMongoDBSuite))
}

我开发的图层:

// InsertOne inserts a document in a given collection and returns the inserted ObjectID.
func (m *mongoDB) InsertOne(collectionName string, document interface{}) (*primitive.ObjectID, error) {
    res, err := m.GetDatabase().Collection(collectionName).InsertOne(context.Background(), document)
    if err != nil {
        return nil, err
    }
    if oid, ok := res.InsertedID.(primitive.ObjectID); ok {
        return &oid, nil
    }
    return nil, nil
}

// InsertMany inserts documents in a given collection and returns the inserted ObjectIDs.
func (m *mongoDB) InsertMany(collectionName string, documents interface{}) ([]*primitive.ObjectID, error) {

    s := reflect.ValueOf(documents)
    if s.Kind() != reflect.Slice {
        panic("Documents given a non-slice type")
    }

    slice := make([]interface{}, s.Len())
    for i := 0; i < s.Len(); i++ {
        slice[i] = s.Index(i).Interface()
    }

    res, err := m.GetDatabase().Collection(collectionName).InsertMany(context.Background(), slice)
    if err != nil {
        return nil, err
    }

    var insertedIDs []*primitive.ObjectID
    for _, insertedID := range res.InsertedIDs {
        if oid, ok := insertedID.(primitive.ObjectID); ok {
            insertedIDs = append(insertedIDs, &oid)
        }
    }

    return insertedIDs, nil
}

结论

我不知道该行为是否合乎逻辑,但如果我的文档没有 ID,则应该生成一个 ID :confused:

备注

这里打开了一个主题:https://developer.mongodb.com/community/forums/t/inserting-a-document-in-go-with-no-id-set-results-into-a-weird-behavior/100359(不知道是不是bug所以我想我也应该post)

谢谢!

最大

如果您插入包含 _id 字段的文档,mongodb 将使用它作为文档 ID,即使它为空。要使 _id 自动生成,您必须插入文档 而没有 _id 字段。这应该有效:

type Doc struct {
  ID         *primitive.ObjectID `json:"_id" bson:"_id,omitempty"`
  ...

如果 _id 字段为 nil,这将不会编组它,强制它自动生成。