sourceafMorphia::Query.fan

using afBson

** A means to build Mongo queries with sane objects and methods. (And not some incomprehensible mess of nested maps and lists!)
** 
** Pass 'Query' objects to a `QueryExecutor` to run them.
class Query {
    
    private |Datastore, Str:Obj|[] _toMongoFuncs    := [,]
    
    ** Creates a match for the given field name. It may reference nested objects using dot notation. Example, 'user.name'
    ** 
    ** Note this is actually the MongoDB property name and *not* the field name. 
    ** Though, the two are usually the same unless you use the '@Property.name' attribute. 
    QueryCriterion field(Str fieldName) {
        // todo: validate field exists
        // Note, this cannot take a field for we can't verify nested objects
        return QueryCriterion(this, fieldName)
    }

    ** Selects documents based on the return value of a javascript function. Example:
    ** 
    **   Query().where(Code("this.name == 'Judge Dredd'"))
    ** 
    ** As only 1 *where* function is allowed per query, only the last *where* function is used.
    ** 
    ** @see `http://docs.mongodb.org/manual/reference/operator/query/where/`
    Query where(Code where) {
        _addFunc |Datastore datastore, Str:Obj mongoQuery| {
            mongoQuery["\$where"] = where
        }
    }

    ** Selects documents that pass all the query expressions in the given list.
    ** Example:
    ** 
    **   Query().and([
    **     Query().field("quantity").lessThan(20),
    **     Query().field("price").eq(10)
    **   ])
    ** 
    ** Note the above could also be written implicitly with:
    ** 
    **   Query().field("quantity").lessThan(20).field("price").eq(10)
    ** 
    ** @see `http://docs.mongodb.org/manual/reference/operator/query/and/`
    Query and(Query[] criteria) {
        _addFunc |Datastore datastore, Str:Obj mongoQuery| {
            mongoQuery.add("\$and", criteria.map { it.toMongo(datastore) })
        }
    }   

    ** Selects documents that pass any of the query expressions in the given list.
    ** Example:
    ** 
    **   Query().or([
    **     Query().field("quantity").lessThan(20),
    **     Query().field("price").eq(10)
    **   ])
    ** 
    ** @see `http://docs.mongodb.org/manual/reference/operator/query/or/`
    Query or(Query[] criteria) {
        _addFunc |Datastore datastore, Str:Obj mongoQuery| {
            mongoQuery.add("\$or", criteria.map { it.toMongo(datastore) })
        }
    }   

    ** Selects documents that fail **all** the query expressions in the given list.
    ** Example:
    ** 
    **   Query().or([
    **     Query().field("quantity").lessThan(20),
    **     Query().field("price").eq(10)
    **   ])
    ** 
    ** @see `http://docs.mongodb.org/manual/reference/operator/query/nor/`
    Query nor(Query[] criteria) {
        _addFunc |Datastore datastore, Str:Obj mongoQuery| {
            mongoQuery.add("\$nor", criteria.map { it.toMongo(datastore) })
        }
    }
    
    ** Returns a Mongo document representing the query. 
    ** May be used by `DataStore` and [Collection]`afMongo::Collection` methods such as 'findAndUpdate(...)'.  
    [Str:Obj] toMongo(Datastore datastore) {
        mongoQuery := Str:Obj[:] { ordered = true }
        _toMongoFuncs.each { it.call(datastore, mongoQuery) }
        return mongoQuery
    }
    
    internal This _addFunc(|Datastore, Str:Obj| func) {
        _toMongoFuncs.add(func)
        return this
    }   
}