mirror of
https://github.com/mainflux/mainflux.git
synced 2025-05-06 19:29:15 +08:00

* Add mongodb reader service Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com> * Add tests for mongodb reader service Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com> * Add documentation for mongodb reader service Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com> * Fix test function name Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com> * Update comment in docker-compose for mongodb-reader service Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
1115 lines
28 KiB
Go
1115 lines
28 KiB
Go
// Copyright (C) MongoDB, Inc. 2017-present.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License. You may obtain
|
|
// a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
package mongo
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
"github.com/mongodb/mongo-go-driver/bson"
|
|
"github.com/mongodb/mongo-go-driver/core/command"
|
|
"github.com/mongodb/mongo-go-driver/core/description"
|
|
"github.com/mongodb/mongo-go-driver/core/dispatch"
|
|
"github.com/mongodb/mongo-go-driver/core/option"
|
|
"github.com/mongodb/mongo-go-driver/core/readconcern"
|
|
"github.com/mongodb/mongo-go-driver/core/readpref"
|
|
"github.com/mongodb/mongo-go-driver/core/session"
|
|
"github.com/mongodb/mongo-go-driver/core/writeconcern"
|
|
"github.com/mongodb/mongo-go-driver/mongo/aggregateopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/changestreamopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/collectionopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/countopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/deleteopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/distinctopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/dropcollopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/findopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/insertopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/replaceopt"
|
|
"github.com/mongodb/mongo-go-driver/mongo/updateopt"
|
|
)
|
|
|
|
// Collection performs operations on a given collection.
|
|
type Collection struct {
|
|
client *Client
|
|
db *Database
|
|
name string
|
|
readConcern *readconcern.ReadConcern
|
|
writeConcern *writeconcern.WriteConcern
|
|
readPreference *readpref.ReadPref
|
|
readSelector description.ServerSelector
|
|
writeSelector description.ServerSelector
|
|
}
|
|
|
|
func newCollection(db *Database, name string, opts ...collectionopt.Option) *Collection {
|
|
collOpt, err := collectionopt.BundleCollection(opts...).Unbundle()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
rc := db.readConcern
|
|
if collOpt.ReadConcern != nil {
|
|
rc = collOpt.ReadConcern
|
|
}
|
|
|
|
wc := db.writeConcern
|
|
if collOpt.WriteConcern != nil {
|
|
wc = collOpt.WriteConcern
|
|
}
|
|
|
|
rp := db.readPreference
|
|
if collOpt.ReadPreference != nil {
|
|
rp = collOpt.ReadPreference
|
|
}
|
|
|
|
coll := &Collection{
|
|
client: db.client,
|
|
db: db,
|
|
name: name,
|
|
readPreference: rp,
|
|
readConcern: rc,
|
|
writeConcern: wc,
|
|
readSelector: db.readSelector,
|
|
writeSelector: db.writeSelector,
|
|
}
|
|
|
|
return coll
|
|
}
|
|
|
|
func (coll *Collection) copy() *Collection {
|
|
return &Collection{
|
|
client: coll.client,
|
|
db: coll.db,
|
|
name: coll.name,
|
|
readConcern: coll.readConcern,
|
|
writeConcern: coll.writeConcern,
|
|
readPreference: coll.readPreference,
|
|
readSelector: coll.readSelector,
|
|
writeSelector: coll.writeSelector,
|
|
}
|
|
}
|
|
|
|
// Clone creates a copy of this collection with updated options, if any are given.
|
|
func (coll *Collection) Clone(opts ...collectionopt.Option) (*Collection, error) {
|
|
copyColl := coll.copy()
|
|
optsColl, err := collectionopt.BundleCollection(opts...).Unbundle()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if optsColl.ReadConcern != nil {
|
|
copyColl.readConcern = optsColl.ReadConcern
|
|
}
|
|
|
|
if optsColl.WriteConcern != nil {
|
|
copyColl.writeConcern = optsColl.WriteConcern
|
|
}
|
|
|
|
if optsColl.ReadPreference != nil {
|
|
copyColl.readPreference = optsColl.ReadPreference
|
|
}
|
|
|
|
return copyColl, nil
|
|
}
|
|
|
|
// Name provides access to the name of the collection.
|
|
func (coll *Collection) Name() string {
|
|
return coll.name
|
|
}
|
|
|
|
// namespace returns the namespace of the collection.
|
|
func (coll *Collection) namespace() command.Namespace {
|
|
return command.NewNamespace(coll.db.name, coll.name)
|
|
}
|
|
|
|
// InsertOne inserts a single document into the collection. A user can supply
|
|
// a custom context to this method, or nil to default to context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the document parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// document.
|
|
//
|
|
// TODO(skriptble): Determine if we should unwrap the value for the
|
|
// InsertOneResult or just return the bson.Element or a bson.Value.
|
|
func (coll *Collection) InsertOne(ctx context.Context, document interface{},
|
|
opts ...insertopt.One) (*InsertOneResult, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
doc, err := TransformDocument(document)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
insertedID, err := ensureID(doc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// convert options into []option.InsertOptioner and dedup
|
|
oneOpts, sess, err := insertopt.BundleOne(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Insert{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Docs: []*bson.Document{doc},
|
|
Opts: oneOpts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
res, err := dispatch.Insert(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
|
|
rr, err := processWriteError(res.WriteConcernError, res.WriteErrors, err)
|
|
if rr&rrOne == 0 {
|
|
return nil, err
|
|
}
|
|
|
|
return &InsertOneResult{InsertedID: insertedID}, err
|
|
}
|
|
|
|
// InsertMany inserts the provided documents. A user can supply a custom context to this
|
|
// method.
|
|
//
|
|
// Currently, batching is not implemented for this operation. Because of this, extremely large
|
|
// sets of documents will not fit into a single BSON document to be sent to the server, so the
|
|
// operation will fail.
|
|
//
|
|
// This method uses TransformDocument to turn the documents parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// documents.
|
|
func (coll *Collection) InsertMany(ctx context.Context, documents []interface{},
|
|
opts ...insertopt.Many) (*InsertManyResult, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
result := make([]interface{}, len(documents))
|
|
docs := make([]*bson.Document, len(documents))
|
|
|
|
for i, doc := range documents {
|
|
bdoc, err := TransformDocument(doc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
insertedID, err := ensureID(bdoc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
docs[i] = bdoc
|
|
result[i] = insertedID
|
|
}
|
|
|
|
// convert options into []option.InsertOptioner and dedup
|
|
manyOpts, sess, err := insertopt.BundleMany(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Insert{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Docs: docs,
|
|
Opts: manyOpts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
res, err := dispatch.Insert(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
|
|
switch err {
|
|
case nil:
|
|
case command.ErrUnacknowledgedWrite:
|
|
return &InsertManyResult{InsertedIDs: result}, ErrUnacknowledgedWrite
|
|
default:
|
|
return nil, err
|
|
}
|
|
if len(res.WriteErrors) > 0 || res.WriteConcernError != nil {
|
|
err = BulkWriteError{
|
|
WriteErrors: writeErrorsFromResult(res.WriteErrors),
|
|
WriteConcernError: convertWriteConcernError(res.WriteConcernError),
|
|
}
|
|
}
|
|
|
|
return &InsertManyResult{InsertedIDs: result}, err
|
|
}
|
|
|
|
// DeleteOne deletes a single document from the collection. A user can supply
|
|
// a custom context to this method, or nil to default to context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter.
|
|
func (coll *Collection) DeleteOne(ctx context.Context, filter interface{},
|
|
opts ...deleteopt.Delete) (*DeleteResult, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
f, err := TransformDocument(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
deleteDocs := []*bson.Document{
|
|
bson.NewDocument(
|
|
bson.EC.SubDocument("q", f),
|
|
bson.EC.Int32("limit", 1)),
|
|
}
|
|
|
|
deleteOpts, sess, err := deleteopt.BundleDelete(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Delete{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Deletes: deleteDocs,
|
|
Opts: deleteOpts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
res, err := dispatch.Delete(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
|
|
rr, err := processWriteError(res.WriteConcernError, res.WriteErrors, err)
|
|
if rr&rrOne == 0 {
|
|
return nil, err
|
|
}
|
|
return &DeleteResult{DeletedCount: int64(res.N)}, err
|
|
}
|
|
|
|
// DeleteMany deletes multiple documents from the collection. A user can
|
|
// supply a custom context to this method, or nil to default to
|
|
// context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter.
|
|
func (coll *Collection) DeleteMany(ctx context.Context, filter interface{},
|
|
opts ...deleteopt.Delete) (*DeleteResult, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
f, err := TransformDocument(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
deleteDocs := []*bson.Document{bson.NewDocument(bson.EC.SubDocument("q", f), bson.EC.Int32("limit", 0))}
|
|
|
|
deleteOpts, sess, err := deleteopt.BundleDelete(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Delete{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Deletes: deleteDocs,
|
|
Opts: deleteOpts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
res, err := dispatch.Delete(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
|
|
rr, err := processWriteError(res.WriteConcernError, res.WriteErrors, err)
|
|
if rr&rrMany == 0 {
|
|
return nil, err
|
|
}
|
|
return &DeleteResult{DeletedCount: int64(res.N)}, err
|
|
}
|
|
|
|
func (coll *Collection) updateOrReplaceOne(ctx context.Context, filter,
|
|
update *bson.Document, sess *session.Client, opts ...option.UpdateOptioner) (*UpdateResult, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
updateDocs := []*bson.Document{
|
|
bson.NewDocument(
|
|
bson.EC.SubDocument("q", filter),
|
|
bson.EC.SubDocument("u", update),
|
|
bson.EC.Boolean("multi", false),
|
|
),
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Update{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Docs: updateDocs,
|
|
Opts: opts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
r, err := dispatch.Update(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
if err != nil && err != command.ErrUnacknowledgedWrite {
|
|
return nil, err
|
|
}
|
|
|
|
res := &UpdateResult{
|
|
MatchedCount: r.MatchedCount,
|
|
ModifiedCount: r.ModifiedCount,
|
|
}
|
|
if len(r.Upserted) > 0 {
|
|
res.UpsertedID = r.Upserted[0].ID
|
|
res.MatchedCount--
|
|
}
|
|
|
|
rr, err := processWriteError(r.WriteConcernError, r.WriteErrors, err)
|
|
if rr&rrOne == 0 {
|
|
return nil, err
|
|
}
|
|
return res, err
|
|
}
|
|
|
|
// UpdateOne updates a single document in the collection. A user can supply a
|
|
// custom context to this method, or nil to default to context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter and update parameter
|
|
// into a *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter and update.
|
|
func (coll *Collection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
|
|
options ...updateopt.Update) (*UpdateResult, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
f, err := TransformDocument(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
u, err := TransformDocument(update)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err := ensureDollarKey(u); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
updOpts, sess, err := updateopt.BundleUpdate(options...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return coll.updateOrReplaceOne(ctx, f, u, sess, updOpts...)
|
|
}
|
|
|
|
// UpdateMany updates multiple documents in the collection. A user can supply
|
|
// a custom context to this method, or nil to default to context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter and update parameter
|
|
// into a *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter and update.
|
|
func (coll *Collection) UpdateMany(ctx context.Context, filter interface{}, update interface{},
|
|
opts ...updateopt.Update) (*UpdateResult, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
f, err := TransformDocument(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
u, err := TransformDocument(update)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if err = ensureDollarKey(u); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
updateDocs := []*bson.Document{
|
|
bson.NewDocument(
|
|
bson.EC.SubDocument("q", f),
|
|
bson.EC.SubDocument("u", u),
|
|
bson.EC.Boolean("multi", true),
|
|
),
|
|
}
|
|
|
|
updOpts, sess, err := updateopt.BundleUpdate(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Update{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Docs: updateDocs,
|
|
Opts: updOpts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
r, err := dispatch.Update(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
if err != nil && err != command.ErrUnacknowledgedWrite {
|
|
return nil, err
|
|
}
|
|
res := &UpdateResult{
|
|
MatchedCount: r.MatchedCount,
|
|
ModifiedCount: r.ModifiedCount,
|
|
}
|
|
// TODO(skriptble): Is this correct? Do we only return the first upserted ID for an UpdateMany?
|
|
if len(r.Upserted) > 0 {
|
|
res.UpsertedID = r.Upserted[0].ID
|
|
res.MatchedCount--
|
|
}
|
|
|
|
rr, err := processWriteError(r.WriteConcernError, r.WriteErrors, err)
|
|
if rr&rrMany == 0 {
|
|
return nil, err
|
|
}
|
|
return res, err
|
|
}
|
|
|
|
// ReplaceOne replaces a single document in the collection. A user can supply
|
|
// a custom context to this method, or nil to default to context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter and replacement
|
|
// parameter into a *bson.Document. See TransformDocument for the list of
|
|
// valid types for filter and replacement.
|
|
func (coll *Collection) ReplaceOne(ctx context.Context, filter interface{},
|
|
replacement interface{}, opts ...replaceopt.Replace) (*UpdateResult, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
f, err := TransformDocument(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
r, err := TransformDocument(replacement)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if elem, ok := r.ElementAtOK(0); ok && strings.HasPrefix(elem.Key(), "$") {
|
|
return nil, errors.New("replacement document cannot contains keys beginning with '$")
|
|
}
|
|
|
|
repOpts, sess, err := replaceopt.BundleReplace(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
updateOptions := make([]option.UpdateOptioner, 0, len(opts))
|
|
for _, opt := range repOpts {
|
|
updateOptions = append(updateOptions, opt)
|
|
}
|
|
|
|
return coll.updateOrReplaceOne(ctx, f, r, sess, updateOptions...)
|
|
}
|
|
|
|
// Aggregate runs an aggregation framework pipeline. A user can supply a custom context to
|
|
// this method.
|
|
//
|
|
// See https://docs.mongodb.com/manual/aggregation/.
|
|
//
|
|
// This method uses TransformDocument to turn the pipeline parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// pipeline.
|
|
func (coll *Collection) Aggregate(ctx context.Context, pipeline interface{},
|
|
opts ...aggregateopt.Aggregate) (Cursor, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
pipelineArr, err := transformAggregatePipeline(pipeline)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// convert options into []option.Optioner and dedup
|
|
aggOpts, sess, err := aggregateopt.BundleAggregate(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Aggregate{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Pipeline: pipelineArr,
|
|
Opts: aggOpts,
|
|
ReadPref: coll.readPreference,
|
|
WriteConcern: coll.writeConcern,
|
|
ReadConcern: coll.readConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
return dispatch.Aggregate(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.readSelector,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
}
|
|
|
|
// Count gets the number of documents matching the filter. A user can supply a
|
|
// custom context to this method, or nil to default to context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter.
|
|
func (coll *Collection) Count(ctx context.Context, filter interface{},
|
|
opts ...countopt.Count) (int64, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
f, err := TransformDocument(filter)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
countOpts, sess, err := countopt.BundleCount(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Count{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Query: f,
|
|
Opts: countOpts,
|
|
ReadPref: coll.readPreference,
|
|
ReadConcern: coll.readConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
return dispatch.Count(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.readSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
}
|
|
|
|
// Distinct finds the distinct values for a specified field across a single
|
|
// collection. A user can supply a custom context to this method, or nil to
|
|
// default to context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter.
|
|
func (coll *Collection) Distinct(ctx context.Context, fieldName string, filter interface{},
|
|
opts ...distinctopt.Distinct) ([]interface{}, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
var f *bson.Document
|
|
var err error
|
|
if filter != nil {
|
|
f, err = TransformDocument(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
distinctOpts, sess, err := distinctopt.BundleDistinct(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Distinct{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Field: fieldName,
|
|
Query: f,
|
|
Opts: distinctOpts,
|
|
ReadPref: coll.readPreference,
|
|
ReadConcern: coll.readConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
res, err := dispatch.Distinct(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.readSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res.Values, nil
|
|
}
|
|
|
|
// Find finds the documents matching a model. A user can supply a custom context to this
|
|
// method.
|
|
//
|
|
// This method uses TransformDocument to turn the filter parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter.
|
|
func (coll *Collection) Find(ctx context.Context, filter interface{},
|
|
opts ...findopt.Find) (Cursor, error) {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
var f *bson.Document
|
|
var err error
|
|
if filter != nil {
|
|
f, err = TransformDocument(filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
findOpts, sess, err := findopt.BundleFind(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Find{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Filter: f,
|
|
Opts: findOpts,
|
|
ReadPref: coll.readPreference,
|
|
ReadConcern: coll.readConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
return dispatch.Find(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.readSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
}
|
|
|
|
// FindOne returns up to one document that matches the model. A user can
|
|
// supply a custom context to this method, or nil to default to
|
|
// context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter.
|
|
func (coll *Collection) FindOne(ctx context.Context, filter interface{},
|
|
opts ...findopt.One) *DocumentResult {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
var f *bson.Document
|
|
var err error
|
|
if filter != nil {
|
|
f, err = TransformDocument(filter)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
}
|
|
|
|
findOneOpts, sess, err := findopt.BundleOne(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
findOneOpts = append(findOneOpts, findopt.Limit(1).ConvertFindOption())
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.Find{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Filter: f,
|
|
Opts: findOneOpts,
|
|
ReadPref: coll.readPreference,
|
|
ReadConcern: coll.readConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
cursor, err := dispatch.Find(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.readSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
return &DocumentResult{cur: cursor}
|
|
}
|
|
|
|
// FindOneAndDelete find a single document and deletes it, returning the
|
|
// original in result. The document to return may be nil.
|
|
//
|
|
// A user can supply a custom context to this method, or nil to default to
|
|
// context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter parameter into a
|
|
// *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter.
|
|
func (coll *Collection) FindOneAndDelete(ctx context.Context, filter interface{},
|
|
opts ...findopt.DeleteOne) *DocumentResult {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
var f *bson.Document
|
|
var err error
|
|
if filter != nil {
|
|
f, err = TransformDocument(filter)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
}
|
|
|
|
findOpts, sess, err := findopt.BundleDeleteOne(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.FindOneAndDelete{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Query: f,
|
|
Opts: findOpts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
res, err := dispatch.FindOneAndDelete(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
return &DocumentResult{rdr: res.Value}
|
|
}
|
|
|
|
// FindOneAndReplace finds a single document and replaces it, returning either
|
|
// the original or the replaced document. The document to return may be nil.
|
|
//
|
|
// A user can supply a custom context to this method, or nil to default to
|
|
// context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter and replacement
|
|
// parameter into a *bson.Document. See TransformDocument for the list of
|
|
// valid types for filter and replacement.
|
|
func (coll *Collection) FindOneAndReplace(ctx context.Context, filter interface{},
|
|
replacement interface{}, opts ...findopt.ReplaceOne) *DocumentResult {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
f, err := TransformDocument(filter)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
r, err := TransformDocument(replacement)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
if elem, ok := r.ElementAtOK(0); ok && strings.HasPrefix(elem.Key(), "$") {
|
|
return &DocumentResult{err: errors.New("replacement document cannot contains keys beginning with '$")}
|
|
}
|
|
|
|
findOpts, sess, err := findopt.BundleReplaceOne(opts...).Unbundle(true)
|
|
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.FindOneAndReplace{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Query: f,
|
|
Replacement: r,
|
|
Opts: findOpts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
res, err := dispatch.FindOneAndReplace(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
return &DocumentResult{rdr: res.Value}
|
|
}
|
|
|
|
// FindOneAndUpdate finds a single document and updates it, returning either
|
|
// the original or the updated. The document to return may be nil.
|
|
//
|
|
// A user can supply a custom context to this method, or nil to default to
|
|
// context.Background().
|
|
//
|
|
// This method uses TransformDocument to turn the filter and update parameter
|
|
// into a *bson.Document. See TransformDocument for the list of valid types for
|
|
// filter and update.
|
|
func (coll *Collection) FindOneAndUpdate(ctx context.Context, filter interface{},
|
|
update interface{}, opts ...findopt.UpdateOne) *DocumentResult {
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
f, err := TransformDocument(filter)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
u, err := TransformDocument(update)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
if elem, ok := u.ElementAtOK(0); !ok || !strings.HasPrefix(elem.Key(), "$") {
|
|
return &DocumentResult{err: errors.New("update document must contain key beginning with '$")}
|
|
}
|
|
|
|
findOpts, sess, err := findopt.BundleUpdateOne(opts...).Unbundle(true)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
err = coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
oldns := coll.namespace()
|
|
cmd := command.FindOneAndUpdate{
|
|
NS: command.Namespace{DB: oldns.DB, Collection: oldns.Collection},
|
|
Query: f,
|
|
Update: u,
|
|
Opts: findOpts,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
|
|
res, err := dispatch.FindOneAndUpdate(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
if err != nil {
|
|
return &DocumentResult{err: err}
|
|
}
|
|
|
|
return &DocumentResult{rdr: res.Value}
|
|
}
|
|
|
|
// Watch returns a change stream cursor used to receive notifications of changes to the collection.
|
|
// This method is preferred to running a raw aggregation with a $changeStream stage because it
|
|
// supports resumability in the case of some errors.
|
|
func (coll *Collection) Watch(ctx context.Context, pipeline interface{},
|
|
opts ...changestreamopt.ChangeStream) (Cursor, error) {
|
|
return newChangeStream(ctx, coll, pipeline, opts...)
|
|
}
|
|
|
|
// Indexes returns the index view for this collection.
|
|
func (coll *Collection) Indexes() IndexView {
|
|
return IndexView{coll: coll}
|
|
}
|
|
|
|
// Drop drops this collection from database.
|
|
func (coll *Collection) Drop(ctx context.Context, opts ...dropcollopt.DropColl) error {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
var sess *session.Client
|
|
for _, opt := range opts {
|
|
if conv, ok := opt.(dropcollopt.DropCollSession); ok {
|
|
sess = conv.ConvertDropCollSession()
|
|
}
|
|
}
|
|
|
|
err := coll.client.ValidSession(sess)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd := command.DropCollection{
|
|
DB: coll.db.name,
|
|
Collection: coll.name,
|
|
WriteConcern: coll.writeConcern,
|
|
Session: sess,
|
|
Clock: coll.client.clock,
|
|
}
|
|
_, err = dispatch.DropCollection(
|
|
ctx, cmd,
|
|
coll.client.topology,
|
|
coll.writeSelector,
|
|
coll.client.id,
|
|
coll.client.topology.SessionPool,
|
|
)
|
|
if err != nil && !command.IsNotFound(err) {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|