diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..98e250f --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,114 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:586ea76dbd0374d6fb649a91d70d652b7fe0ccffb8910a77468e7702e7901f3d" + name = "github.com/go-stack/stack" + packages = ["."] + pruneopts = "UT" + revision = "2fee6af1a9795aafbe0253a0cfbdf668e1fb8a9a" + version = "v1.8.0" + +[[projects]] + branch = "master" + digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009" + name = "github.com/golang/snappy" + packages = ["."] + pruneopts = "UT" + revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" + +[[projects]] + digest = "1:820eeb72f78947f9b2e87bd99a57b5cc791c8fb47676a5c3824d408441ab1546" + name = "github.com/mongodb/mongo-go-driver" + packages = [ + "bson", + "bson/bsoncodec", + "bson/bsonrw", + "bson/bsontype", + "bson/primitive", + "event", + "internal", + "mongo", + "mongo/options", + "mongo/readconcern", + "mongo/readpref", + "mongo/writeconcern", + "tag", + "version", + "x/bsonx", + "x/bsonx/bsoncore", + "x/mongo/driver", + "x/mongo/driver/auth", + "x/mongo/driver/auth/internal/gssapi", + "x/mongo/driver/session", + "x/mongo/driver/topology", + "x/mongo/driver/uuid", + "x/network/address", + "x/network/command", + "x/network/compressor", + "x/network/connection", + "x/network/connstring", + "x/network/description", + "x/network/result", + "x/network/wiremessage", + ] + pruneopts = "UT" + revision = "29905d4bda472574c8b499e2f93b5b2747d8fbd7" + version = "v0.1.0" + +[[projects]] + branch = "master" + digest = "1:40fdfd6ab85ca32b6935853bbba35935dcb1d796c8135efd85947566c76e662e" + name = "github.com/xdg/scram" + packages = ["."] + pruneopts = "UT" + revision = "7eeb5667e42c09cb51bf7b7c28aea8c56767da90" + +[[projects]] + branch = "master" + digest = "1:f5c1d04bc09c644c592b45b9f0bad4030521b1a7d11c7dadbb272d9439fa6e8e" + name = "github.com/xdg/stringprep" + packages = ["."] + pruneopts = "UT" + revision = "73f8eece6fdcd902c185bf651de50f3828bed5ed" + +[[projects]] + branch = "master" + digest = "1:f92f6956e4059f6a3efc14924d2dd58ba90da25cc57fe07ae3779ef2f5e0c5f2" + name = "golang.org/x/crypto" + packages = ["pbkdf2"] + pruneopts = "UT" + revision = "505ab145d0a99da450461ae2c1a9f6cd10d1f447" + +[[projects]] + branch = "master" + digest = "1:5e4d81c50cffcb124b899e4f3eabec3930c73532f0096c27f94476728ba03028" + name = "golang.org/x/sync" + packages = ["semaphore"] + pruneopts = "UT" + revision = "42b317875d0fa942474b76e1b46a6060d720ae6e" + +[[projects]] + digest = "1:8029e9743749d4be5bc9f7d42ea1659471767860f0cdc34d37c3111bd308a295" + name = "golang.org/x/text" + packages = [ + "internal/gen", + "internal/triegen", + "internal/ucd", + "transform", + "unicode/cldr", + "unicode/norm", + ] + pruneopts = "UT" + revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" + version = "v0.3.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/mongodb/mongo-go-driver/bson/primitive", + "github.com/mongodb/mongo-go-driver/mongo", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..56e4e49 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,34 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/mongodb/mongo-go-driver" + version = "0.1.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/mongox/base/objectid.go b/mongox/base/objectid.go new file mode 100644 index 0000000..5ee9ad7 --- /dev/null +++ b/mongox/base/objectid.go @@ -0,0 +1,20 @@ +package base + +import ( + "github.com/mongodb/mongo-go-driver/bson/primitive" +) + +// ObjectID is a structure with objectId as an _id field +type ObjectID struct { + ID primitive.ObjectID `bson:"_id" json:"_id"` +} + +// GetID returns an _id +func (db *ObjectID) GetID() primitive.ObjectID { + return db.ID +} + +// SetID sets an _id +func (db *ObjectID) SetID(id primitive.ObjectID) { + db.ID = id +} diff --git a/mongox/common.go b/mongox/common.go new file mode 100644 index 0000000..1fa4127 --- /dev/null +++ b/mongox/common.go @@ -0,0 +1,13 @@ +package mongox + +type Saver interface { + Save(db *Database) error +} + +type Deleter interface { + Delete(db *Database) error +} + +type Loader interface { + Load(db *Database, filters ...interface{}) error +} diff --git a/mongox/common/loadone.go b/mongox/common/loadone.go new file mode 100644 index 0000000..c589ca5 --- /dev/null +++ b/mongox/common/loadone.go @@ -0,0 +1,35 @@ +package common + +import ( + "github.com/mainnika/mongox-go-driver/mongox" + "github.com/mainnika/mongox-go-driver/mongox/errors" + "github.com/mainnika/mongox-go-driver/mongox/query" + "github.com/mongodb/mongo-go-driver/mongo" + "github.com/mongodb/mongo-go-driver/mongo/options" +) + +// LoadOne function loads a first single target document by a query +func LoadOne(db *mongox.Database, target interface{}, composed *query.Query) error { + + collection := db.GetCollectionOf(target) + opts := &options.FindOneOptions{} + + if composed.Sorter() != nil { + opts.Sort = composed.Sorter().Sort() + } + + result := collection.FindOne(db.Context(), composed.M(), opts) + if result.Err() != nil { + return errors.InternalErrorf("can't create find one result: %s", result.Err()) + } + + err := result.Decode(target) + if err == mongo.ErrNoDocuments { + return errors.NotFoundErrorf("%s", err) + } + if err != nil { + return errors.InternalErrorf("can't decode desult: %s", err) + } + + return nil +} diff --git a/mongox/database.go b/mongox/database.go new file mode 100644 index 0000000..9f0362f --- /dev/null +++ b/mongox/database.go @@ -0,0 +1,80 @@ +package mongox + +import ( + "context" + "reflect" + + "github.com/mainnika/mongox-go-driver/mongox/errors" + "github.com/mongodb/mongo-go-driver/mongo" +) + +// Database handler +type Database struct { + client *mongo.Client + dbname string + ctx context.Context +} + +// NewDatabase function creates new database instance with mongo client and empty context +func NewDatabase(client *mongo.Client, dbname string) *Database { + + db := &Database{} + db.client = client + db.dbname = dbname + + return db +} + +// Client function returns a mongo client +func (d *Database) Client() *mongo.Client { + return d.client +} + +// Context function returns a context +func (d *Database) Context() context.Context { + return d.ctx +} + +// Name function returns a database name +func (d *Database) Name() string { + return d.dbname +} + +// New function creates new database context with same client +func (d *Database) New(ctx context.Context) *Database { + + if ctx != nil { + ctx = context.Background() + } + + return &Database{ + client: d.client, + dbname: d.dbname, + ctx: ctx, + } +} + +// GetCollectionOf returns the collection object by the «collection» tag of the given document; +// the «collection» tag should exists, e.g.: +// type Foobar struct { +// base.ObjectID `bson:",inline" json:",inline" collection:"foobars"` +// ... +// Will panic if there is no «collection» tag +func (d *Database) GetCollectionOf(document interface{}) *mongo.Collection { + + el := reflect.TypeOf(document).Elem() + numField := el.NumField() + + for i := 0; i < numField; i++ { + field := el.Field(i) + tag := field.Tag + found, ok := tag.Lookup("collection") + if !ok { + continue + } + + return d.client.Database(d.dbname).Collection(found) + } + + panic(errors.InternalErrorf("document %v does not have a collection tag", document)) +} diff --git a/mongox/errors/internalerror.go b/mongox/errors/internalerror.go new file mode 100644 index 0000000..287c13f --- /dev/null +++ b/mongox/errors/internalerror.go @@ -0,0 +1,16 @@ +package errors + +import "fmt" + +// InternalError error +type InternalError string + +// Error message +func (ie InternalError) Error() string { + return fmt.Sprintf("internal error, %s", string(ie)) +} + +// InternalErrorf function creates an instance of InternalError +func InternalErrorf(format string, params ...interface{}) error { + return InternalError(fmt.Sprintf(format, params...)) +} diff --git a/mongox/errors/notfound.go b/mongox/errors/notfound.go new file mode 100644 index 0000000..5ddef39 --- /dev/null +++ b/mongox/errors/notfound.go @@ -0,0 +1,16 @@ +package errors + +import "fmt" + +// NotFound error +type NotFound string + +// Error message +func (nf NotFound) Error() string { + return fmt.Sprintf("can not find, %s", string(nf)) +} + +// NotFoundErrorf function creates an instance of BadRequestError +func NotFoundErrorf(format string, params ...interface{}) error { + return NotFound(fmt.Sprintf(format, params...)) +} diff --git a/mongox/query/compose.go b/mongox/query/compose.go new file mode 100644 index 0000000..870e9cf --- /dev/null +++ b/mongox/query/compose.go @@ -0,0 +1,53 @@ +package query + +import ( + "github.com/mainnika/mongox-go-driver/mongox/errors" + "github.com/mongodb/mongo-go-driver/bson" +) + +// ComposeQuery is a function to compose filters into a single query +func Compose(filters ...interface{}) *Query { + + q := &Query{} + + for _, f := range filters { + + ok := false + ok = ok || applyBson(q, f) + ok = ok || applyLimits(q, f) + + if !ok { + panic(errors.InternalErrorf("unknown filter %v", f)) + } + } + + return q +} + +// applyBson is a fallback for a custom bson.M +func applyBson(q *Query, f interface{}) bool { + + switch f := f.(type) { + case bson.M: + q.And(f) + default: + return false + } + + return true +} + +// applyLimits extends query with contol functions +func applyLimits(q *Query, f interface{}) bool { + + switch f := f.(type) { + case Limiter: + q.limiter = f + case Sorter: + q.sorter = f + default: + return false + } + + return true +} diff --git a/mongox/query/query.go b/mongox/query/query.go new file mode 100644 index 0000000..ee6ff60 --- /dev/null +++ b/mongox/query/query.go @@ -0,0 +1,60 @@ +package query + +import ( + "github.com/mongodb/mongo-go-driver/bson" + + "reflect" +) + +// Query is an enchanched bson.M map +type Query struct { + m bson.M + limiter Limiter + sorter Sorter +} + +// And function pushes the elem query to the $and array of the query +func (q *Query) And(elem bson.M) *Query { + + if q.m == nil { + q.m = bson.M{} + } + + queries, exists := q.m["$and"].(bson.A) + + if !exists { + q.m["$and"] = bson.A{elem} + return q + } + + q.m["$and"] = append(queries, elem) + + return q +} + +// Limiter is a limit function for a query +func (q *Query) Limiter() Limiter { + + return q.limiter +} + +// Sorter is a sort rule for a query +func (q *Query) Sorter() Sorter { + + return q.sorter +} + +// Empty checks the query for any content +func (q *Query) Empty() bool { + + qv := reflect.ValueOf(q) + keys := qv.MapKeys() + + return len(keys) == 0 +} + +// M returns underlying query map +func (q *Query) M() bson.M { + + return q.m +} diff --git a/mongox/query/sort.go b/mongox/query/sort.go new file mode 100644 index 0000000..1be7281 --- /dev/null +++ b/mongox/query/sort.go @@ -0,0 +1,20 @@ +package query + +import ( + "github.com/mongodb/mongo-go-driver/bson" +) + +// Sorter is a filter to sort the data before query +type Sorter interface { + Sort() bson.M +} + +// Sort is a simple implementations of the Sorter filter +type Sort bson.M + +var _ Sorter = &Sort{} + +// Sort returns a slice of fields which have to be sorted +func (f Sort) Sort() bson.M { + return bson.M(f) +}