13 Commits

Author SHA1 Message Date
Nikita Tokarchuk 87708fee04 Add callback support to a saving 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 06f6f48f11 Add context support to a saving 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 28be1f46b9 Add filters support for a saving 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 848703d56d Add callback support to a single deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 8fcd764a8c Add context query support to a single deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 50e947b203 Invoke onclose at the end of an array deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 73dc4974a2 Add query context support for an array deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 08397650c6 Add filters support for an array deleting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk ee4bf46662 Add callback support for a counting 2020-07-23 01:44:22 +02:00
Nikita Tokarchuk 9fb3094b87 Use reflect2 2020-07-23 00:00:53 +02:00
Nikita Tokarchuk b22b0f0919 Add query to the context 2020-07-23 00:00:53 +02:00
Nikita Tokarchuk 951e5f5bef Add onclose callback 2020-07-23 00:00:53 +02:00
Nikita Tokarchuk b796d5ac3b Make callback type 2020-07-23 00:00:53 +02:00
19 changed files with 133 additions and 104 deletions
+2
View File
@@ -3,6 +3,8 @@ module github.com/mainnika/mongox-go-driver/v2
require (
github.com/google/go-cmp v0.3.0 // indirect
github.com/klauspost/compress v1.10.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.5.1
github.com/xdg/stringprep v1.0.0 // indirect
+7
View File
@@ -44,11 +44,17 @@ github.com/klauspost/compress v1.10.1 h1:a/QY0o9S6wCi0XhxaMX/QmusicNUqCqFugR6WKP
github.com/klauspost/compress v1.10.1/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@@ -113,6 +119,7 @@ golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
+2 -2
View File
@@ -3,10 +3,10 @@ package base
import (
"fmt"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox"
"github.com/mainnika/mongox-go-driver/v2/mongox/utils"
)
// GetID returns source document id
@@ -63,7 +63,7 @@ func getObjectOrPanic(source mongox.JSONBased) (id primitive.D) {
func getInterfaceOrPanic(source mongox.InterfaceBased) (id interface{}) {
id = source.GetID()
if !utils.IsNil(id) {
if !reflect2.IsNil(id) {
return id
}
-19
View File
@@ -1,19 +0,0 @@
package database
import (
"context"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
func onDecode(ctx context.Context, iter interface{}, callbacks ...query.OnDecode) (err error) {
for _, cb := range callbacks {
err = cb(ctx, iter)
if err != nil {
return
}
}
return
}
+4 -1
View File
@@ -13,11 +13,14 @@ func (d *Database) Count(target interface{}, filters ...interface{}) (result int
collection := d.GetCollectionOf(target)
opts := options.Count()
composed := query.Compose(filters...)
ctx := query.WithContext(d.Context(), composed)
opts.Limit = composed.Limiter()
opts.Skip = composed.Skipper()
result, err = collection.CountDocuments(d.Context(), composed.M(), opts)
result, err = collection.CountDocuments(ctx, composed.M(), opts)
_ = composed.OnClose().Invoke(ctx, target)
return
}
+9 -2
View File
@@ -8,10 +8,11 @@ import (
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
)
// DeleteArray removes documents list from a database by their ids
func (d *Database) DeleteArray(target interface{}) (err error) {
func (d *Database) DeleteArray(target interface{}, filters ...interface{}) (err error) {
targetV := reflect.ValueOf(target)
targetT := targetV.Type()
@@ -37,6 +38,8 @@ func (d *Database) DeleteArray(target interface{}) (err error) {
collection := d.GetCollectionOf(zeroElem.Interface())
opts := options.Delete()
ids := primitive.A{}
composed := query.Compose(filters...)
ctx := query.WithContext(d.Context(), composed)
for i := 0; i < targetLen; i++ {
elem := targetSliceV.Index(i)
@@ -47,7 +50,11 @@ func (d *Database) DeleteArray(target interface{}) (err error) {
return fmt.Errorf("can't delete zero elements")
}
result, err := collection.DeleteMany(d.Context(), primitive.M{"_id": primitive.M{"$in": ids}}, opts)
composed.And(primitive.M{"_id": primitive.M{"$in": ids}})
defer composed.OnClose().Invoke(ctx, target)
result, err := collection.DeleteMany(ctx, composed.M(), opts)
if err != nil {
return
}
+14 -3
View File
@@ -4,12 +4,12 @@ import (
"fmt"
"time"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo/options"
"github.com/mainnika/mongox-go-driver/v2/mongox/base"
"github.com/mainnika/mongox-go-driver/v2/mongox/query"
"github.com/mainnika/mongox-go-driver/v2/mongox/utils"
)
// DeleteOne removes a document from a database and then returns it into target
@@ -19,10 +19,11 @@ func (d *Database) DeleteOne(target interface{}, filters ...interface{}) (err er
opts := &options.FindOneAndDeleteOptions{}
composed := query.Compose(filters...)
protected := base.GetProtection(target)
ctx := query.WithContext(d.Context(), composed)
opts.Sort = composed.Sorter()
if !utils.IsNil(target) {
if !reflect2.IsNil(target) {
composed.And(primitive.M{"_id": base.GetID(target)})
}
@@ -32,12 +33,22 @@ func (d *Database) DeleteOne(target interface{}, filters ...interface{}) (err er
protected.V = time.Now().Unix()
}
result := collection.FindOneAndDelete(d.Context(), composed.M(), opts)
defer composed.OnClose().Invoke(ctx, target)
result := collection.FindOneAndDelete(ctx, composed.M(), opts)
if result.Err() != nil {
return fmt.Errorf("can't create find one and delete result: %w", result.Err())
}
err = result.Decode(target)
if err != nil {
return
}
err = composed.OnDecode().Invoke(ctx, target)
if err != nil {
return
}
return
}
+8 -5
View File
@@ -34,6 +34,7 @@ func (d *Database) LoadArray(target interface{}, filters ...interface{}) (err er
composed := query.Compose(filters...)
zeroElem := reflect.Zero(targetSliceElemT)
hasPreloader, _ := composed.Preloader()
ctx := query.WithContext(d.Context(), composed)
var result *mongox.Cursor
var i int
@@ -48,7 +49,9 @@ func (d *Database) LoadArray(target interface{}, filters ...interface{}) (err er
return
}
for i = 0; result.Next(d.Context()); {
defer composed.OnClose().Invoke(ctx, target)
for i = 0; result.Next(ctx); {
var elem interface{}
@@ -65,13 +68,13 @@ func (d *Database) LoadArray(target interface{}, filters ...interface{}) (err er
err = result.Decode(elem)
}
if err != nil {
_ = result.Close(d.Context())
_ = result.Close(ctx)
return
}
err = onDecode(d.ctx, elem, composed.OnDecode()...)
err = composed.OnDecode().Invoke(ctx, elem)
if err != nil {
_ = result.Close(d.Context())
_ = result.Close(ctx)
return
}
@@ -81,5 +84,5 @@ func (d *Database) LoadArray(target interface{}, filters ...interface{}) (err er
targetSliceV = targetSliceV.Slice(0, i)
targetV.Elem().Set(targetSliceV)
return result.Close(d.Context())
return result.Close(ctx)
}
+5 -2
View File
@@ -13,6 +13,7 @@ func (d *Database) LoadOne(target interface{}, filters ...interface{}) (err erro
composed := query.Compose(append(filters, query.Limit(1))...)
hasPreloader, _ := composed.Preloader()
ctx := query.WithContext(d.Context(), composed)
var result *mongox.Cursor
@@ -25,7 +26,9 @@ func (d *Database) LoadOne(target interface{}, filters ...interface{}) (err erro
return fmt.Errorf("can't create find result: %w", err)
}
hasNext := result.Next(d.Context())
defer composed.OnClose().Invoke(ctx, target)
hasNext := result.Next(ctx)
if result.Err() != nil {
return err
}
@@ -40,7 +43,7 @@ func (d *Database) LoadOne(target interface{}, filters ...interface{}) (err erro
return
}
err = onDecode(d.ctx, target, composed.OnDecode()...)
err = composed.OnDecode().Invoke(ctx, target)
if err != nil {
return
}
+2 -1
View File
@@ -14,6 +14,7 @@ func (d *Database) LoadStream(target interface{}, filters ...interface{}) (loade
composed := query.Compose(filters...)
hasPreloader, _ := composed.Preloader()
ctx := query.WithContext(d.Context(), composed)
if hasPreloader {
cursor, err = d.createAggregateLoad(target, composed)
@@ -25,7 +26,7 @@ func (d *Database) LoadStream(target interface{}, filters ...interface{}) (loade
return
}
loader = &StreamLoader{cur: cursor, ctx: d.Context(), target: target, query: composed}
loader = &StreamLoader{cur: cursor, ctx: ctx, target: target, query: composed}
return
}
+19 -4
View File
@@ -12,13 +12,16 @@ import (
)
// SaveOne saves a single source document to the database
func (d *Database) SaveOne(source interface{}) (err error) {
func (d *Database) SaveOne(source interface{}, filters ...interface{}) (err error) {
collection := d.GetCollectionOf(source)
opts := options.FindOneAndReplace()
id := base.GetID(source)
protected := base.GetProtection(source)
composed := query.Compose(bson.M{"_id": id})
composed := query.Compose(filters...)
ctx := query.WithContext(d.Context(), composed)
composed.And(bson.M{"_id": id})
opts.SetUpsert(true)
opts.SetReturnDocument(options.After)
@@ -29,10 +32,22 @@ func (d *Database) SaveOne(source interface{}) (err error) {
protected.V = time.Now().Unix()
}
result := collection.FindOneAndReplace(d.Context(), composed.M(), source, opts)
defer composed.OnClose().Invoke(ctx, source)
result := collection.FindOneAndReplace(ctx, composed.M(), source, opts)
if result.Err() != nil {
return result.Err()
}
return result.Decode(source)
err = result.Decode(source)
if err != nil {
return
}
err = composed.OnDecode().Invoke(ctx, source)
if err != nil {
return
}
return
}
+4 -1
View File
@@ -42,7 +42,7 @@ func (l *StreamLoader) Decode() (err error) {
return
}
err = onDecode(l.ctx, l.target, l.query.OnDecode()...)
err = l.query.OnDecode().Invoke(l.ctx, l.target)
if err != nil {
return
}
@@ -72,6 +72,9 @@ func (l *StreamLoader) Cursor() (cursor *mongox.Cursor) {
// Close cursor
func (l *StreamLoader) Close() (err error) {
_ = l.query.OnClose().Invoke(l.ctx, l.target)
return l.cur.Close(l.ctx)
}
+2 -2
View File
@@ -22,12 +22,12 @@ type Database interface {
New(ctx context.Context) (db Database)
GetCollectionOf(document interface{}) (collection *Collection)
Count(target interface{}, filters ...interface{}) (count int64, err error)
DeleteArray(target interface{}) (err error)
DeleteArray(target interface{}, filters ...interface{}) (err error)
DeleteOne(target interface{}, filters ...interface{}) (err error)
LoadArray(target interface{}, filters ...interface{}) (err error)
LoadOne(target interface{}, filters ...interface{}) (err error)
LoadStream(target interface{}, filters ...interface{}) (loader StreamLoader, err error)
SaveOne(source interface{}) (err error)
SaveOne(source interface{}, filters ...interface{}) (err error)
IndexEnsure(cfg interface{}, document interface{}) (err error)
}
+20 -1
View File
@@ -4,4 +4,23 @@ import (
"context"
)
type OnDecode func(ctx context.Context, iter interface{}) (err error)
type Callback func(ctx context.Context, iter interface{}) (err error)
type Callbacks []Callback
type (
OnDecode Callback
OnClose Callback
)
// Invoke callbacks sequence
func (c Callbacks) Invoke(ctx context.Context, iter interface{}) (err error) {
for _, cb := range c {
err = cb(ctx, iter)
if err != nil {
return
}
}
return
}
+9 -4
View File
@@ -3,11 +3,11 @@ package query
import (
"fmt"
"github.com/modern-go/reflect2"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"github.com/mainnika/mongox-go-driver/v2/mongox/base/protection"
"github.com/mainnika/mongox-go-driver/v2/mongox/utils"
)
// Compose is a function to compose filters into a single query
@@ -27,7 +27,7 @@ func Compose(filters ...interface{}) (query *Query) {
// Push applies single filter to a query
func Push(q *Query, f interface{}) (ok bool) {
if utils.IsNil(f) {
if reflect2.IsNil(f) {
return true
}
@@ -129,9 +129,14 @@ func applyCallbacks(q *Query, f interface{}) (ok bool) {
switch cb := f.(type) {
case OnDecode:
q.ondecode = append(q.ondecode, cb)
ok = true
q.ondecode = append(q.ondecode, Callback(cb))
case OnClose:
q.onclose = append(q.onclose, Callback(cb))
default:
return
}
ok = true
return
}
+19
View File
@@ -0,0 +1,19 @@
package query
import (
"context"
)
type ctxQueryKey struct{}
// GetFromContext function extracts the request data from context
func GetFromContext(ctx context.Context) (q *Query, ok bool) {
q, ok = ctx.Value(ctxQueryKey{}).(*Query)
return
}
// WithContext function creates the new context with request data
func WithContext(ctx context.Context, q *Query) (withQuery context.Context) {
withQuery = context.WithValue(ctx, ctxQueryKey{}, q)
return
}
+7 -2
View File
@@ -11,7 +11,8 @@ type Query struct {
sorter Sorter
skipper Skipper
preloader Preloader
ondecode []OnDecode
ondecode Callbacks
onclose Callbacks
}
// And function pushes the elem query to the $and array of the query
@@ -77,10 +78,14 @@ func (q *Query) Preloader() (ok bool, preloads []string) {
}
// OnDecode callback is called after the mongo decode function
func (q *Query) OnDecode() (callbacks []OnDecode) {
func (q *Query) OnDecode() (callbacks Callbacks) {
return q.ondecode
}
func (q *Query) OnClose() (callbacks Callbacks) {
return q.onclose
}
// Empty checks the query for any content
func (q *Query) Empty() (isEmpty bool) {
return len(q.m) == 0
-23
View File
@@ -1,23 +0,0 @@
package utils
import (
"unsafe"
)
// IsNil function evaluates the interface value to nil
func IsNil(i interface{}) (isNil bool) {
type iface struct {
_ unsafe.Pointer
ptr unsafe.Pointer
}
unpacked := (*iface)(unsafe.Pointer(&i))
if unpacked.ptr == nil {
isNil = true
return
}
isNil = *(*unsafe.Pointer)(unpacked.ptr) == nil
return
}
-32
View File
@@ -1,32 +0,0 @@
package utils
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsNil(t *testing.T) {
testvalues := []struct {
i interface{}
isnil bool
}{
{nil, true},
{(*string)(nil), true},
{([]string)(nil), true},
{(map[string]string)(nil), true},
{(func() bool)(nil), true},
{(chan func() bool)(nil), true},
{"", true},
{0, true},
{append(([]string)(nil), ""), false},
{[]string{}, false},
{1, false},
{"1", false},
}
for _, tt := range testvalues {
assert.Equal(t, tt.isnil, IsNil(tt.i))
}
}