
com.foursquare.rogue.Query.scala Maven / Gradle / Ivy
// Copyright 2011 Foursquare Labs Inc. All Rights Reserved.
package com.foursquare.rogue
import com.foursquare.rogue.MongoHelpers.{
AndCondition, MongoBuilder, MongoModify, MongoOrder, MongoSelect, QueryExecutor}
import com.mongodb.{BasicDBObjectBuilder, DBCursor, DBObject, WriteConcern}
import net.liftweb.common.{Box, Full}
import net.liftweb.mongodb.MongoDB
import net.liftweb.mongodb.record.{MongoRecord, MongoMetaRecord}
import net.liftweb.record.Field
import org.bson.types.BasicBSONList
import scala.collection.mutable.ListBuffer
import scala.collection.immutable.ListMap
// ***************************************************************************
// *** Builders
// ***************************************************************************
/**
* The definition of methods needed to build a query.
*
* To construct a query, an instance of a query-builder needs to be created. That's
* done by using an implicit conversion from an instance of a meta-record. In code, the
* user writes a query by calling one of the query construction methods on an instance of
* the query-record meta-record instance. The implicit conversion will construct an appropriate
* query builder from the meta-record.
*
* Query builders are parameterized using a collection of phantom types.
* For our purposes here, phantom types are types which are inferred by the type system,
* rather than being explicitly provided by users. The phantom types are inferred by the type
* system on the basis of what clauses are contained in the query. For example, if there's a
* ordering clause, that constrains the types so that the type system must infer a type parameter
* of type "Ordered". This use of phantom types allows the type system to prevent a range of
* query errors - for example, if two query clauses have incompatible ordering constraints, the
* type system will reject it.
*
* The specific mechanics of the type inference process are based on implicit parameters. A
* type can only get inferred into an expression based on a parameter. But we don't want people
* to have to specify parameters explicitly - that would wreck the syntax. Instead, we use implicit
* parameters. The inference system will find an implicit parameter that is type compatible with
* what's used in the rest of the expression.
*
* @param M the record type being queried.
* @param R
* @param Ord a phantom type which defines the sorting behavior of the query.
* @param Sel a phantom type which defines the selection criteria of the query.
* @param Lim a phantom type which defines the result-size limit of the query.
* @param Sk a phantom type which defines the skip-size of the query.
* @param Or a phantom type which defines whether this query is joined to another query-clause by
* an or-clause. The type system will guarantee that the query and its or-connected query
* have the same types.
*/
case class BaseQuery[M <: MongoRecord[M], R,
Ord <: MaybeOrdered,
Sel <: MaybeSelected,
Lim <: MaybeLimited,
Sk <: MaybeSkipped,
Or <: MaybeHasOrClause](
val meta: M with MongoMetaRecord[M],
lim: Option[Int],
sk: Option[Int],
maxScan: Option[Int],
comment: Option[String],
hnt: Option[ListMap[String, Any]],
condition: AndCondition,
order: Option[MongoOrder],
select: Option[MongoSelect[R, M]],
slaveOk: Option[Boolean],
empty: Boolean) {
// The meta field on the MongoMetaRecord (as an instance of MongoRecord)
// points to the master MongoMetaRecord. This is here in case you have a
// second MongoMetaRecord pointing to the slave.
lazy val master = meta.meta
private def addClause[F](clause: M => QueryClause[F],
expectedIndexBehavior: MaybeIndexed):
BaseQuery[M, R, Ord, Sel, Lim, Sk, Or] = {
val cl = clause(meta)
val newClause = cl.withExpectedIndexBehavior(expectedIndexBehavior)
this.copy(
condition = condition.copy(clauses = newClause :: condition.clauses),
empty = this.empty || cl.empty)
}
/**
* Adds a where clause to a query.
*/
def where[F](clause: M => QueryClause[F]) =
addClause(clause, expectedIndexBehavior = Index)
/**
* Adds another and-connected clause to the query.
*/
def and[F](clause: M => QueryClause[F]) =
addClause(clause, expectedIndexBehavior = Index)
/**
* Adds an iscan clause to a query.
*/
def iscan[F](clause: M => QueryClause[F]) =
addClause(clause, expectedIndexBehavior = IndexScan)
/**
* Adds a scan clause to a query.
*/
def scan[F](clause: M => QueryClause[F]) =
addClause(clause, expectedIndexBehavior = DocumentScan)
private def addClauseOpt[V, F](opt: Option[V])
(clause: (M, V) => QueryClause[F],
expectedIndexBehavior: MaybeIndexed) = {
opt match {
case Some(v) => addClause(clause(_, v), expectedIndexBehavior)
case None => this
}
}
def whereOpt[V, F](opt: Option[V])(clause: (M, V) => QueryClause[F]) =
addClauseOpt(opt)(clause, expectedIndexBehavior = Index)
def andOpt[V, F](opt: Option[V])(clause: (M, V) => QueryClause[F]) =
addClauseOpt(opt)(clause, expectedIndexBehavior = Index)
def iscanOpt[V, F](opt: Option[V])(clause: (M, V) => QueryClause[F]) =
addClauseOpt(opt)(clause, expectedIndexBehavior = IndexScan)
def scanOpt[V, F](opt: Option[V])(clause: (M, V) => QueryClause[F]) =
addClauseOpt(opt)(clause, expectedIndexBehavior = DocumentScan)
def raw(f: BasicDBObjectBuilder => Unit): BaseQuery[M, R, Ord, Sel, Lim, Sk, Or] = {
val newClause = new RawQueryClause(f)
this.copy(condition = condition.copy(clauses = newClause :: condition.clauses))
}
/**
* Chains an "or" subquery to the current query.
*
*
The use of the implicit parameter here is key to how the Rogue type checking
* mechanics work. In order to attach an "or" clause to a query, the query as it exists
* must not yet have an or-clause. So the implicit parameter, which carries
* the phantom type information, must be "HasNoOrClause" before this is called. After it's called,
* you can see that the "MaybeHasOrClause" type parameter is changed, and is now specifically
* bound to "HasOrClause", rather than to a type variable.
*/
def or(subqueries: (M with MongoMetaRecord[M] =>
BaseQuery[M, M, Unordered, Unselected, Unlimited, Unskipped, _])*)
(implicit ev: Or =:= HasNoOrClause): BaseQuery[M, R, Ord, Sel, Lim, Sk, HasOrClause] = {
val orCondition = QueryHelpers.orConditionFromQueries(subqueries.toList.map(q => q(meta)))
this.copy(condition = condition.copy(orCondition = Some(orCondition)))
}
/**
* Like "or", this uses the Rogue phantom-type/implicit parameter mechanics. To call this
* method, the query must not yet have an ordering clause attached. This is captured
* by the implicit parameter being constrained to be "Unordered". After this is called, the
* type signature of the returned query is updated so that the "MaybeOrdered" type parameter is
* now Ordered.
*/
def orderAsc[V](field: M => QueryField[V, M])
(implicit ev: Ord =:= Unordered): BaseQuery[M, R, Ordered, Sel, Lim, Sk, Or] =
this.copy(order = Some(MongoOrder(List((field(meta).field.name, true)))))
def orderDesc[V](field: M => QueryField[V, M])
(implicit ev: Ord =:= Unordered): BaseQuery[M, R, Ordered, Sel, Lim, Sk, Or] =
this.copy(order = Some(MongoOrder(List((field(meta).field.name, false)))))
def andAsc[V](field: M => QueryField[V, M])
(implicit ev: Ord =:= Ordered): BaseQuery[M, R, Ordered, Sel, Lim, Sk, Or] =
this.copy(order = Some(MongoOrder((field(meta).field.name, true) :: order.get.terms)))
def andDesc[V](field: M => QueryField[V, M])
(implicit ev: Ord =:= Ordered): BaseQuery[M, R, Ordered, Sel, Lim, Sk, Or] =
this.copy(order = Some(MongoOrder((field(meta).field.name, false) :: order.get.terms)))
/**
* Natural ordering.
* TODO: doesn't make sense in conjunction with ordering on any other fields. enforce w/ phantom types?
*/
def orderNaturalAsc[V](implicit ev: Ord =:= Unordered): BaseQuery[M, R, Ordered, Sel, Lim, Sk, Or] =
this.copy(order = Some(MongoOrder(List(("$natural", true)))))
def orderNaturalDesc[V](implicit ev: Ord =:= Unordered): BaseQuery[M, R, Ordered, Sel, Lim, Sk, Or] =
this.copy(order = Some(MongoOrder(List(("$natural", false)))))
/**
* Places a limit on the size of the returned result.
*
* Like "or", this uses the Rogue phantom-type/implicit parameter mechanics. To call this
* method, the query must not yet have a limit clause attached. This is captured
* by the implicit parameter being constrained to be "Unlimited". After this is called, the
* type signature of the returned query is updated so that the "MaybeLimited" type parameter is
* now Limited.
*/
def limit(n: Int)(implicit ev: Lim =:= Unlimited): BaseQuery[M, R, Ord, Sel, Limited, Sk, Or] =
this.copy(lim = Some(n))
def limitOpt(n: Option[Int])(implicit ev: Lim =:= Unlimited): BaseQuery[M, R, Ord, Sel, Limited, Sk, Or] =
this.copy(lim = n)
/**
* Adds a skip to the query.
*
* Like {@link or}, this uses the Rogue phantom-type/implicit parameter mechanics. To call this
* method, the query must not yet have a skip clause attached. This is captured
* by the implicit parameter being constrained to be {@link Unskipped}. After this is called, the
* type signature of the returned query is updated so that the {@link MaybeSkipped} type parameter is
* now {@link Skipped}.
*/
def skip(n: Int)(implicit ev: Sk =:= Unskipped): BaseQuery[M, R, Ord, Sel, Lim, Skipped, Or] =
this.copy(sk = Some(n))
def skipOpt(n: Option[Int])(implicit ev: Sk =:= Unskipped): BaseQuery[M, R, Ord, Sel, Lim, Skipped, Or] =
this.copy(sk = n)
private[rogue] def parseDBObject(dbo: DBObject): R = select match {
case Some(MongoSelect(Nil, transformer)) =>
// A MongoSelect clause exists, but has empty fields. Return null.
// This is used for .exists(), where we just want to check the number
// of returned results is > 0.
transformer(null)
case Some(MongoSelect(fields, transformer)) =>
val inst = fields.head.field.owner
def setInstanceFieldFromDbo(field: Field[_, M]) = {
inst.fieldByName(field.name) match {
case Full(fld) => fld.setFromAny(dbo.get(field.name))
case _ => {
val splitName = field.name.split('.').toList
Box.!!(splitName.foldLeft(dbo: Object)((obj: Object, fieldName: String) => {
obj match {
case dbl: BasicBSONList =>
(for {
index <- 0 to dbl.size - 1
val item: DBObject = dbl.get(index).asInstanceOf[DBObject]
} yield item.get(fieldName)).toList
case dbo: DBObject =>
dbo.get(fieldName)
case null => null
}
}))
}
}
}
setInstanceFieldFromDbo(inst.fieldByName("_id").open_!)
transformer(fields.map(fld => fld(setInstanceFieldFromDbo(fld.field))))
case None => meta.fromDBObject(dbo).asInstanceOf[R]
}
private def drainBuffer[A, B](from: ListBuffer[A],
to: ListBuffer[B],
f: List[A] => List[B], size: Int): Unit = {
if (from.size >= size) {
to ++= f(from.toList)
from.clear
}
}
/**
* Gets the size of the query result. This should only be called on queries that do not
* have limits or skips.
*/
def count()(implicit ev1: Lim =:= Unlimited, ev2: Sk =:= Unskipped): Long = {
if (this.empty) 0 else
QueryExecutor.condition("count", this)(meta.count(_))
}
/**
* Returns the number of distinct values returned by a query. The query must not have
* limit or skip clauses.
*/
def countDistinct[V](field: M => QueryField[V, M])
(implicit ev1: Lim =:= Unlimited, ev2: Sk =:= Unskipped): Long = {
if (this.empty) 0 else
QueryExecutor.condition("countDistinct", this)(meta.countDistinct(field(meta).field.name, _))
}
/**
* Checks if there are any records that match this query.
*/
def exists()(implicit ev1: Lim =:= Unlimited, ev2: Sk =:= Unskipped): Boolean =
this.copy(select = Some(MongoSelect[Null, M](Nil, _ => null))).limit(1).fetch().size > 0
/**
* Executes a function on each record value returned by a query.
* @param f a function to be invoked on each fetched record.
* @return nothing.
*/
def foreach(f: R => Unit): Unit = {
if (!this.empty)
QueryExecutor.query("find", this, None)(dbo => f(parseDBObject(dbo)))
}
/**
* Execute the query, returning all of the records that match the query.
* @return a list containing the records that match the query
*/
def fetch(): List[R] = {
if (this.empty) Nil else {
val rv = new ListBuffer[R]
QueryExecutor.query("find", this, None)(dbo => rv += parseDBObject(dbo))
rv.toList
}
}
/**
* Execute a query, returning no more than a specified number of result records. The
* query must not have a limit clause.
* @param limit the maximum number of records to return.
*/
def fetch(limit: Int)(implicit ev: Lim =:= Unlimited): List[R] =
this.limit(limit).fetch()
/**
* fetch a batch of results, and execute a function on each element of the list.
* @param f the function to invoke on the records that match the query.
* @return a list containing the results of invoking the function on each record.
*/
def fetchBatch[T](batchSize: Int)(f: List[R] => List[T]): List[T] = {
if (this.empty) Nil else {
val rv = new ListBuffer[T]
val buf = new ListBuffer[R]
QueryExecutor.query("find", this, Some(batchSize)) { dbo =>
buf += parseDBObject(dbo)
drainBuffer(buf, rv, f, batchSize)
}
drainBuffer(buf, rv, f, 1)
rv.toList
}
}
def iterate[S](state: S)(handler: (S, Rogue.Iter.Event[R]) => Rogue.Iter.Command[S]): S = {
if (this.empty) {
handler(state, Rogue.Iter.EOF).state
} else {
QueryExecutor.iterate("find", this, state)(handler)
}
}
def iterateBatch[S](batchSize: Int, state: S)(handler: (S, Rogue.Iter.Event[List[R]]) => Rogue.Iter.Command[S]): S = {
if (this.empty) {
handler(state, Rogue.Iter.EOF).state
} else {
QueryExecutor.iterateBatch("find", this, batchSize, state)(handler)
}
}
/**
* Fetches the first record that matches the query. The query must not contain a "limited" clause.
* @return an option record containing either the first result that matches the
* query, or None if there are no records that match.
*/
def get()(implicit ev: Lim =:= Unlimited): Option[R] =
fetch(1).headOption
/**
* Fetches the records that match the query in paginated form. The query must not contain
* a "limit" clause.
* @param countPerPage the number of records to be contained in each page of the result.
*/
def paginate(countPerPage: Int)(implicit ev1: Lim =:= Unlimited, ev2: Sk =:= Unskipped) = {
new BasePaginatedQuery(this.copy(), countPerPage)
}
def noop()(implicit ev1: Sel =:= Unselected,
ev2: Lim =:= Unlimited,
ev3: Sk =:= Unskipped) = BaseModifyQuery(this, MongoModify(Nil))
/**
* Delete all of the records that match the query. The query must not contain any "skip",
* "limit", or "select" clauses. Sends the delete operation to mongo, and returns - does
* not wait for the delete to be finished.
* All modifications are done against master (not meta, which could point to a slave).
*/
def bulkDelete_!!()(implicit ev1: Sel =:= Unselected,
ev2: Lim =:= Unlimited,
ev3: Sk =:= Unskipped): Unit = {
if (!this.empty)
QueryExecutor.condition("remove", this)(master.bulkDelete_!!(_))
}
/**
* Delete all of the recurds that match the query. The query must not contain any "skip",
* "limit", or "select" clauses. Sends the delete operation to mongo, and waits for the
* delete operation to complete before returning to the caller.
*/
def bulkDelete_!!(concern: WriteConcern)(implicit ev1: Sel =:= Unselected,
ev2: Lim =:= Unlimited,
ev3: Sk =:= Unskipped): Unit = {
if (!this.empty) {
QueryExecutor.condition("remove", this) { qry =>
MongoDB.useCollection(master.mongoIdentifier, master.collectionName) { coll =>
coll.remove(qry, QueryHelpers.config.defaultBulkDeleteWriteConcern)
}
}
}
}
/**
* Finds the first record that matches the query (if any), fetches it, and then deletes it.
* A copy of the deleted record is returned to the caller.
*/
def findAndDeleteOne(): Option[R] = {
if (this.empty) None else {
val mod = BaseFindAndModifyQuery(this, MongoModify(Nil))
QueryExecutor.findAndModify(mod, returnNew=false, upsert=false, remove=true)(this.parseDBObject _)
}
}
override def toString: String =
MongoBuilder.buildQueryString("find", this)
def asDBObject: DBObject =
MongoBuilder.buildCondition(this.condition)
def signature(): String =
MongoBuilder.buildSignature(this)
/**
* Return a string containing details about how the query would be executed in mongo.
* In particular, this is useful for finding out what indexes will be used by the query.
*/
def explain(): String =
QueryExecutor.explain("find", this)
def maxScan(max: Int): BaseQuery[M, R, Ord, Sel, Lim, Sk, Or] = this.copy(maxScan = Some(max))
def comment(c: String): BaseQuery[M, R, Ord, Sel, Lim, Sk, Or] = this.copy(comment = Some(c))
/**
* Set a flag to indicate whether this query may hit secondaries. This only
* really makes sense if you're using replica sets. If this field is
* unspecified, rogue will leave the option untouched, so you'll use
* secondaries or not depending on how you configure the mongo java driver.
* Also, this only works if you're doing a query -- findAndModify, updates,
* and deletes always go to the primaries.
*
* For more info, see
* http://www.mongodb.org/display/DOCS/Querying#Querying-slaveOk%28QueryingSecondaries%29.
*/
def setSlaveOk(b: Boolean): BaseQuery[M, R, Ord, Sel, Lim, Sk, Or] = this.copy(slaveOk = Some(b))
def hint(index: MongoIndex[M]) = this.copy(hnt = Some(index.asListMap))
/**
* Adds a select clause to the query. The use of this method constrains the type
* signature of the query to force the "Sel" field to be type "Selected".
*
* The use of the implicit parameter here is key to how the Rogue type checking
* mechanics work. In order to attach a "select" clause to a query, the query as it exists
* must not have a select clause yet. So the implicit parameter, which carries
* the phantom type information, must be "Unselected" before this is called. After it's called,
* you can see that the "MaybeSelected" type parameter is changed, and is now specifically
* bound to "Selected", rather than to a type variable.
*/
def select[F1](f: M => SelectField[F1, M])
(implicit ev: Sel =:= Unselected): BaseQuery[M, F1, Ord, Selected, Lim, Sk, Or] = {
selectCase(f, (f: F1) => f)
}
def select[F1, F2](f1: M => SelectField[F1, M], f2: M => SelectField[F2, M])
(implicit ev: Sel =:= Unselected): BaseQuery[M, (F1, F2), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, (f1: F1, f2: F2) => (f1, f2))
}
def select[F1, F2, F3](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M])
(implicit ev: Sel =:= Unselected): BaseQuery[M, (F1, F2, F3), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, f3, (f1: F1, f2: F2, f3: F3) => (f1, f2, f3))
}
def select[F1, F2, F3, F4](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M])
(implicit ev: Sel =:= Unselected): BaseQuery[M, (F1, F2, F3, F4), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, f3, f4, (f1: F1, f2: F2, f3: F3, f4: F4) => (f1, f2, f3, f4))
}
def select[F1, F2, F3, F4, F5](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M])
(implicit ev: Sel =:= Unselected):
BaseQuery[M, (F1, F2, F3, F4, F5), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, f3, f4, f5, (f1: F1, f2: F2, f3: F3, f4: F4, f5: F5) => (f1, f2, f3, f4, f5))
}
def select[F1, F2, F3, F4, F5, F6](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M])
(implicit ev: Sel =:= Unselected):
BaseQuery[M, (F1, F2, F3, F4, F5, F6), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, f3, f4, f5, f6, (f1: F1, f2: F2, f3: F3, f4: F4, f5: F5, f6: F6) => (f1, f2, f3, f4, f5, f6))
}
def select[F1, F2, F3, F4, F5, F6, F7](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
f7: M => SelectField[F7, M])
(implicit ev: Sel =:= Unselected):
BaseQuery[M, (F1, F2, F3, F4, F5, F6, F7), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, f3, f4, f5, f6, f7,
(f1: F1, f2: F2, f3: F3, f4: F4, f5: F5, f6: F6, f7: F7) => (f1, f2, f3, f4, f5, f6, f7))
}
def select[F1, F2, F3, F4, F5, F6, F7, F8](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
f7: M => SelectField[F7, M],
f8: M => SelectField[F8, M])
(implicit ev: Sel =:= Unselected):
BaseQuery[M, (F1, F2, F3, F4, F5, F6, F7, F8), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, f3, f4, f5, f6, f7, f8,
(f1: F1, f2: F2, f3: F3, f4: F4, f5: F5, f6: F6, f7: F7, f8: F8) => (f1, f2, f3, f4, f5, f6, f7, f8))
}
def select[F1, F2, F3, F4, F5, F6, F7, F8, F9](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
f7: M => SelectField[F7, M],
f8: M => SelectField[F8, M],
f9: M => SelectField[F9, M])
(implicit ev: Sel =:= Unselected):
BaseQuery[M, (F1, F2, F3, F4, F5, F6, F7, F8, F9), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, f3, f4, f5, f6, f7, f8, f9,
(f1: F1, f2: F2, f3: F3, f4: F4, f5: F5, f6: F6, f7: F7, f8: F8, f9: F9) =>
(f1, f2, f3, f4, f5, f6, f7, f8, f9))
}
def select[F1, F2, F3, F4, F5, F6, F7, F8, F9, F10](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
f7: M => SelectField[F7, M],
f8: M => SelectField[F8, M],
f9: M => SelectField[F9, M],
f10: M => SelectField[F10, M])
(implicit ev: Sel =:= Unselected):
BaseQuery[M, (F1, F2, F3, F4, F5, F6, F7, F8, F9, F10), Ord, Selected, Lim, Sk, Or] = {
selectCase(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10,
(f1: F1, f2: F2, f3: F3, f4: F4, f5: F5, f6: F6, f7: F7, f8: F8, f9: F9, f10: F10) =>
(f1, f2, f3, f4, f5, f6, f7, f8, f9, f10))
}
def selectCase[F1, CC](f: M => SelectField[F1, M],
create: F1 => CC)(implicit ev: Sel =:= Unselected):
BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f(inst))
val transformer = (xs: List[_]) => create(xs.head.asInstanceOf[F1])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
create: (F1, F2) => CC)
(implicit ev: Sel =:= Unselected): BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1], xs(1).asInstanceOf[F2])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, F3, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
create: (F1, F2, F3) => CC)
(implicit ev: Sel =:= Unselected): BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst), f3(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1],
xs(1).asInstanceOf[F2],
xs(2).asInstanceOf[F3])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, F3, F4, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
create: (F1, F2, F3, F4) => CC)
(implicit ev: Sel =:= Unselected):
BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst), f3(inst), f4(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1],
xs(1).asInstanceOf[F2],
xs(2).asInstanceOf[F3],
xs(3).asInstanceOf[F4])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, F3, F4, F5, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
create: (F1, F2, F3, F4, F5) => CC)
(implicit ev: Sel =:= Unselected): BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst), f3(inst), f4(inst), f5(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1],
xs(1).asInstanceOf[F2],
xs(2).asInstanceOf[F3],
xs(3).asInstanceOf[F4],
xs(4).asInstanceOf[F5])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, F3, F4, F5, F6, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
create: (F1, F2, F3, F4, F5, F6) => CC)
(implicit ev: Sel =:= Unselected): BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst), f3(inst), f4(inst), f5(inst), f6(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1],
xs(1).asInstanceOf[F2],
xs(2).asInstanceOf[F3],
xs(3).asInstanceOf[F4],
xs(4).asInstanceOf[F5],
xs(5).asInstanceOf[F6])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, F3, F4, F5, F6, F7, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
f7: M => SelectField[F7, M],
create: (F1, F2, F3, F4, F5, F6, F7) => CC)
( implicit ev: Sel =:= Unselected): BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst), f3(inst), f4(inst), f5(inst), f6(inst), f7(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1],
xs(1).asInstanceOf[F2],
xs(2).asInstanceOf[F3],
xs(3).asInstanceOf[F4],
xs(4).asInstanceOf[F5],
xs(5).asInstanceOf[F6],
xs(6).asInstanceOf[F7])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, F3, F4, F5, F6, F7, F8, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
f7: M => SelectField[F7, M],
f8: M => SelectField[F8, M],
create: (F1, F2, F3, F4, F5, F6, F7, F8) => CC)
(implicit ev: Sel =:= Unselected): BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst), f3(inst), f4(inst), f5(inst), f6(inst), f7(inst), f8(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1],
xs(1).asInstanceOf[F2],
xs(2).asInstanceOf[F3],
xs(3).asInstanceOf[F4],
xs(4).asInstanceOf[F5],
xs(5).asInstanceOf[F6],
xs(6).asInstanceOf[F7],
xs(7).asInstanceOf[F8])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, F3, F4, F5, F6, F7, F8, F9, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
f7: M => SelectField[F7, M],
f8: M => SelectField[F8, M],
f9: M => SelectField[F9, M],
create: (F1, F2, F3, F4, F5, F6, F7, F8, F9) => CC)
(implicit ev: Sel =:= Unselected): BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst), f3(inst), f4(inst), f5(inst), f6(inst), f7(inst), f8(inst), f9(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1],
xs(1).asInstanceOf[F2],
xs(2).asInstanceOf[F3],
xs(3).asInstanceOf[F4],
xs(4).asInstanceOf[F5],
xs(5).asInstanceOf[F6],
xs(6).asInstanceOf[F7],
xs(7).asInstanceOf[F8],
xs(8).asInstanceOf[F9])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
def selectCase[F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, CC](f1: M => SelectField[F1, M],
f2: M => SelectField[F2, M],
f3: M => SelectField[F3, M],
f4: M => SelectField[F4, M],
f5: M => SelectField[F5, M],
f6: M => SelectField[F6, M],
f7: M => SelectField[F7, M],
f8: M => SelectField[F8, M],
f9: M => SelectField[F9, M],
f10: M => SelectField[F10, M],
create: (F1, F2, F3, F4, F5, F6, F7, F8, F9, F10) => CC)
(implicit ev: Sel =:= Unselected): BaseQuery[M, CC, Ord, Selected, Lim, Sk, Or] = {
val inst = meta.createRecord
val fields = List(f1(inst), f2(inst), f3(inst), f4(inst), f5(inst),
f6(inst), f7(inst), f8(inst), f9(inst), f10(inst))
val transformer = (xs: List[_]) => create(xs(0).asInstanceOf[F1],
xs(1).asInstanceOf[F2],
xs(2).asInstanceOf[F3],
xs(3).asInstanceOf[F4],
xs(4).asInstanceOf[F5],
xs(5).asInstanceOf[F6],
xs(6).asInstanceOf[F7],
xs(7).asInstanceOf[F8],
xs(8).asInstanceOf[F9],
xs(9).asInstanceOf[F10])
this.copy(select = Some(MongoSelect(fields, transformer)))
}
}
// *******************************************************
// *** Modify Queries
// *******************************************************
case class BaseModifyQuery[M <: MongoRecord[M]](query: BaseQuery[M, _, _ <: MaybeOrdered,
_ <: MaybeSelected,
_ <: MaybeLimited,
_ <: MaybeSkipped,
_ <: MaybeHasOrClause],
mod: MongoModify) {
private def addClause[F](clause: M => ModifyClause[F]) = {
this.copy(mod = MongoModify(clause(query.meta) :: mod.clauses))
}
def modify[F](clause: M => ModifyClause[F]) = addClause(clause)
def and[F](clause: M => ModifyClause[F]) = addClause(clause)
private def addClauseOpt[V, F](opt: Option[V])(clause: (M, V) => ModifyClause[F]) = {
opt match {
case Some(v) => addClause(clause(_, v))
case None => this
}
}
def modifyOpt[V, F](opt: Option[V])(clause: (M, V) => ModifyClause[F]) =
addClauseOpt(opt)(clause)
def andOpt[V, F](opt: Option[V])(clause: (M, V) => ModifyClause[F]) =
addClauseOpt(opt)(clause)
// These methods always do modifications against master (not query.meta, which could point to a slave).
def updateMulti(): Unit = {
if (!query.empty)
QueryExecutor.modify(this, upsert = false, multi = true, writeConcern = QueryHelpers.config.defaultUpdateMultiWriteConcern)
}
def updateOne(): Unit = {
if (!query.empty)
QueryExecutor.modify(this, upsert = false, multi = false, writeConcern = QueryHelpers.config.defaultUpdateOneWriteConcern)
}
def upsertOne(): Unit = {
// Execute this even if the query won't match any records!
QueryExecutor.modify(this, upsert = true, multi = false, writeConcern = QueryHelpers.config.defaultUpsertOneWriteConcern)
}
def updateMulti(writeConcern: WriteConcern): Unit = {
if (!query.empty)
QueryExecutor.modify(this, upsert = false, multi = true, writeConcern = writeConcern)
}
def updateOne(writeConcern: WriteConcern): Unit = {
if (!query.empty)
QueryExecutor.modify(this, upsert = false, multi = false, writeConcern = writeConcern)
}
def upsertOne(writeConcern: WriteConcern): Unit = {
// Execute this even if the query won't match any records!
QueryExecutor.modify(this, upsert = true, multi = false, writeConcern = writeConcern)
}
override def toString = MongoBuilder.buildModifyString(this)
def asDBObject = (this.query.asDBObject, MongoBuilder.buildModify(this.mod))
}
// *******************************************************
// *** FindAndModify Queries
// *******************************************************
case class BaseFindAndModifyQuery[M <: MongoRecord[M], R](query: BaseQuery[M, R, _ <: MaybeOrdered,
_ <: MaybeSelected,
_ <: MaybeLimited,
_ <: MaybeSkipped,
_ <: MaybeHasOrClause],
mod: MongoModify) {
private def addClause[F](clause: M => ModifyClause[F]) = {
this.copy(mod = MongoModify(clause(query.meta) :: mod.clauses))
}
def findAndModify[F](clause: M => ModifyClause[F]) = addClause(clause)
def and[F](clause: M => ModifyClause[F]) = addClause(clause)
private def addClauseOpt[V, F](opt: Option[V])(clause: (M, V) => ModifyClause[F]) = {
opt match {
case Some(v) => addClause(clause(_, v))
case None => this
}
}
def findAndModifyOpt[V, F](opt: Option[V])(clause: (M, V) => ModifyClause[F]) =
addClauseOpt(opt)(clause)
def andOpt[V, F](opt: Option[V])(clause: (M, V) => ModifyClause[F]) =
addClauseOpt(opt)(clause)
def updateOne(returnNew: Boolean = false): Option[R] = {
// Always do modifications against master (not query.meta, which could point to slave)
if (query.empty) None else
QueryExecutor.findAndModify(this, returnNew, upsert=false, remove=false)(query.parseDBObject _)
}
def upsertOne(returnNew: Boolean = false): Option[R] = {
// Execute this even if the query won't match any records!
QueryExecutor.findAndModify(this, returnNew, upsert=true, remove=false)(query.parseDBObject _)
}
override def toString = MongoBuilder.buildFindAndModifyString(this, false, false, false)
}
class BasePaginatedQuery[M <: MongoRecord[M], R]
(q: BaseQuery[M, R, _, _, Unlimited, Unskipped, _],
val countPerPage: Int, val pageNum: Int = 1) {
def copy() = new BasePaginatedQuery(q, countPerPage, pageNum)
def setPage(p: Int) = if (p == pageNum) this else new BasePaginatedQuery(q, countPerPage, p)
def setCountPerPage(c: Int) = if (c == countPerPage) this else new BasePaginatedQuery(q, c, pageNum)
lazy val countAll: Long = q.count
def fetch(): List[R] = q.skip(countPerPage * (pageNum - 1)).limit(countPerPage).fetch()
def numPages = math.ceil(countAll.toDouble / countPerPage.toDouble).toInt max 1
}