func (opt *DBOptions) IgnoreProjection() *DBOptions {
	opt.ignoreProjection = true
	return opt
}
Ask the database to ignore any projection parameters. This might be reasonable when the downstream services
wish to perform further actions on the complete version of the resource.
package v2
import (
	"context"
	"fmt"
	"github.com/imulab/go-scim/pkg/v2/crud"
	"github.com/imulab/go-scim/pkg/v2/crud/expr"
	"github.com/imulab/go-scim/pkg/v2/db"
	"github.com/imulab/go-scim/pkg/v2/prop"
	"github.com/imulab/go-scim/pkg/v2/spec"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
	"go.mongodb.org/mongo-driver/mongo/options"
	"strconv"
)
func DB(resourceType *spec.ResourceType, coll *mongo.Collection, opt *DBOptions) db.DB {
	d := &mongoDB{
		resourceType: resourceType,
		superAttr:    resourceType.SuperAttribute(true),
		coll:         coll,
		t:            newTransformer(resourceType),
		opt:          opt,
	}
	d.ensureIndex()
	return d
}
type mongoDB struct {
	superAttr    *spec.Attribute
	resourceType *spec.ResourceType
	coll         *mongo.Collection
	t            *transformer
	opt          *DBOptions
}
func (d *mongoDB) Insert(ctx context.Context, resource *prop.Resource) error {
	_, err := d.coll.InsertOne(ctx, newBsonAdapter(resource), options.InsertOne())
	if err != nil {
		return fmt.Errorf("%w: %v", spec.ErrInternal, err)
	}
	return nil
}
func (d *mongoDB) Count(ctx context.Context, filter string) (int, error) {
	tf, err := d.mongoFilter(filter)
	if err != nil {
		return 0, err
	}
	n, err := d.coll.CountDocuments(ctx, tf, options.Count())
	if err != nil {
		return 0, fmt.Errorf("%w: %v", spec.ErrInternal, err)
	}
	return int(n), nil
}
func (d *mongoDB) Get(ctx context.Context, id string, projection *crud.Projection) (*prop.Resource, error) {
	opt := options.FindOne()
	if !d.opt.ignoreProjection && projection != nil {
		opt = opt.SetProjection(d.mongoProjection(projection))
	}
	tf, err := d.mongoFilter(fmt.Sprintf("id eq %s", strconv.Quote(id)))
	if err != nil {
		return nil, err
	}
	sr := d.coll.FindOne(ctx, tf, opt)
	if err := sr.Err(); err != nil {
		if err == mongo.ErrNoDocuments {
			return nil, fmt.Errorf("%w: resource not found by id '%s'", spec.ErrNotFound, id)
		}
		return nil, fmt.Errorf("%w: %v", spec.ErrInternal, err)
	}
	w := newResourceUnmarshaler(d.resourceType)
	if err := sr.Decode(w); err != nil {
		return nil, err
	}
	return w.Resource(), nil
}
func (d *mongoDB) Replace(ctx context.Context, ref *prop.Resource, resource *prop.Resource) error {
	var (
		id      = ref.IdOrEmpty()
		version = ref.MetaVersionOrEmpty()
	)
	tf, err := d.mongoFilter(fmt.Sprintf("(id eq %s) and (meta.version eq %s)", strconv.Quote(id), strconv.Quote(version)))
	if err != nil {
		return err
	}
	sr := d.coll.FindOneAndReplace(ctx, tf, newBsonAdapter(resource), options.FindOneAndReplace())
	if err := sr.Err(); err != nil {
		if err == mongo.ErrNoDocuments {
			return d.errNotFoundOrModified(id)
		}
		return err
	}
	return nil
}
func (d *mongoDB) Delete(ctx context.Context, resource *prop.Resource) error {
	var (
		id      = resource.IdOrEmpty()
		version = resource.MetaVersionOrEmpty()
	)
	tf, err := d.mongoFilter(fmt.Sprintf("(id eq %s) and (meta.version eq %s)", strconv.Quote(id), strconv.Quote(version)))
	if err != nil {
		return err
	}
	sr := d.coll.FindOneAndDelete(ctx, tf, options.FindOneAndDelete())
	if err := sr.Err(); err != nil {
		if err == mongo.ErrNoDocuments {
			return d.errNotFoundOrModified(id)
		}
		return fmt.Errorf("%w: %v", spec.ErrInternal, err)
	}
	return nil
}
func (d *mongoDB) Query(ctx context.Context, filter string, sort *crud.Sort, pagination *crud.Pagination, projection *crud.Projection) ([]*prop.Resource, error) {
	opt := options.Find()
	tf, err := d.mongoFilter(filter)
	if err != nil {
		return nil, err
	}
	if sort != nil {
		opt.SetSort(d.mongoSort(sort))
	}
	if pagination != nil {
		skip, limit := d.mongoPagination(pagination)
		opt.SetSkip(skip)
		opt.SetLimit(limit)
	}
	if !d.opt.ignoreProjection && projection != nil {
		opt.SetProjection(d.mongoProjection(projection))
	}
	cursor, err := d.coll.Find(ctx, tf, opt)
	if err != nil {
		return nil, fmt.Errorf("%w: %v", spec.ErrInternal, err)
	}
	defer func() {
		_ = cursor.Close(ctx)
	}()
	results := make([]*prop.Resource, 0)
	for cursor.Next(ctx) {
		w := newResourceUnmarshaler(d.resourceType)
		if err := cursor.Decode(w); err != nil {
			return nil, err
		}
		results = append(results, w.Resource())
	}
	if err := cursor.Err(); err != nil {
		return nil, err
	}
	return results, nil
}
func (d *mongoDB) mongoPathFor(path string) string {
	curAttr := d.superAttr
	cursor, err := expr.CompilePath(path)
	if err != nil {
		return ""
	}
	if cursor.Token() == d.resourceType.Schema().ID() {
		cursor = cursor.Next()
	}
	if cursor == nil {
		return ""
	}
	for cursor != nil {
		curAttr = curAttr.SubAttributeForName(cursor.Token())
		if curAttr == nil {
			return ""
		}
		cursor = cursor.Next()
	}
	mp := curAttr.Path()
	if md, ok := metadataHub[curAttr.ID()]; ok {
		mp = md.MongoPath
	}
	return mp
}
func (d *mongoDB) mongoSort(sort *crud.Sort) bson.D {
	var by string
	{
		if len(sort.By) > 0 {
			by = d.mongoPathFor(sort.By)
		}
		if len(by) == 0 {
			by = "_id"
		}
	}
	switch sort.Order {
	case crud.SortAsc, crud.SortDefault:
		return bson.D{{Key: by, Value: 1}}
	case crud.SortDesc:
		return bson.D{{Key: by, Value: -1}}
	default:
		panic("invalid sort order")
	}
}
func (d *mongoDB) mongoPagination(pagination *crud.Pagination) (skip int64, limit int64) {
	skip = int64(pagination.StartIndex - 1)
	limit = int64(pagination.Count)
	return
}
func (d *mongoDB) mongoProjection(projection *crud.Projection) bson.D {
	if len(projection.Attributes) > 0 {
		include := bson.D{}
		for _, p := range projection.Attributes {
			if mp := d.mongoPathFor(p); len(mp) > 0 {
				include = append(include, bson.E{Key: mp, Value: 1})
			}
		}
		return include
	}
	if len(projection.ExcludedAttributes) > 0 {
		exclude := bson.D{}
		for _, p := range projection.Attributes {
			if mp := d.mongoPathFor(p); len(mp) > 0 {
				exclude = append(exclude, bson.E{Key: mp, Value: 0})
			}
		}
		return exclude
	}
	return bson.D{}
}
func (d *mongoDB) mongoFilter(filter string) (bson.D, error) {
	cf, err := expr.CompileFilter(filter)
	if err != nil {
		return nil, err
	}
	tf, err := d.t.transform(cf)
	if err != nil {
		return nil, err
	}
	return tf, nil
}
func (d *mongoDB) errNotFoundOrModified(id string) error {
	return fmt.Errorf("%w: resource by id '%s' was not found or was modified since by another request", spec.ErrConflict, id)
}
func Options() *DBOptions {
	return &DBOptions{}
}
type DBOptions struct {
	ignoreProjection bool
}