在Go语言中,使用mongo-driver和Mongosh时,$pull的工作方式不同。

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

$pull doesn't work the same in Go with mongo-driver and in Mongosh

问题

什么?

尝试从Mongo文档中的对象数组中删除一个元素。

我使用的是:
go version go1.17.1 darwin/amd64;
go.mongodb.org/mongo-driver v1.8.4;
MongoDB 5.0.6 Enterprise

以下是函数的简化版本,尽可能简单:

func (r *Repo) UpdateModelByID(ctx context.Context, id string) error {
    objID, err := primitive.ObjectIDFromHex(id)
    // 实际上我在这里处理了 err,它是 nil

    filter := bson.D{{"id", objID}}

    update := bson.D{
        {
            Key: "$pull",
            Value: bson.E{
                Key: "data",
                Value: bson.E{
                    Key: "$in",
                    Value: []string{"field_type_1", "field_type_2"},
                },
            },
        },
    }

    log.Debugln("UPDATE", update)

    result, err := cdm.getTemplatesCollection().UpdateOne(ctx, filter, update)
    // 实际上我在这里处理了 err,它是 nil
    log.Debugf("RESULT_OF_UPDATE: %+v", result)
}

期望的结果是:
我提供的ID对应的文档中,数组"data"中的元素不再具有"field_type"字段等于"field_type_1"或"field_type_2"。

实际结果是:

DEBU[0023] UPDATE [{$pull {data {field_type {$in [field_type_1, field_type_2]}}}}]
DEBU[0023] RESULT_OF_UPDATE: &{MatchedCount:1 ModifiedCount:0 UpsertedCount:0 UpsertedID:<nil>}

通过mongosh执行相同的命令:

db.templates.updateOne(
   { _id: ObjectId("6228a89d621d19a2f7977d2f") },
   { $pull: { data: {field_type: {$in: ["field_type_1", "field_type_2"]}}}
   }
)
{ acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0 }

然后数组中的元素被删除了。

为什么会这样呢?

我注意到原生查询使用的是单花括号(只是一个对象),而Go代码使用的是bson.D,它在技术上是这些相同对象(bson.E)的数组([{}])。

我尝试更改mongosh命令进行测试:

db.templates.updateOne(
   { _id: ObjectId("6228a89d621d19a2f7977d2f") },
   [{ $pull: { data: {field_type: {$in: ["field_type_1", "field_type_2"]}}}
   }]
)
MongoServerError: Unrecognized pipeline stage name: '$pull'

在Go中,我尝试使用bson.E而不是bson.D(尽管根据文档,后者对于命令是推荐的),结果得到了这个错误:

DEBU[0030] UPDATE {$pull {data {field_type {$in [field_type_1, field_type_2]}}}}
ERRO[0030] cannot update model by id: update document must contain key beginning with '$' method=UpdateModelByID templateId=6228a89d621d19a2f7977d2f
英文:

What?

Trying to remove an element from an array of objects in a mongo document.

I use:
go version go1.17.1 darwin/amd64;
go.mongodb.org/mongo-driver v 1.8.4;
MongoDB 5.0.6 Enterprise

Shortened version of the function with everything as plain as possible:

func (r *Repo) UpdateModelByID(ctx context.Context, id string) error {
    objID, err := primitive.ObjectIDFromHex(id)
    // I actually handle err here, it&#39;s nil

    filter := bson.D{{&quot;_id&quot;, objID}}

    update = bson.D{{
       Key: &quot;$pull&quot;,
       Value: bson.E{
          Key: &quot;data&quot;,
          Value: bson.E{
             Key: &quot;field_type&quot;,
             Value: bson.E{
                Key:   &quot;$in&quot;,
                Value: []string{&quot;field_type_1&quot;, &quot;field_type_2&quot;},
             },
          },
       },
    }}
    
    log.Debugln(&quot;UPDATE&quot;, update)

    result, err = cdm.getTemplatesCollection().UpdateOne(ctx, filter, update)
    // I actually handle err here, it&#39;s nil
    log.Debugf(&quot;RESULT_OF_UPDATE: %+v&quot;, result)
}

Expected:

Document with id I provided will no longer have elements in array "data" that have a field "field_type" equal to "field_type_1" or "field_type_2"

Got:

DEBU[0023] UPDATE [{$pull {data {field_type {$in [field_type_1, field_type_2]}}}}] 
DEBU[0023] RESULT_OF_UPDATE: &amp;{MatchedCount:1 ModifiedCount:0 UpsertedCount:0 UpsertedID:&lt;nil&gt;}

Same command through mongosh:

db.templates.updateOne(
   { _id: ObjectId(&quot;6228a89d621d19a2f7977d2f&quot;) },
   { $pull: { data: {field_type: {$in: [&quot;field_type_1&quot;, &quot;field_type_2&quot;]}}}
   }
)
{ acknowledged: true,
  insertedId: null,
  matchedCount: 1,
  modifiedCount: 1,
  upsertedCount: 0 }

And the elem-s are gone from the array.

Why could this be?

I did notice that native query has single curly brackets (just an object), while Go code uses bson.D which technically is an array of those same objects (bson.E) -> [{}].

I tried changing mongosh command to test:

    db.templates.updateOne(
   { _id: ObjectId(&quot;6228a89d621d19a2f7977d2f&quot;) },
   [{ $pull: { data: {field_type: {$in: [&quot;field_type_1&quot;, &quot;field_type_2&quot;]}}}
   }]
)
MongoServerError: Unrecognized pipeline stage name: &#39;$pull&#39;

In Go I tried using bson.E instead of bson.D (though the latter is recommended for commands per documentation) and got this:

DEBU[0030] UPDATE {$pull {data {field_type {$in [field_type_1, field_type_2]}}}} 
ERRO[0030] cannot update model by id: update document must contain key beginning with &#39;$&#39;  method=UpdateModelByID templateId=6228a89d621d19a2f7977d2f

答案1

得分: 1

bson.D 模型化了一个文档,它是一个有序的键值对列表,其中 key 是属性名,value 是属性的值。

因此,如果你打算使用 bson.D 来模型化文档,你必须始终使用 bson.D 的复合字面量来表示等效的 shell 命令中的文档。

所以你的 update 文档应该像这样:

update := bson.D{{
	Key: "$pull", Value: bson.D{{
		Key: "data", Value: bson.D{{
			Key: "field_type", Value: bson.D{{
				Key: "$in", Value: []string{"field_type_1", "field_type_2"},
			}}}},
		}},
	}},
}

如果你省略了复合字面量中的命名字段,它会简化为:

update := bson.D{
	{"$pull", bson.D{
		"data", bson.D{
			"field_type", bson.D{
				"$in", []string{"field_type_1", "field_type_2"},
			}}}},
	},
}

是的,这看起来有点丑陋。请注意,如果元素的顺序不重要,使用 bson.M 值来定义文档(它是一个映射)会更容易。详情请参考 https://stackoverflow.com/questions/64281675/bson-d-vs-bson-m-for-find-queries/64281937#64281937

使用 bson.M 定义的 update 可以像这样:

update := bson.M{
	"$pull": bson.M{
		"data": bson.M{
			"field_type": bson.M{
				"$in": []string{"field_type_1", "field_type_2"},
			},
		},
	},
}
英文:

bson.D models a document, with an ordered list of key-value pairs, where the key is the property name, value is the property's value.

So if you intend to use bson.D to model documents, you always have to write a bson.D composite literal where there's a document in the equivalent shell command.

So your update document must look like this:

update := bson.D{{
	Key: &quot;$pull&quot;, Value: bson.D{{
		Key: &quot;data&quot;, Value: bson.D{{
			Key: &quot;field_type&quot;, Value: bson.D{{
				Key: &quot;$in&quot;, Value: []string{&quot;field_type_1&quot;, &quot;field_type_2&quot;},
			}}},
		}},
	}},
}

If you omit the named fields from the composite literals, it's reduced to this:

update := bson.D{
	{&quot;$pull&quot;, bson.D{{
		&quot;data&quot;, bson.D{{
			&quot;field_type&quot;, bson.D{{
				&quot;$in&quot;, []string{&quot;field_type_1&quot;, &quot;field_type_2&quot;},
			}}},
		}},
	}},
}

Yes, it's kinda ugly. Please note that if order of elements does not matter, it's much easier to use bson.M values to define documents (it's a map). For details, see https://stackoverflow.com/questions/64281675/bson-d-vs-bson-m-for-find-queries/64281937#64281937

Your update could look like this defined with bson.M:

update := bson.M{
	&quot;$pull&quot;: bson.M{
		&quot;data&quot;: bson.M{
			&quot;field_type&quot;: bson.M{
				&quot;$in&quot;: []string{&quot;field_type_1&quot;, &quot;field_type_2&quot;},
			},
		},
	},
}

huangapple
  • 本文由 发表于 2022年3月10日 17:50:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/71422077.html
匿名

发表评论

匿名网友

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

确定