6 changed files with 961 additions and 0 deletions
@ -0,0 +1,181 @@
@@ -0,0 +1,181 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.repository.support; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
import javax.annotation.Nullable; |
||||
|
||||
import org.bson.Document; |
||||
|
||||
import com.querydsl.core.DefaultQueryMetadata; |
||||
import com.querydsl.core.QueryModifiers; |
||||
import com.querydsl.core.SimpleQuery; |
||||
import com.querydsl.core.support.QueryMixin; |
||||
import com.querydsl.core.types.Expression; |
||||
import com.querydsl.core.types.FactoryExpression; |
||||
import com.querydsl.core.types.OrderSpecifier; |
||||
import com.querydsl.core.types.ParamExpression; |
||||
import com.querydsl.core.types.Path; |
||||
import com.querydsl.core.types.Predicate; |
||||
import com.querydsl.core.types.dsl.CollectionPathBase; |
||||
|
||||
/** |
||||
* {@code AbstractMongodbQuery} provides a base class for general Querydsl query implementation. |
||||
* |
||||
* @author Mark Paluch |
||||
* @param <Q> concrete subtype |
||||
*/ |
||||
abstract class AbstractMongodbQuery<Q extends AbstractMongodbQuery<Q>> implements SimpleQuery<Q> { |
||||
|
||||
@SuppressWarnings("serial") |
||||
static class NoResults extends RuntimeException {} |
||||
|
||||
private final MongodbDocumentSerializer serializer; |
||||
private final QueryMixin<Q> queryMixin; |
||||
|
||||
/** |
||||
* Create a new MongodbQuery instance |
||||
* |
||||
* @param serializer serializer |
||||
*/ |
||||
@SuppressWarnings("unchecked") |
||||
public AbstractMongodbQuery(MongodbDocumentSerializer serializer) { |
||||
@SuppressWarnings("unchecked") // Q is this plus subclass
|
||||
Q query = (Q) this; |
||||
this.queryMixin = new QueryMixin<Q>(query, new DefaultQueryMetadata(), false); |
||||
this.serializer = serializer; |
||||
} |
||||
|
||||
/** |
||||
* Define a join |
||||
* |
||||
* @param ref reference |
||||
* @param target join target |
||||
* @return join builder |
||||
*/ |
||||
public <T> JoinBuilder<Q, T> join(Path<T> ref, Path<T> target) { |
||||
return new JoinBuilder<Q, T>(queryMixin, ref, target); |
||||
} |
||||
|
||||
/** |
||||
* Define a join |
||||
* |
||||
* @param ref reference |
||||
* @param target join target |
||||
* @return join builder |
||||
*/ |
||||
public <T> JoinBuilder<Q, T> join(CollectionPathBase<?, T, ?> ref, Path<T> target) { |
||||
return new JoinBuilder<Q, T>(queryMixin, ref, target); |
||||
} |
||||
|
||||
/** |
||||
* Define a constraint for an embedded object |
||||
* |
||||
* @param collection collection |
||||
* @param target target |
||||
* @return builder |
||||
*/ |
||||
public <T> AnyEmbeddedBuilder<Q> anyEmbedded(Path<? extends Collection<T>> collection, Path<T> target) { |
||||
return new AnyEmbeddedBuilder<Q>(queryMixin, collection); |
||||
} |
||||
|
||||
@Override |
||||
public Q distinct() { |
||||
return queryMixin.distinct(); |
||||
} |
||||
|
||||
public Q where(Predicate e) { |
||||
return queryMixin.where(e); |
||||
} |
||||
|
||||
@Override |
||||
public Q where(Predicate... e) { |
||||
return queryMixin.where(e); |
||||
} |
||||
|
||||
@Override |
||||
public Q limit(long limit) { |
||||
return queryMixin.limit(limit); |
||||
} |
||||
|
||||
@Override |
||||
public Q offset(long offset) { |
||||
return queryMixin.offset(offset); |
||||
} |
||||
|
||||
@Override |
||||
public Q restrict(QueryModifiers modifiers) { |
||||
return queryMixin.restrict(modifiers); |
||||
} |
||||
|
||||
public Q orderBy(OrderSpecifier<?> o) { |
||||
return queryMixin.orderBy(o); |
||||
} |
||||
|
||||
@Override |
||||
public Q orderBy(OrderSpecifier<?>... o) { |
||||
return queryMixin.orderBy(o); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Q set(ParamExpression<T> param, T value) { |
||||
return queryMixin.set(param, value); |
||||
} |
||||
|
||||
protected Document createProjection(Expression<?> projection) { |
||||
if (projection instanceof FactoryExpression) { |
||||
Document obj = new Document(); |
||||
for (Object expr : ((FactoryExpression) projection).getArgs()) { |
||||
if (expr instanceof Expression) { |
||||
obj.put((String) serializer.handle((Expression) expr), 1); |
||||
} |
||||
} |
||||
return obj; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
protected Document createQuery(@Nullable Predicate predicate) { |
||||
if (predicate != null) { |
||||
return (Document) serializer.handle(predicate); |
||||
} else { |
||||
return new Document(); |
||||
} |
||||
} |
||||
|
||||
QueryMixin<Q> getQueryMixin() { |
||||
return queryMixin; |
||||
} |
||||
|
||||
MongodbDocumentSerializer getSerializer() { |
||||
return serializer; |
||||
} |
||||
|
||||
/** |
||||
* Get the where definition as a Document instance |
||||
* |
||||
* @return |
||||
*/ |
||||
public Document asDocument() { |
||||
return createQuery(queryMixin.getMetadata().getWhere()); |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return asDocument().toString(); |
||||
} |
||||
} |
||||
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.repository.support; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
import com.querydsl.core.support.QueryMixin; |
||||
import com.querydsl.core.types.ExpressionUtils; |
||||
import com.querydsl.core.types.Path; |
||||
import com.querydsl.core.types.Predicate; |
||||
import com.querydsl.mongodb.MongodbOps; |
||||
|
||||
/** |
||||
* {@code AnyEmbeddedBuilder} is a builder for constraints on embedded objects |
||||
* |
||||
* @param <Q> query type |
||||
* @author Mark Paluch |
||||
*/ |
||||
class AnyEmbeddedBuilder<Q extends AbstractMongodbQuery<Q>> { |
||||
|
||||
private final QueryMixin<Q> queryMixin; |
||||
|
||||
private final Path<? extends Collection<?>> collection; |
||||
|
||||
public AnyEmbeddedBuilder(QueryMixin<Q> queryMixin, Path<? extends Collection<?>> collection) { |
||||
this.queryMixin = queryMixin; |
||||
this.collection = collection; |
||||
} |
||||
|
||||
public Q on(Predicate... conditions) { |
||||
return queryMixin |
||||
.where(ExpressionUtils.predicate(MongodbOps.ELEM_MATCH, collection, ExpressionUtils.allOf(conditions))); |
||||
} |
||||
} |
||||
@ -0,0 +1,260 @@
@@ -0,0 +1,260 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.repository.support; |
||||
|
||||
import java.util.Collection; |
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
|
||||
import javax.annotation.Nullable; |
||||
|
||||
import org.springframework.data.mongodb.core.MongoOperations; |
||||
import org.springframework.data.mongodb.core.query.BasicQuery; |
||||
import org.springframework.data.mongodb.core.query.Query; |
||||
|
||||
import com.google.common.collect.HashMultimap; |
||||
import com.google.common.collect.Multimap; |
||||
import com.mysema.commons.lang.CloseableIterator; |
||||
import com.querydsl.core.Fetchable; |
||||
import com.querydsl.core.JoinExpression; |
||||
import com.querydsl.core.QueryMetadata; |
||||
import com.querydsl.core.QueryModifiers; |
||||
import com.querydsl.core.QueryResults; |
||||
import com.querydsl.core.types.Expression; |
||||
import com.querydsl.core.types.ExpressionUtils; |
||||
import com.querydsl.core.types.Operation; |
||||
import com.querydsl.core.types.OrderSpecifier; |
||||
import com.querydsl.core.types.Path; |
||||
import com.querydsl.core.types.Predicate; |
||||
|
||||
/** |
||||
* {@link Fetchable} Mongodb query with a pluggable Document to Bean transformation. |
||||
* |
||||
* @param <K> result type |
||||
* @param <Q> concrete subtype |
||||
* @author Mark Paluch |
||||
*/ |
||||
class FetchableMongodbQuery<K> extends AbstractMongodbQuery<FetchableMongodbQuery<K>> implements Fetchable<K> { |
||||
|
||||
private final Class<K> entityClass; |
||||
private final String collection; |
||||
private final MongoOperations mongoOperations; |
||||
|
||||
public FetchableMongodbQuery(MongodbDocumentSerializer serializer, Class<? extends K> entityClass, |
||||
MongoOperations mongoOperations) { |
||||
|
||||
super(serializer); |
||||
|
||||
this.entityClass = (Class<K>) entityClass; |
||||
this.collection = mongoOperations.getCollectionName(entityClass); |
||||
this.mongoOperations = mongoOperations; |
||||
} |
||||
|
||||
public FetchableMongodbQuery(MongodbDocumentSerializer serializer, Class<? extends K> entityClass, String collection, |
||||
MongoOperations mongoOperations) { |
||||
|
||||
super(serializer); |
||||
|
||||
this.entityClass = (Class<K>) entityClass; |
||||
this.collection = collection; |
||||
this.mongoOperations = mongoOperations; |
||||
} |
||||
|
||||
/** |
||||
* Iterate with the specific fields |
||||
* |
||||
* @param paths fields to return |
||||
* @return iterator |
||||
*/ |
||||
public CloseableIterator<K> iterate(Path<?>... paths) { |
||||
getQueryMixin().setProjection(paths); |
||||
return iterate(); |
||||
} |
||||
|
||||
@Override |
||||
public CloseableIterator<K> iterate() { |
||||
|
||||
org.springframework.data.util.CloseableIterator<? extends K> stream = mongoOperations.stream(createQuery(), |
||||
entityClass, collection); |
||||
|
||||
return new CloseableIterator<K>() { |
||||
@Override |
||||
public boolean hasNext() { |
||||
return stream.hasNext(); |
||||
} |
||||
|
||||
@Override |
||||
public K next() { |
||||
return stream.next(); |
||||
} |
||||
|
||||
@Override |
||||
public void remove() { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public void close() { |
||||
stream.close(); |
||||
} |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Fetch with the specific fields |
||||
* |
||||
* @param paths fields to return |
||||
* @return results |
||||
*/ |
||||
public List<K> fetch(Path<?>... paths) { |
||||
getQueryMixin().setProjection(paths); |
||||
return fetch(); |
||||
} |
||||
|
||||
@Override |
||||
public List<K> fetch() { |
||||
return mongoOperations.query(entityClass).matching(createQuery()).all(); |
||||
} |
||||
|
||||
/** |
||||
* Fetch first with the specific fields |
||||
* |
||||
* @param paths fields to return |
||||
* @return first result |
||||
*/ |
||||
public K fetchFirst(Path<?>... paths) { |
||||
getQueryMixin().setProjection(paths); |
||||
return fetchFirst(); |
||||
} |
||||
|
||||
@Override |
||||
public K fetchFirst() { |
||||
return mongoOperations.query(entityClass).matching(createQuery()).firstValue(); |
||||
} |
||||
|
||||
/** |
||||
* Fetch one with the specific fields |
||||
* |
||||
* @param paths fields to return |
||||
* @return first result |
||||
*/ |
||||
public K fetchOne(Path<?>... paths) { |
||||
getQueryMixin().setProjection(paths); |
||||
return fetchOne(); |
||||
} |
||||
|
||||
@Override |
||||
public K fetchOne() { |
||||
return mongoOperations.query(entityClass).matching(createQuery()).oneValue(); |
||||
|
||||
} |
||||
|
||||
/** |
||||
* Fetch results with the specific fields |
||||
* |
||||
* @param paths fields to return |
||||
* @return results |
||||
*/ |
||||
public QueryResults<K> fetchResults(Path<?>... paths) { |
||||
getQueryMixin().setProjection(paths); |
||||
return fetchResults(); |
||||
} |
||||
|
||||
@Override |
||||
public QueryResults<K> fetchResults() { |
||||
long total = fetchCount(); |
||||
if (total > 0L) { |
||||
return new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total); |
||||
} else { |
||||
return QueryResults.emptyResults(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public long fetchCount() { |
||||
return mongoOperations.query(entityClass).matching(createQuery()).count(); |
||||
} |
||||
|
||||
protected org.springframework.data.mongodb.core.query.Query createQuery() { |
||||
QueryMetadata metadata = getQueryMixin().getMetadata(); |
||||
Predicate filter = createFilter(metadata); |
||||
return createQuery(filter, metadata.getProjection(), metadata.getModifiers(), metadata.getOrderBy()); |
||||
} |
||||
|
||||
protected org.springframework.data.mongodb.core.query.Query createQuery(@Nullable Predicate where, |
||||
Expression<?> projection, QueryModifiers modifiers, List<OrderSpecifier<?>> orderBy) { |
||||
|
||||
BasicQuery basicQuery = new BasicQuery(createQuery(where), createProjection(projection)); |
||||
|
||||
Integer limit = modifiers.getLimitAsInteger(); |
||||
Integer offset = modifiers.getOffsetAsInteger(); |
||||
|
||||
if (limit != null) { |
||||
basicQuery.limit(limit); |
||||
} |
||||
if (offset != null) { |
||||
basicQuery.skip(offset); |
||||
} |
||||
if (orderBy.size() > 0) { |
||||
basicQuery.setSortObject(getSerializer().toSort(orderBy)); |
||||
} |
||||
return basicQuery; |
||||
} |
||||
|
||||
@Nullable |
||||
protected Predicate createFilter(QueryMetadata metadata) { |
||||
Predicate filter; |
||||
if (!metadata.getJoins().isEmpty()) { |
||||
filter = ExpressionUtils.allOf(metadata.getWhere(), createJoinFilter(metadata)); |
||||
} else { |
||||
filter = metadata.getWhere(); |
||||
} |
||||
return filter; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Nullable |
||||
protected Predicate createJoinFilter(QueryMetadata metadata) { |
||||
Multimap<Expression<?>, Predicate> predicates = HashMultimap.create(); |
||||
List<JoinExpression> joins = metadata.getJoins(); |
||||
for (int i = joins.size() - 1; i >= 0; i--) { |
||||
JoinExpression join = joins.get(i); |
||||
Path<?> source = (Path) ((Operation<?>) join.getTarget()).getArg(0); |
||||
Path<?> target = (Path) ((Operation<?>) join.getTarget()).getArg(1); |
||||
Collection<Predicate> extraFilters = predicates.get(target.getRoot()); |
||||
Predicate filter = ExpressionUtils.allOf(join.getCondition(), allOf(extraFilters)); |
||||
List<? extends Object> ids = getIds(target.getType(), filter); |
||||
if (ids.isEmpty()) { |
||||
throw new NoResults(); |
||||
} |
||||
Path<?> path = ExpressionUtils.path(String.class, source, "$id"); |
||||
predicates.put(source.getRoot(), ExpressionUtils.in((Path<Object>) path, ids)); |
||||
} |
||||
Path<?> source = (Path) ((Operation) joins.get(0).getTarget()).getArg(0); |
||||
return allOf(predicates.get(source.getRoot())); |
||||
} |
||||
|
||||
private Predicate allOf(Collection<Predicate> predicates) { |
||||
return predicates != null ? ExpressionUtils.allOf(predicates) : null; |
||||
} |
||||
|
||||
protected List<Object> getIds(Class<?> targetType, Predicate condition) { |
||||
// TODO : fetch only ids
|
||||
Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList()); |
||||
|
||||
return mongoOperations.findDistinct(query, "_id", targetType, Object.class); |
||||
} |
||||
} |
||||
@ -0,0 +1,51 @@
@@ -0,0 +1,51 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.repository.support; |
||||
|
||||
import com.querydsl.core.JoinType; |
||||
import com.querydsl.core.support.QueryMixin; |
||||
import com.querydsl.core.types.ExpressionUtils; |
||||
import com.querydsl.core.types.Path; |
||||
import com.querydsl.core.types.Predicate; |
||||
|
||||
/** |
||||
* {@code JoinBuilder} is a builder for join constraints |
||||
* |
||||
* @author Mark Paluch |
||||
* @param <Q> |
||||
* @param <T> |
||||
*/ |
||||
class JoinBuilder<Q extends AbstractMongodbQuery<Q>, T> { |
||||
|
||||
private final QueryMixin<Q> queryMixin; |
||||
|
||||
private final Path<?> ref; |
||||
|
||||
private final Path<T> target; |
||||
|
||||
public JoinBuilder(QueryMixin<Q> queryMixin, Path<?> ref, Path<T> target) { |
||||
this.queryMixin = queryMixin; |
||||
this.ref = ref; |
||||
this.target = target; |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
public Q on(Predicate... conditions) { |
||||
queryMixin.addJoin(JoinType.JOIN, ExpressionUtils.as((Path) ref, target)); |
||||
queryMixin.on(conditions); |
||||
return queryMixin.getSelf(); |
||||
} |
||||
} |
||||
@ -0,0 +1,361 @@
@@ -0,0 +1,361 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.repository.support; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.Collection; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.regex.Pattern; |
||||
|
||||
import org.bson.Document; |
||||
import org.bson.types.ObjectId; |
||||
|
||||
import com.google.common.collect.Sets; |
||||
import com.mongodb.DBRef; |
||||
import com.querydsl.core.types.*; |
||||
import com.querydsl.mongodb.MongodbOps; |
||||
|
||||
/** |
||||
* Serializes the given Querydsl query to a Document query for MongoDB. |
||||
* |
||||
* @author Mark Paluch |
||||
*/ |
||||
abstract class MongodbDocumentSerializer implements Visitor<Object, Void> { |
||||
|
||||
public Object handle(Expression<?> expression) { |
||||
return expression.accept(this, null); |
||||
} |
||||
|
||||
public Document toSort(List<OrderSpecifier<?>> orderBys) { |
||||
Document sort = new Document(); |
||||
for (OrderSpecifier<?> orderBy : orderBys) { |
||||
Object key = orderBy.getTarget().accept(this, null); |
||||
sort.append(key.toString(), orderBy.getOrder() == Order.ASC ? 1 : -1); |
||||
} |
||||
return sort; |
||||
} |
||||
|
||||
@Override |
||||
public Object visit(Constant<?> expr, Void context) { |
||||
if (Enum.class.isAssignableFrom(expr.getType())) { |
||||
@SuppressWarnings("unchecked") // Guarded by previous check
|
||||
Constant<? extends Enum<?>> expectedExpr = (Constant<? extends Enum<?>>) expr; |
||||
return expectedExpr.getConstant().name(); |
||||
} else { |
||||
return expr.getConstant(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public Object visit(TemplateExpression<?> expr, Void context) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public Object visit(FactoryExpression<?> expr, Void context) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
protected String asDBKey(Operation<?> expr, int index) { |
||||
return (String) asDBValue(expr, index); |
||||
} |
||||
|
||||
protected Object asDBValue(Operation<?> expr, int index) { |
||||
return expr.getArg(index).accept(this, null); |
||||
} |
||||
|
||||
private String regexValue(Operation<?> expr, int index) { |
||||
return Pattern.quote(expr.getArg(index).accept(this, null).toString()); |
||||
} |
||||
|
||||
protected Document asDocument(String key, Object value) { |
||||
return new Document(key, value); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Override |
||||
public Object visit(Operation<?> expr, Void context) { |
||||
Operator op = expr.getOperator(); |
||||
if (op == Ops.EQ) { |
||||
if (expr.getArg(0) instanceof Operation) { |
||||
Operation<?> lhs = (Operation<?>) expr.getArg(0); |
||||
if (lhs.getOperator() == Ops.COL_SIZE || lhs.getOperator() == Ops.ARRAY_SIZE) { |
||||
return asDocument(asDBKey(lhs, 0), asDocument("$size", asDBValue(expr, 1))); |
||||
} else { |
||||
throw new UnsupportedOperationException("Illegal operation " + expr); |
||||
} |
||||
} else if (expr.getArg(0) instanceof Path) { |
||||
Path<?> path = (Path<?>) expr.getArg(0); |
||||
Constant<?> constant = (Constant<?>) expr.getArg(1); |
||||
return asDocument(asDBKey(expr, 0), convert(path, constant)); |
||||
} |
||||
} else if (op == Ops.STRING_IS_EMPTY) { |
||||
return asDocument(asDBKey(expr, 0), ""); |
||||
|
||||
} else if (op == Ops.AND) { |
||||
Map<Object, Object> lhs = (Map<Object, Object>) handle(expr.getArg(0)); |
||||
Map<Object, Object> rhs = (Map<Object, Object>) handle(expr.getArg(1)); |
||||
if (Sets.intersection(lhs.keySet(), rhs.keySet()).isEmpty()) { |
||||
lhs.putAll(rhs); |
||||
return lhs; |
||||
} else { |
||||
List<Object> list = new ArrayList<Object>(2); |
||||
list.add(handle(expr.getArg(0))); |
||||
list.add(handle(expr.getArg(1))); |
||||
return asDocument("$and", list); |
||||
} |
||||
|
||||
} else if (op == Ops.NOT) { |
||||
// Handle the not's child
|
||||
Operation<?> subOperation = (Operation<?>) expr.getArg(0); |
||||
Operator subOp = subOperation.getOperator(); |
||||
if (subOp == Ops.IN) { |
||||
return visit( |
||||
ExpressionUtils.operation(Boolean.class, Ops.NOT_IN, subOperation.getArg(0), subOperation.getArg(1)), |
||||
context); |
||||
} else { |
||||
Document arg = (Document) handle(expr.getArg(0)); |
||||
return negate(arg); |
||||
} |
||||
|
||||
} else if (op == Ops.OR) { |
||||
List<Object> list = new ArrayList<Object>(2); |
||||
list.add(handle(expr.getArg(0))); |
||||
list.add(handle(expr.getArg(1))); |
||||
return asDocument("$or", list); |
||||
|
||||
} else if (op == Ops.NE) { |
||||
Path<?> path = (Path<?>) expr.getArg(0); |
||||
Constant<?> constant = (Constant<?>) expr.getArg(1); |
||||
return asDocument(asDBKey(expr, 0), asDocument("$ne", convert(path, constant))); |
||||
|
||||
} else if (op == Ops.STARTS_WITH) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile("^" + regexValue(expr, 1))); |
||||
|
||||
} else if (op == Ops.STARTS_WITH_IC) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile("^" + regexValue(expr, 1), Pattern.CASE_INSENSITIVE)); |
||||
|
||||
} else if (op == Ops.ENDS_WITH) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile(regexValue(expr, 1) + "$")); |
||||
|
||||
} else if (op == Ops.ENDS_WITH_IC) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile(regexValue(expr, 1) + "$", Pattern.CASE_INSENSITIVE)); |
||||
|
||||
} else if (op == Ops.EQ_IGNORE_CASE) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile("^" + regexValue(expr, 1) + "$", Pattern.CASE_INSENSITIVE)); |
||||
|
||||
} else if (op == Ops.STRING_CONTAINS) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile(".*" + regexValue(expr, 1) + ".*")); |
||||
|
||||
} else if (op == Ops.STRING_CONTAINS_IC) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile(".*" + regexValue(expr, 1) + ".*", Pattern.CASE_INSENSITIVE)); |
||||
|
||||
} else if (op == Ops.MATCHES) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile(asDBValue(expr, 1).toString())); |
||||
|
||||
} else if (op == Ops.MATCHES_IC) { |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile(asDBValue(expr, 1).toString(), Pattern.CASE_INSENSITIVE)); |
||||
|
||||
} else if (op == Ops.LIKE) { |
||||
String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString(); |
||||
return asDocument(asDBKey(expr, 0), Pattern.compile(regex)); |
||||
|
||||
} else if (op == Ops.BETWEEN) { |
||||
Document value = new Document("$gte", asDBValue(expr, 1)); |
||||
value.append("$lte", asDBValue(expr, 2)); |
||||
return asDocument(asDBKey(expr, 0), value); |
||||
|
||||
} else if (op == Ops.IN) { |
||||
int constIndex = 0; |
||||
int exprIndex = 1; |
||||
if (expr.getArg(1) instanceof Constant<?>) { |
||||
constIndex = 1; |
||||
exprIndex = 0; |
||||
} |
||||
if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) { |
||||
@SuppressWarnings("unchecked") // guarded by previous check
|
||||
Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant(); |
||||
return asDocument(asDBKey(expr, exprIndex), asDocument("$in", values)); |
||||
} else { |
||||
Path<?> path = (Path<?>) expr.getArg(exprIndex); |
||||
Constant<?> constant = (Constant<?>) expr.getArg(constIndex); |
||||
return asDocument(asDBKey(expr, exprIndex), convert(path, constant)); |
||||
} |
||||
|
||||
} else if (op == Ops.NOT_IN) { |
||||
int constIndex = 0; |
||||
int exprIndex = 1; |
||||
if (expr.getArg(1) instanceof Constant<?>) { |
||||
constIndex = 1; |
||||
exprIndex = 0; |
||||
} |
||||
if (Collection.class.isAssignableFrom(expr.getArg(constIndex).getType())) { |
||||
@SuppressWarnings("unchecked") // guarded by previous check
|
||||
Collection<?> values = ((Constant<? extends Collection<?>>) expr.getArg(constIndex)).getConstant(); |
||||
return asDocument(asDBKey(expr, exprIndex), asDocument("$nin", values)); |
||||
} else { |
||||
Path<?> path = (Path<?>) expr.getArg(exprIndex); |
||||
Constant<?> constant = (Constant<?>) expr.getArg(constIndex); |
||||
return asDocument(asDBKey(expr, exprIndex), asDocument("$ne", convert(path, constant))); |
||||
} |
||||
|
||||
} else if (op == Ops.COL_IS_EMPTY) { |
||||
List<Object> list = new ArrayList<Object>(2); |
||||
list.add(asDocument(asDBKey(expr, 0), new ArrayList<Object>())); |
||||
list.add(asDocument(asDBKey(expr, 0), asDocument("$exists", false))); |
||||
return asDocument("$or", list); |
||||
|
||||
} else if (op == Ops.LT) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$lt", asDBValue(expr, 1))); |
||||
|
||||
} else if (op == Ops.GT) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$gt", asDBValue(expr, 1))); |
||||
|
||||
} else if (op == Ops.LOE) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$lte", asDBValue(expr, 1))); |
||||
|
||||
} else if (op == Ops.GOE) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$gte", asDBValue(expr, 1))); |
||||
|
||||
} else if (op == Ops.IS_NULL) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$exists", false)); |
||||
|
||||
} else if (op == Ops.IS_NOT_NULL) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$exists", true)); |
||||
|
||||
} else if (op == Ops.CONTAINS_KEY) { |
||||
Path<?> path = (Path<?>) expr.getArg(0); |
||||
Expression<?> key = expr.getArg(1); |
||||
return asDocument(visit(path, context) + "." + key.toString(), asDocument("$exists", true)); |
||||
|
||||
} else if (op == MongodbOps.NEAR) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$near", asDBValue(expr, 1))); |
||||
|
||||
} else if (op == MongodbOps.NEAR_SPHERE) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$nearSphere", asDBValue(expr, 1))); |
||||
|
||||
} else if (op == MongodbOps.ELEM_MATCH) { |
||||
return asDocument(asDBKey(expr, 0), asDocument("$elemMatch", asDBValue(expr, 1))); |
||||
} |
||||
|
||||
throw new UnsupportedOperationException("Illegal operation " + expr); |
||||
} |
||||
|
||||
private Object negate(Document arg) { |
||||
List<Object> list = new ArrayList<Object>(); |
||||
for (Map.Entry<String, Object> entry : arg.entrySet()) { |
||||
if (entry.getKey().equals("$or")) { |
||||
list.add(asDocument("$nor", entry.getValue())); |
||||
|
||||
} else if (entry.getKey().equals("$and")) { |
||||
List<Object> list2 = new ArrayList<Object>(); |
||||
for (Object o : ((Collection) entry.getValue())) { |
||||
list2.add(negate((Document) o)); |
||||
} |
||||
list.add(asDocument("$or", list2)); |
||||
|
||||
} else if (entry.getValue() instanceof Pattern) { |
||||
list.add(asDocument(entry.getKey(), asDocument("$not", entry.getValue()))); |
||||
|
||||
} else if (entry.getValue() instanceof Document) { |
||||
list.add(negate(entry.getKey(), (Document) entry.getValue())); |
||||
|
||||
} else { |
||||
list.add(asDocument(entry.getKey(), asDocument("$ne", entry.getValue()))); |
||||
} |
||||
} |
||||
return list.size() == 1 ? list.get(0) : asDocument("$or", list); |
||||
} |
||||
|
||||
private Object negate(String key, Document value) { |
||||
if (value.size() == 1) { |
||||
return asDocument(key, asDocument("$not", value)); |
||||
|
||||
} else { |
||||
List<Object> list2 = new ArrayList<Object>(); |
||||
for (Map.Entry<String, Object> entry2 : value.entrySet()) { |
||||
list2.add(asDocument(key, asDocument("$not", asDocument(entry2.getKey(), entry2.getValue())))); |
||||
} |
||||
return asDocument("$or", list2); |
||||
} |
||||
} |
||||
|
||||
protected Object convert(Path<?> property, Constant<?> constant) { |
||||
if (isReference(property)) { |
||||
return asReference(constant.getConstant()); |
||||
} else if (isId(property)) { |
||||
if (isReference(property.getMetadata().getParent())) { |
||||
return asReferenceKey(property.getMetadata().getParent().getType(), constant.getConstant()); |
||||
} else if (constant.getType().equals(String.class) && isImplicitObjectIdConversion()) { |
||||
String id = (String) constant.getConstant(); |
||||
return ObjectId.isValid(id) ? new ObjectId(id) : id; |
||||
} |
||||
} |
||||
return visit(constant, null); |
||||
} |
||||
|
||||
protected boolean isImplicitObjectIdConversion() { |
||||
return true; |
||||
} |
||||
|
||||
protected DBRef asReferenceKey(Class<?> entity, Object id) { |
||||
// TODO override in subclass
|
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
protected abstract DBRef asReference(Object constant); |
||||
|
||||
protected abstract boolean isReference(Path<?> arg); |
||||
|
||||
protected boolean isId(Path<?> arg) { |
||||
// TODO override in subclass
|
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public String visit(Path<?> expr, Void context) { |
||||
PathMetadata metadata = expr.getMetadata(); |
||||
if (metadata.getParent() != null) { |
||||
Path<?> parent = metadata.getParent(); |
||||
if (parent.getMetadata().getPathType() == PathType.DELEGATE) { |
||||
parent = parent.getMetadata().getParent(); |
||||
} |
||||
if (metadata.getPathType() == PathType.COLLECTION_ANY) { |
||||
return visit(parent, context); |
||||
} else if (parent.getMetadata().getPathType() != PathType.VARIABLE) { |
||||
String rv = getKeyForPath(expr, metadata); |
||||
String parentStr = visit(parent, context); |
||||
return rv != null ? parentStr + "." + rv : parentStr; |
||||
} |
||||
} |
||||
return getKeyForPath(expr, metadata); |
||||
} |
||||
|
||||
protected String getKeyForPath(Path<?> expr, PathMetadata metadata) { |
||||
return metadata.getElement().toString(); |
||||
} |
||||
|
||||
@Override |
||||
public Object visit(SubQueryExpression<?> expr, Void context) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
|
||||
@Override |
||||
public Object visit(ParamExpression<?> expr, Void context) { |
||||
throw new UnsupportedOperationException(); |
||||
} |
||||
} |
||||
@ -0,0 +1,61 @@
@@ -0,0 +1,61 @@
|
||||
/* |
||||
* Copyright 2018 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.mongodb.repository.support; |
||||
|
||||
import java.util.Arrays; |
||||
|
||||
import com.querydsl.core.types.ConstantImpl; |
||||
import com.querydsl.core.types.Expression; |
||||
import com.querydsl.core.types.dsl.BooleanExpression; |
||||
import com.querydsl.core.types.dsl.Expressions; |
||||
import com.querydsl.mongodb.MongodbOps; |
||||
|
||||
/** |
||||
* Mongodb Document-API-specific operations. |
||||
* |
||||
* @author tiwe |
||||
* @author Mark Paluch |
||||
*/ |
||||
class MongodbExpressions { |
||||
|
||||
private MongodbExpressions() {} |
||||
|
||||
/** |
||||
* Finds the closest points relative to the given location and orders the results with decreasing proximity |
||||
* |
||||
* @param expr location |
||||
* @param latVal latitude |
||||
* @param longVal longitude |
||||
* @return predicate |
||||
*/ |
||||
public static BooleanExpression near(Expression<Double[]> expr, double latVal, double longVal) { |
||||
return Expressions.booleanOperation(MongodbOps.NEAR, expr, ConstantImpl.create(Arrays.asList(latVal, longVal))); |
||||
} |
||||
|
||||
/** |
||||
* Finds the closest points relative to the given location on a sphere and orders the results with decreasing |
||||
* proximity |
||||
* |
||||
* @param expr location |
||||
* @param latVal latitude |
||||
* @param longVal longitude |
||||
* @return predicate |
||||
*/ |
||||
public static BooleanExpression nearSphere(Expression<Double[]> expr, double latVal, double longVal) { |
||||
return Expressions.booleanOperation(MongodbOps.NEAR_SPHERE, expr, |
||||
ConstantImpl.create(Arrays.asList(latVal, longVal))); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue