sirius.db.mongo.Finder Maven / Gradle / Ivy
Show all versions of sirius-db Show documentation
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.db.mongo;
import com.google.common.collect.ImmutableList;
import com.mongodb.BasicDBObject;
import com.mongodb.client.FindIterable;
import com.mongodb.client.MongoCursor;
import org.bson.Document;
import sirius.db.mixing.Mapping;
import sirius.kernel.async.TaskContext;
import sirius.kernel.commons.Monoflop;
import sirius.kernel.commons.Value;
import sirius.kernel.commons.Watch;
import sirius.kernel.health.Microtiming;
import javax.annotation.Nonnull;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Fluent builder to build a find statement.
*/
public class Finder extends QueryBuilder {
private static final String KEY_MONGO = "mongo";
private Document fields;
private Document orderBy;
private int skip;
private int limit;
private int batchSize;
protected Finder(Mongo mongo) {
super(mongo);
}
/**
* Limits the fields being returned to the given list.
*
* @param fieldsToReturn specified the list of fields to return
* @return the builder itself for fluent method calls
*/
public Finder selectFields(Mapping... fieldsToReturn) {
fields = new Document();
for (Mapping field : fieldsToReturn) {
fields.put(field.toString(), 1);
}
return this;
}
/**
* Limits the fields being returned to the given list.
*
* @param fieldsToReturn specified the list of fields to return
* @return the builder itself for fluent method calls
*/
public Finder selectFields(String... fieldsToReturn) {
fields = new Document();
for (String field : fieldsToReturn) {
fields.put(field, 1);
}
return this;
}
/**
* Adds a sort constraint to order by the given field ascending.
*
* @param field the field to order by.
* @return the builder itself for fluent method calls
*/
public Finder orderByAsc(Mapping field) {
return orderByAsc(field.toString());
}
/**
* Adds a sort constraint to order by the given field ascending.
*
* @param field the field to order by.
* @return the builder itself for fluent method calls
*/
public Finder orderByAsc(String field) {
if (orderBy == null) {
orderBy = new Document();
}
orderBy.put(field, 1);
return this;
}
/**
* Adds a sort constraint to order by the given field descending.
*
* @param field the field to order by.
* @return the builder itself for fluent method calls
*/
public Finder orderByDesc(Mapping field) {
return orderByDesc(field.toString());
}
/**
* Adds a sort constraint to order by the given field descending.
*
* @param field the field to order by.
* @return the builder itself for fluent method calls
*/
public Finder orderByDesc(String field) {
if (orderBy == null) {
orderBy = new Document();
}
orderBy.put(field, -1);
return this;
}
/**
* Adds a limit to the query.
*
* @param skip the number of items to skip (used for pagination).
* @param limit the max. number of items to return (exluding those who have been skipped).
* @return the builder itself for fluent method calls
*/
public Finder limit(int skip, int limit) {
this.skip = skip;
this.limit = limit;
return this;
}
/**
* Adds a limit to the query.
*
* @param limit the max. number of items to return
* @return the builder itself for fluent method calls
*/
public Finder limit(int limit) {
this.limit = limit;
return this;
}
/**
* Specifies the number of items to skip before items are added to the result.
*
* @param skip the number of items to skip. Values <= 0 are ignored.
* @return the query itself for fluent method calls
*/
public Finder skip(int skip) {
this.skip = skip;
return this;
}
/**
* Specifies the number of items per batch. This has no effect on the result. Low batchSizes can be used
* to prevent cursor timeouts when using a time consuming processor, but will be slower because the cursor
* makes more requests to the server.
*
* @param batchSize the number of items per batch. Values <= 0 are ignored.
* @return the query itself for fluent method calls
*/
public Finder batchSize(int batchSize) {
this.batchSize = batchSize;
return this;
}
/**
* Executes the query for the given collection and returns a single document.
*
* @param type the type of entities to search
* @return the founbd document wrapped as Optional or an empty one, if no document was found.
*/
public Optional singleIn(Class> type) {
return singleIn(getRelationName(type));
}
/**
* Executes the query for the given collection and returns a single document.
*
* @param collection the collection to search in
* @return the founbd document wrapped as Optional or an empty one, if no document was found.
*/
public Optional singleIn(String collection) {
Watch w = Watch.start();
try {
FindIterable cur = mongo.db().getCollection(collection).find(filterObject);
if (fields != null) {
cur.projection(fields);
}
if (orderBy != null) {
cur.sort(orderBy);
}
cur.skip(skip);
Document obj = cur.first();
if (obj == null) {
return Optional.empty();
} else {
return Optional.of(new Doc(obj));
}
} finally {
mongo.callDuration.addValue(w.elapsedMillis());
if (Microtiming.isEnabled()) {
w.submitMicroTiming(KEY_MONGO, "FIND ONE - " + collection + ": " + filterObject);
}
traceIfRequired(collection, w);
}
}
/**
* Executes the query for the given collection and calls the given processor for each document as long as it
* returns true.
*
* @param type the type of entities to search
* @param processor the processor to handle matches, which also controls if further results should be processed
*/
public void eachIn(Class> type, Function processor) {
eachIn(getRelationName(type), processor);
}
/**
* Executes the query for the given collection and calls the given processor for each document as long as it
* returns true.
*
* @param collection the collection to search in
* @param processor the processor to handle matches, which also controls if further results should be processed
*/
public void eachIn(String collection, Function processor) {
Watch w = Watch.start();
if (Mongo.LOG.isFINE()) {
Mongo.LOG.FINE("FIND: %s\nFilter: %s", collection, filterObject);
}
FindIterable cur = mongo.db().getCollection(collection).find(filterObject);
if (fields != null) {
cur.projection(fields);
}
if (orderBy != null) {
cur.sort(orderBy);
}
cur.skip(skip);
if (limit > 0) {
cur.limit(limit);
}
if (batchSize > 0) {
cur.batchSize(batchSize);
}
TaskContext ctx = TaskContext.get();
Monoflop mf = Monoflop.create();
for (Document doc : cur) {
if (mf.firstCall()) {
handleTracingAndReporting(collection, w);
}
boolean keepGoing = processor.apply(new Doc(doc));
if (!keepGoing || !ctx.isActive()) {
return;
}
}
}
@Override
public Doc explain(String collection) {
FindIterable cur = mongo.db().getCollection(collection).find(filterObject);
if (fields != null) {
cur.projection(fields);
}
if (orderBy != null) {
cur.sort(orderBy);
}
cur.skip(skip);
if (limit > 0) {
cur.limit(limit);
}
return new Doc(cur.modifiers(new Document("$explain", true)).first());
}
private void handleTracingAndReporting(String collection, Watch w) {
mongo.callDuration.addValue(w.elapsedMillis());
if (Microtiming.isEnabled()) {
w.submitMicroTiming(KEY_MONGO, "FIND ALL - " + collection + ": " + filterObject);
}
traceIfRequired(collection, w);
}
/**
* Executes the query for the given collection and calls the given processor for each document.
*
* @param type the type of entities to search
* @param processor the processor to handle matches
*/
public void allIn(Class> type, Consumer processor) {
allIn(getRelationName(type), processor);
}
/**
* Executes the query for the given collection and calls the given processor for each document.
*
* @param collection the collection to search in
* @param processor the processor to handle matches
*/
public void allIn(String collection, Consumer processor) {
eachIn(collection, d -> {
processor.accept(d);
return true;
});
}
/**
* Counts the number of documents in the result of the given query.
*
* Note that limits are ignored for this query.
*
* @param type the type of entities to search
* @return the number of documents found
*/
public long countIn(Class> type) {
return countIn(getRelationName(type));
}
/**
* Counts the number of documents in the result of the given query.
*
* Note that limits are ignored for this query.
*
* @param collection the collection to search in
* @return the number of documents found
*/
public long countIn(String collection) {
Watch w = Watch.start();
try {
return mongo.db().getCollection(collection).count(filterObject);
} finally {
mongo.callDuration.addValue(w.elapsedMillis());
if (Microtiming.isEnabled()) {
w.submitMicroTiming(KEY_MONGO, "COUNT - " + collection + ": " + filterObject);
}
traceIfRequired(collection, w);
}
}
/**
* Aggregates the documents in the result of the given query with an accumulator operator.
*
* Note that limits are ignored for this query.
*
* @param type the type of entities to aggregate
* @param field the field to aggregate
* @param operator the accumulation operator to aggregate with
* @return the result of the accumulation (usually Integer, Double or List)
* @see MongoDB Reference
*/
public Value aggregateIn(@Nonnull Class> type, @Nonnull Mapping field, @Nonnull String operator) {
return aggregateIn(getRelationName(type), field, operator);
}
/**
* Aggregates the documents in the result of the given query with an accumulator operator.
*
* Note that limits are ignored for this query.
*
* @param collection the collection to search in
* @param field the field to aggregate
* @param operator the accumulation operator to aggregate with
* @return the result of the accumulation (usually Integer, Double or List)
* @see MongoDB Reference
*/
public Value aggregateIn(@Nonnull String collection, @Nonnull Mapping field, @Nonnull String operator) {
Watch w = Watch.start();
try {
BasicDBObject groupStage = new BasicDBObject().append(Mango.ID_FIELD, null)
.append("result", new BasicDBObject(operator, "$" + field));
MongoCursor queryResult = mongo.db()
.getCollection(collection)
.aggregate(ImmutableList.of(new BasicDBObject("$match",
filterObject),
new BasicDBObject("$group",
groupStage)))
.iterator();
if (queryResult.hasNext()) {
return Value.of(queryResult.next().get("result"));
} else {
return Value.EMPTY;
}
} finally {
mongo.callDuration.addValue(w.elapsedMillis());
if (Microtiming.isEnabled()) {
w.submitMicroTiming(KEY_MONGO,
"AGGREGATE - " + collection + "." + field + " (" + operator + "): " + filterObject);
}
traceIfRequired(collection, w);
}
}
}