55
pom.xml
55
pom.xml
@@ -22,6 +22,9 @@
|
||||
<couchbase.osgi>3.2.5</couchbase.osgi>
|
||||
<springdata.commons>2.7.0-SNAPSHOT</springdata.commons>
|
||||
<java-module-name>spring.data.couchbase</java-module-name>
|
||||
<apt>1.1.3</apt>
|
||||
<querydsl>5.0.0</querydsl>
|
||||
<mysema.querydsl>3.7.4</mysema.querydsl>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
@@ -37,6 +40,13 @@
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-apt</artifactId>
|
||||
<version>${querydsl}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
@@ -160,9 +170,10 @@
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>${javax-annotation-api}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- -addmods java.annotations.common -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.openwebbeans</groupId>
|
||||
<artifactId>openwebbeans-se</artifactId>
|
||||
@@ -298,6 +309,48 @@
|
||||
<groupId>org.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.mysema.maven</groupId>
|
||||
<artifactId>apt-maven-plugin</artifactId>
|
||||
<version>${apt}</version>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.querydsl</groupId>
|
||||
<artifactId>querydsl-apt</artifactId>
|
||||
<version>${querydsl}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-test-sources</phase>
|
||||
<goals>
|
||||
<goal>test-process</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>target/generated-test-sources</outputDirectory>
|
||||
<processor>org.springframework.data.couchbase.repository.support.CouchbaseAnnotationProcessor</processor>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>build-helper-maven-plugin</artifactId>
|
||||
<version>3.0.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>generate-test-sources</phase>
|
||||
<goals>
|
||||
<goal>add-source</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<sources>
|
||||
<source>target/generated-test-sources</source>
|
||||
</sources>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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 com.querydsl.couchbase.document;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
|
||||
import com.querydsl.core.DefaultQueryMetadata;
|
||||
import com.querydsl.core.JoinExpression;
|
||||
import com.querydsl.core.QueryMetadata;
|
||||
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.ExpressionUtils;
|
||||
import com.querydsl.core.types.FactoryExpression;
|
||||
import com.querydsl.core.types.Operation;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.ParamExpression;
|
||||
import com.querydsl.core.types.Path;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
/**
|
||||
* renamed from AbstractCouchbaseQuery to AbstractCouchbaseQueryDSL to avoid confusion with the AbstractCouchbaseQuery
|
||||
* that is in the package com.querydsl.couchbase
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
|
||||
public abstract class AbstractCouchbaseQueryDSL<Q extends AbstractCouchbaseQueryDSL<Q>> implements SimpleQuery<Q> {
|
||||
private final CouchbaseDocumentSerializer serializer;
|
||||
private final QueryMixin<Q> queryMixin;// = new QueryMixin(this, new DefaultQueryMetadata(), false);
|
||||
// TODO private ReadPreference readPreference;
|
||||
|
||||
public AbstractCouchbaseQueryDSL(CouchbaseDocumentSerializer serializer) {
|
||||
this.serializer = serializer;
|
||||
@SuppressWarnings("unchecked") // Q is this plus subclass
|
||||
Q query = (Q) this;
|
||||
this.queryMixin = new QueryMixin<Q>(query, new DefaultQueryMetadata(), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* mongodb uses createQuery(Predicate filter) where the serializer creates the 'query' <br>
|
||||
* and then uses the result to create a BasicQuery with queryObject = result <br>
|
||||
* Couchbase Query has a 'criteria' which is a <br>
|
||||
* List<QueryCriteriaDefinition> criteria <br>
|
||||
* so we could create a List<QueryCriteriaDefinition> or an uber QueryCriteria that combines <br>
|
||||
* all the sub QueryDefinitions in the filter.
|
||||
*/
|
||||
protected QueryCriteriaDefinition createCriteria(Predicate predicate) {
|
||||
// mongodb uses createQuery(Predicate filter) where the serializer creates the 'queryObject' of the BasicQuery
|
||||
return predicate != null ? (QueryCriteriaDefinition) this.serializer.handle(predicate) : null;
|
||||
}
|
||||
|
||||
// TODO - need later
|
||||
// public <T> JoinBuilder<Q, T> join(Path<T> ref, Path<T> target) {
|
||||
// return new JoinBuilder(this.queryMixin, ref, target);
|
||||
// }
|
||||
|
||||
// public <T> JoinBuilder<Q, T> join(CollectionPathBase<?, T, ?> ref, Path<T> target) {
|
||||
// return new JoinBuilder(this.queryMixin, ref, target);
|
||||
// }
|
||||
|
||||
// public <T> AnyEmbeddedBuilder<Q> anyEmbedded(Path<? extends Collection<T>> collection, Path<T> target) {
|
||||
// return new AnyEmbeddedBuilder(this.queryMixin, collection);
|
||||
// }
|
||||
|
||||
@Nullable
|
||||
protected Predicate createFilter(QueryMetadata metadata) {
|
||||
Predicate filter;
|
||||
if (!metadata.getJoins().isEmpty()) {
|
||||
filter = ExpressionUtils.allOf(new Predicate[] { metadata.getWhere(), this.createJoinFilter(metadata) });
|
||||
} else {
|
||||
filter = metadata.getWhere();
|
||||
}
|
||||
return filter;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Predicate createJoinFilter(QueryMetadata metadata) {
|
||||
Map<Expression<?>, Predicate> predicates = new HashMap();
|
||||
List<JoinExpression> joins = metadata.getJoins();
|
||||
|
||||
for (int i = joins.size() - 1; i >= 0; --i) {
|
||||
JoinExpression join = (JoinExpression) joins.get(i);
|
||||
Path<?> source = (Path) ((Operation) join.getTarget()).getArg(0);
|
||||
Path<?> target = (Path) ((Operation) join.getTarget()).getArg(1);
|
||||
Predicate extraFilters = (Predicate) predicates.get(target.getRoot());
|
||||
Predicate filter = ExpressionUtils.allOf(new Predicate[] { join.getCondition(), extraFilters });
|
||||
List<? extends Object> ids = this.getIds(target.getType(), filter);
|
||||
if (ids.isEmpty()) {
|
||||
throw new AbstractCouchbaseQueryDSL.NoResults();
|
||||
}
|
||||
|
||||
Path<?> path = ExpressionUtils.path(String.class, source, "$id");
|
||||
predicates.merge(source.getRoot(),
|
||||
ExpressionUtils.in(path, (Collection) ids/* TODO was just ids without casting to Collection */),
|
||||
ExpressionUtils::and);
|
||||
}
|
||||
|
||||
Path<?> source = (Path) ((Operation) ((JoinExpression) joins.get(0)).getTarget()).getArg(0);
|
||||
return predicates.get(source.getRoot());
|
||||
}
|
||||
|
||||
private Predicate allOf(Collection<Predicate> predicates) {
|
||||
return predicates != null ? ExpressionUtils.allOf(predicates) : null;
|
||||
}
|
||||
|
||||
protected abstract List<Object> getIds(Class<?> var1, Predicate var2);
|
||||
|
||||
public Q distinct() {
|
||||
return this.queryMixin.distinct();
|
||||
}
|
||||
|
||||
public Q where(Predicate e) {
|
||||
return this.queryMixin.where(e);
|
||||
}
|
||||
|
||||
public Q where(Predicate... e) {
|
||||
return this.queryMixin.where(e);
|
||||
}
|
||||
|
||||
public Q limit(long limit) {
|
||||
return this.queryMixin.limit(limit);
|
||||
}
|
||||
|
||||
public Q offset(long offset) {
|
||||
return this.queryMixin.offset(offset);
|
||||
}
|
||||
|
||||
public Q restrict(QueryModifiers modifiers) {
|
||||
return this.queryMixin.restrict(modifiers);
|
||||
}
|
||||
|
||||
public Q orderBy(OrderSpecifier<?> o) {
|
||||
return this.queryMixin.orderBy(o);
|
||||
}
|
||||
|
||||
public Q orderBy(OrderSpecifier<?>... o) {
|
||||
return this.queryMixin.orderBy(o);
|
||||
}
|
||||
|
||||
public <T> Q set(ParamExpression<T> param, T value) {
|
||||
return this.queryMixin.set(param, value);
|
||||
}
|
||||
|
||||
protected Map<String, String> createProjection(Expression<?> projection) {
|
||||
if (projection instanceof FactoryExpression) {
|
||||
Map<String, String> obj = new HashMap();
|
||||
Iterator var3 = ((FactoryExpression) projection).getArgs().iterator();
|
||||
|
||||
while (var3.hasNext()) {
|
||||
Object expr = var3.next();
|
||||
if (expr instanceof Expression) {
|
||||
obj.put(expr.toString(), (String) this.serializer.handle((Expression) expr));
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected CouchbaseDocument createQuery(@Nullable Predicate predicate) {
|
||||
return predicate != null ? (CouchbaseDocument) this.serializer.handle(predicate) : new CouchbaseDocument();
|
||||
}
|
||||
|
||||
// public void setReadPreference(ReadPreference readPreference) {
|
||||
// this.readPreference = readPreference;
|
||||
// }
|
||||
|
||||
protected QueryMixin<Q> getQueryMixin() {
|
||||
return this.queryMixin;
|
||||
}
|
||||
|
||||
protected CouchbaseDocumentSerializer getSerializer() {
|
||||
return this.serializer;
|
||||
}
|
||||
|
||||
// protected ReadPreference getReadPreference() {
|
||||
// return this.readPreference;
|
||||
// }
|
||||
|
||||
public CouchbaseDocument asDocument() {
|
||||
return this.createQuery(this.queryMixin.getMetadata().getWhere());
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return this.asDocument().toString();
|
||||
}
|
||||
|
||||
static class NoResults extends RuntimeException {
|
||||
NoResults() {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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 com.querydsl.couchbase.document;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.couchbase.repository.support.DBRef;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import com.querydsl.core.types.Constant;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.ExpressionUtils;
|
||||
import com.querydsl.core.types.FactoryExpression;
|
||||
import com.querydsl.core.types.Operation;
|
||||
import com.querydsl.core.types.Operator;
|
||||
import com.querydsl.core.types.Ops;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.ParamExpression;
|
||||
import com.querydsl.core.types.Path;
|
||||
import com.querydsl.core.types.PathMetadata;
|
||||
import com.querydsl.core.types.PathType;
|
||||
import com.querydsl.core.types.SubQueryExpression;
|
||||
import com.querydsl.core.types.TemplateExpression;
|
||||
import com.querydsl.core.types.Visitor;
|
||||
|
||||
/**
|
||||
* Serializes the given Querydsl query to a Document query for Couchbase.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public abstract class CouchbaseDocumentSerializer implements Visitor<Object, Void> {
|
||||
|
||||
boolean workInProgress = true;
|
||||
|
||||
public Object handle(Expression<?> expression) {
|
||||
return expression.accept(this, null);
|
||||
}
|
||||
|
||||
public Sort toSort(List<OrderSpecifier<?>> orderBys) {
|
||||
Sort sort = Sort.unsorted();
|
||||
for (OrderSpecifier<?> orderBy : orderBys) {
|
||||
Object key = orderBy.getTarget().accept(this, null);
|
||||
// sort.and(Sort.by(orderBy));
|
||||
// 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 QueryCriteriaDefinition asDocument(String key, Object value) {
|
||||
QueryCriteria qc = null;
|
||||
if (1 == 1) {
|
||||
throw new UnsupportedOperationException("Wrong path to create this criteria " + key);
|
||||
}
|
||||
if (key.equals("$and") || key.equals("$or") /* value instanceof QueryCriteria[] */) {
|
||||
throw new UnsupportedOperationException("Wrong path to create this criteria " + key);
|
||||
} else if (key.equals("$in") /* value instanceof QueryCriteria[] */) {
|
||||
throw new RuntimeException(("not supported"));
|
||||
} else {
|
||||
qc = QueryCriteria.where(key).is(value);
|
||||
}
|
||||
|
||||
return qc;
|
||||
}
|
||||
|
||||
@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
|
||||
|| lhs.getOperator() == Ops.STRING_LENGTH) {
|
||||
// return asDocument(asDBKey(lhs, 0), asDocument("$size", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).is(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));
|
||||
*/
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).is(asDBValue(expr, 1));
|
||||
}
|
||||
} else if (op == Ops.STRING_IS_EMPTY) {
|
||||
// return asDocument(asDBKey(expr, 0), "");
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).isNotValued().or(asDBKey(expr, 0)).is("");
|
||||
} 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 {
|
||||
QueryCriteria arg = (QueryCriteria) handle(expr.getArg(0));
|
||||
return arg.negate(); // negate(arg);
|
||||
}
|
||||
|
||||
} else if (op == Ops.AND) {
|
||||
// return asDocument("$and", collectConnectorArgs("$and", expr));
|
||||
return collectConnectorArgs("$and", expr);
|
||||
} else if (op == Ops.OR) {
|
||||
// return asDocument("$or", collectConnectorArgs("$or", expr));
|
||||
return collectConnectorArgs("$or", expr);
|
||||
} 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)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).ne(asDBValue(expr, 1));
|
||||
} else if (op == Ops.STARTS_WITH) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression("^" + regexValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).startingWith(asDBValue(expr, 1));
|
||||
} else if (op == Ops.STARTS_WITH_IC) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression("^" + regexValue(expr, 1), "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).upper()
|
||||
.startingWith(asDBValue(expr, 1).toString().toUpperCase(Locale.ROOT));
|
||||
} else if (op == Ops.ENDS_WITH) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(regexValue(expr, 1) + "$"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).endingWith(asDBValue(expr, 1));
|
||||
} else if (op == Ops.ENDS_WITH_IC) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(regexValue(expr, 1) + "$", "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).upper()
|
||||
.endingWith(asDBValue(expr, 1).toString().toUpperCase(Locale.ROOT));
|
||||
} else if (op == Ops.EQ_IGNORE_CASE) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression("^" + regexValue(expr, 1) + "$", "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).upper().eq(asDBValue(expr, 1).toString().toUpperCase(Locale.ROOT));
|
||||
} else if (op == Ops.STRING_CONTAINS) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(".*" + regexValue(expr, 1) + ".*"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).containing(asDBValue(expr, 1));
|
||||
} else if (op == Ops.STRING_CONTAINS_IC) {
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(".*" + regexValue(expr, 1) + ".*", "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).upper()
|
||||
.containing(asDBValue(expr, 1).toString().toUpperCase(Locale.ROOT));
|
||||
/*
|
||||
} else if (op == Ops.MATCHES) {
|
||||
//return asDocument(asDBKey(expr, 0), new CBRegularExpression(asDBValue(expr, 1).toString()));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).like(asDBValue(expr,1));
|
||||
} else if (op == Ops.MATCHES_IC) {
|
||||
//return asDocument(asDBKey(expr, 0), new CBRegularExpression(asDBValue(expr, 1).toString(), "i"));
|
||||
return QueryCriteria.where("UPPER("+asDBKey(expr, 0)+")").like("UPPER("+asDBValue(expr,1)+")");
|
||||
*/
|
||||
} else if (op == Ops.LIKE) {
|
||||
// String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(regex));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).like(asDBValue(expr, 1));
|
||||
} else if (op == Ops.LIKE_IC) {
|
||||
// String regex = ExpressionUtils.likeToRegex((Expression) expr.getArg(1)).toString();
|
||||
// return asDocument(asDBKey(expr, 0), new CBRegularExpression(regex, "i"));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).upper().like(asDBValue(expr, 1).toString().toUpperCase(Locale.ROOT));
|
||||
} else if (op == Ops.BETWEEN) {
|
||||
// Document value = new Document("$gte", this.asDBValue(expr, 1));
|
||||
// value.append("$lte", this.asDBValue(expr, 2));
|
||||
// return this.asDocument(this.asDBKey(expr, 0), value);
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).between(asDBValue(expr, 1), asDBValue(expr, 2));
|
||||
} 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));
|
||||
return QueryCriteria.where(asDBKey(expr, exprIndex)).in(values);
|
||||
} else { // I think framework already converts IN to EQ if arg is not a collection
|
||||
// Path<?> path = (Path<?>) expr.getArg(exprIndex);
|
||||
// Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
|
||||
// return asDocument(asDBKey(expr, exprIndex), convert(path, constant));
|
||||
Object value = expr.getArg(constIndex);
|
||||
return QueryCriteria.where(asDBKey(expr, exprIndex)).eq(value);
|
||||
}
|
||||
|
||||
} 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));
|
||||
return QueryCriteria.where(asDBKey(expr, exprIndex)).notIn(values);
|
||||
} else { // I think framework already converts NOT_IN to NE if arg is not a collection
|
||||
// Path<?> path = (Path<?>) expr.getArg(exprIndex);
|
||||
// Constant<?> constant = (Constant<?>) expr.getArg(constIndex);
|
||||
// return asDocument(asDBKey(expr, exprIndex), asDocument("$ne", convert(path, constant)));
|
||||
Object value = expr.getArg(constIndex);
|
||||
return QueryCriteria.where(asDBKey(expr, exprIndex)).ne(value);
|
||||
}
|
||||
|
||||
} 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);
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).isNotValued();
|
||||
} else if (op == Ops.LT) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$lt", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).lt(asDBValue(expr, 1));
|
||||
} else if (op == Ops.GT) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$gt", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).gt(asDBValue(expr, 1));
|
||||
} else if (op == Ops.LOE) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$lte", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).lte(asDBValue(expr, 1));
|
||||
} else if (op == Ops.GOE) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$gte", asDBValue(expr, 1)));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).gte(asDBValue(expr, 1));
|
||||
} else if (op == Ops.IS_NULL) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$exists", false));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).isNull();
|
||||
} else if (op == Ops.IS_NOT_NULL) {
|
||||
// return asDocument(asDBKey(expr, 0), asDocument("$exists", true));
|
||||
return QueryCriteria.where(asDBKey(expr, 0)).isNotNull();
|
||||
} else if (op == Ops.CONTAINS_KEY) { // TODO not sure about this one
|
||||
Path<?> path = (Path<?>) expr.getArg(0);
|
||||
// Expression<?> key = expr.getArg(1);
|
||||
// return asDocument(visit(path, context) + "." + key.toString(), asDocument("$exists", true));
|
||||
return QueryCriteria.where("meta().id"/*asDBKey(expr, 0)*/).eq(asDBKey(expr, 1));
|
||||
} else if (op == Ops.STRING_LENGTH) {
|
||||
return "LENGTH(" + asDBKey(expr, 0) + ")";// QueryCriteria.where(asDBKey(expr, 0)).size();
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException("Illegal operation " + expr);
|
||||
}
|
||||
|
||||
/* TODO -- need later
|
||||
private Object negate(QueryCriteriaDefinition 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((QueryCriteriaDefinition) o));
|
||||
}
|
||||
list.add(asDocument("$or", list2));
|
||||
|
||||
} else if (entry.getValue() instanceof Pattern || entry.getValue() instanceof CBRegularExpression) {
|
||||
list.add(asDocument(entry.getKey(), asDocument("$not", entry.getValue())));
|
||||
|
||||
} else if (entry.getValue() instanceof QueryCriteriaDefinition) {
|
||||
list.add(negate(entry.getKey(), (QueryCriteriaDefinition) 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, QueryCriteriaDefinition 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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
/* TODO -- need later
|
||||
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();
|
||||
}
|
||||
|
||||
private QueryCriteriaDefinition collectConnectorArgs(String operator, Operation<?> operation) {
|
||||
QueryCriteria first = null;
|
||||
for (Expression<?> exp : operation.getArgs()) {
|
||||
QueryCriteria document = (QueryCriteria) handle(exp);
|
||||
if (first == null) {
|
||||
first = document;
|
||||
} else {
|
||||
if (operator.equals("$or")) {
|
||||
first = first.or(document);
|
||||
} else if (operator.equals("$and")) {
|
||||
first = first.and(document);
|
||||
}
|
||||
}
|
||||
}
|
||||
return first;
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,25 @@ public abstract class AbstractCouchbaseConverter implements CouchbaseConverter,
|
||||
|
||||
}
|
||||
|
||||
/* TODO needed later
|
||||
@Override
|
||||
public Object convertToCouchbaseType(Object value, TypeInformation<?> typeInformation) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.conversions.getCustomWriteTarget(value.getClass()) //
|
||||
.map(it -> (Object) this.conversionService.convert(value, it)) //
|
||||
.orElseGet(() -> Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertToCouchbaseType(String source) {
|
||||
return source;
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public Class<?> getWriteClassFor(Class<?> clazz) {
|
||||
return this.conversions.getCustomWriteTarget(clazz).orElse(clazz);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -29,6 +29,7 @@ import org.springframework.data.util.TypeInformation;
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Simon Baslé
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface CouchbaseConverter
|
||||
extends EntityConverter<CouchbasePersistentEntity<?>, CouchbasePersistentProperty, Object, CouchbaseDocument>,
|
||||
@@ -61,4 +62,10 @@ public interface CouchbaseConverter
|
||||
* @return the alias value for the type
|
||||
*/
|
||||
Alias getTypeAlias(TypeInformation<?> info);
|
||||
|
||||
// TODO needed later
|
||||
// CouchbaseTypeMapper getMapper();
|
||||
// Object convertToCouchbaseType(Object source, TypeInformation<?> typeInformation);
|
||||
//
|
||||
// Object convertToCouchbaseType(String source);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2021 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -35,6 +35,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Mark Paluch
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T, CouchbasePersistentProperty>
|
||||
implements CouchbasePersistentEntity<T>, EnvironmentAware {
|
||||
@@ -164,4 +165,14 @@ public class BasicCouchbasePersistentEntity<T> extends BasicPersistentEntity<T,
|
||||
return annotation == null ? false : annotation.touchOnRead() && getExpiry() > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTextScoreProperty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CouchbasePersistentProperty getTextScoreProperty() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.springframework.data.mapping.PersistentEntity;
|
||||
* Represents an entity that can be persisted which contains 0 or more properties.
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public interface CouchbasePersistentEntity<T> extends PersistentEntity<T, CouchbasePersistentProperty> {
|
||||
|
||||
@@ -61,4 +62,7 @@ public interface CouchbasePersistentEntity<T> extends PersistentEntity<T, Couchb
|
||||
*/
|
||||
boolean isTouchOnRead();
|
||||
|
||||
boolean hasTextScoreProperty();
|
||||
|
||||
CouchbasePersistentProperty getTextScoreProperty();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -18,6 +18,12 @@ package org.springframework.data.couchbase.core.query;
|
||||
import java.io.Serializable;
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* A N1QL Query Expression
|
||||
*
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class N1QLExpression {
|
||||
private static final N1QLExpression NULL_INSTANCE = new N1QLExpression("NULL");
|
||||
private static final N1QLExpression TRUE_INSTANCE = new N1QLExpression("TRUE");
|
||||
@@ -90,6 +96,15 @@ public class N1QLExpression {
|
||||
return NULL_INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an expression representing WRAPPER.
|
||||
*
|
||||
* @return an expression representing WRAPPER.
|
||||
*/
|
||||
public static N1QLExpression WRAPPER() {
|
||||
return EMPTY_INSTANCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an expression representing MISSING.
|
||||
*
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import static org.springframework.util.Assert.notNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -53,7 +56,7 @@ public class Query {
|
||||
private int limit;
|
||||
private boolean distinct;
|
||||
private String[] distinctFields;
|
||||
private Sort sort = Sort.unsorted();
|
||||
protected Sort sort = Sort.unsorted();
|
||||
private QueryScanConsistency queryScanConsistency;
|
||||
private Meta meta;
|
||||
|
||||
@@ -66,6 +69,19 @@ public class Query {
|
||||
addCriteria(criteriaDefinition);
|
||||
}
|
||||
|
||||
public Query(Query that) {
|
||||
Assert.notNull(that, "source query cannot be null");
|
||||
this.criteria.addAll(that.criteria);
|
||||
this.parameters = that.parameters;
|
||||
this.skip = that.skip;
|
||||
this.limit = that.limit;
|
||||
this.distinct = that.distinct;
|
||||
this.distinctFields = that.distinctFields;
|
||||
this.sort = that.sort;
|
||||
this.queryScanConsistency = that.queryScanConsistency;
|
||||
this.meta = that.meta;
|
||||
};
|
||||
|
||||
public static Query query(QueryCriteriaDefinition criteriaDefinition) {
|
||||
return new Query(criteriaDefinition);
|
||||
}
|
||||
@@ -75,6 +91,10 @@ public class Query {
|
||||
return this;
|
||||
}
|
||||
|
||||
protected List<QueryCriteriaDefinition> getCriteriaList() {
|
||||
return this.criteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the postional parameters on the query object There can only be named parameters or positional parameters - not
|
||||
* both.
|
||||
@@ -209,7 +229,7 @@ public class Query {
|
||||
* @return
|
||||
*/
|
||||
public Query with(final Sort sort) {
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
notNull(sort, "Sort must not be null!");
|
||||
if (sort.isUnsorted()) {
|
||||
return this;
|
||||
}
|
||||
@@ -348,9 +368,9 @@ public class Query {
|
||||
return statement.toString();
|
||||
}
|
||||
|
||||
public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(
|
||||
ReactiveCouchbaseTemplate template, String collectionName,
|
||||
Class domainClass, Class returnClass, boolean isCount, String[] distinctFields, String[] fields) {
|
||||
public static StringBasedN1qlQueryParser.N1qlSpelValues getN1qlSpelValues(ReactiveCouchbaseTemplate template,
|
||||
String collectionName, Class domainClass, Class returnClass, boolean isCount, String[] distinctFields,
|
||||
String[] fields) {
|
||||
String typeKey = template.getConverter().getTypeKey();
|
||||
final CouchbasePersistentEntity<?> persistentEntity = template.getConverter().getMappingContext()
|
||||
.getRequiredPersistentEntity(domainClass);
|
||||
@@ -391,4 +411,51 @@ public class Query {
|
||||
return meta;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!o.getClass().isAssignableFrom(getClass())) {
|
||||
return false;
|
||||
}
|
||||
Query that = (Query) o;
|
||||
if (this.criteria.size() != that.criteria.size()) {
|
||||
return false;
|
||||
}
|
||||
if (this.criteria.equals(that.criteria)) {
|
||||
return false;
|
||||
}
|
||||
int i = 0;
|
||||
for (QueryCriteriaDefinition thisCriteria : this.criteria) {
|
||||
if (!thisCriteria.equals(that.criteria.get(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.parameters.equals(that.parameters)) {
|
||||
return false;
|
||||
}
|
||||
;
|
||||
if (this.skip != that.skip) {
|
||||
return false;
|
||||
}
|
||||
if (this.limit != that.limit) {
|
||||
return false;
|
||||
}
|
||||
if (this.distinct != that.distinct) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Arrays.equals(this.distinctFields, that.distinctFields)) {
|
||||
return false;
|
||||
}
|
||||
if (this.sort != that.sort) {
|
||||
return false;
|
||||
}
|
||||
if (this.queryScanConsistency != that.queryScanConsistency) {
|
||||
return false;
|
||||
}
|
||||
if (!meta.equals(that.meta)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import static org.springframework.data.couchbase.core.query.N1QLExpression.x;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Formatter;
|
||||
@@ -41,11 +40,11 @@ import com.couchbase.client.java.json.JsonValue;
|
||||
*/
|
||||
public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
|
||||
private final N1QLExpression key;
|
||||
private N1QLExpression key;
|
||||
/**
|
||||
* Holds the chain itself, the current operator being always the last one.
|
||||
*/
|
||||
private List<QueryCriteria> criteriaChain;
|
||||
private LinkedList<QueryCriteria> criteriaChain;
|
||||
/**
|
||||
* Represents how the chain is hung together, null only for the first element.
|
||||
*/
|
||||
@@ -54,29 +53,27 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
private Object[] value;
|
||||
private String format;
|
||||
|
||||
QueryCriteria(List<QueryCriteria> chain, N1QLExpression key, Object[] value, ChainOperator chainOperator) {
|
||||
public QueryCriteria(LinkedList<QueryCriteria> chain, N1QLExpression key, Object[] value,
|
||||
ChainOperator chainOperator) {
|
||||
this(chain, key, value, chainOperator, null, null);
|
||||
}
|
||||
|
||||
QueryCriteria(List<QueryCriteria> chain, N1QLExpression key, Object value, ChainOperator chainOperator) {
|
||||
QueryCriteria(LinkedList<QueryCriteria> chain, N1QLExpression key, Object value, ChainOperator chainOperator) {
|
||||
this(chain, key, new Object[] { value }, chainOperator, null, null);
|
||||
}
|
||||
|
||||
QueryCriteria(List<QueryCriteria> chain, N1QLExpression key, Object[] value, ChainOperator chainOperator,
|
||||
QueryCriteria(LinkedList<QueryCriteria> chain, N1QLExpression key, Object[] value, ChainOperator chainOperator,
|
||||
String operator, String format) {
|
||||
this.criteriaChain = chain;
|
||||
criteriaChain.add(this);
|
||||
this.criteriaChain = chain != null ? chain : new LinkedList<>();
|
||||
this.chainOperator = chainOperator;
|
||||
this.criteriaChain.add(this);// add the new one to the chain. The new one has the chainOperator set
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.chainOperator = chainOperator;
|
||||
// this.chainOperator = chainOperator; ignored now.
|
||||
this.operator = operator;
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
Object[] getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static factory method to create a Criteria using the provided String key.
|
||||
*/
|
||||
@@ -88,13 +85,56 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
* Static factory method to create a Criteria using the provided N1QLExpression key.
|
||||
*/
|
||||
public static QueryCriteria where(N1QLExpression key) {
|
||||
return new QueryCriteria(new ArrayList<>(), key, null, null);
|
||||
return new QueryCriteria(null, key, null, null);
|
||||
}
|
||||
|
||||
// wrap criteria (including the criteriaChain) in a new QueryCriteria and set the queryChain of the original criteria
|
||||
// to just itself. The new query will be the value[0] of the original query.
|
||||
private void replaceThisAsWrapperOf(QueryCriteria criteria) {
|
||||
QueryCriteria qc = new QueryCriteria(criteria.criteriaChain, criteria.key, criteria.value, criteria.chainOperator,
|
||||
criteria.operator, criteria.format);
|
||||
criteriaChain.remove(criteria); // we replaced it with the clone
|
||||
criteriaChain = new LinkedList<>();
|
||||
criteriaChain.add(this);
|
||||
key = N1QLExpression.WRAPPER();
|
||||
operator = "";
|
||||
value = new Object[] { qc };
|
||||
chainOperator = null;
|
||||
}
|
||||
|
||||
// wrap criteria (including the criteriaChain) in a new QueryCriteria and set the queryChain of the original criteria
|
||||
// to just itself. The new query will be the value[0] of the original query.
|
||||
private void wrapThis() {
|
||||
QueryCriteria qc = new QueryCriteria(this.criteriaChain, this.key, this.value, this.chainOperator, this.operator,
|
||||
this.format);
|
||||
this.criteriaChain.remove(this); // we replaced it with the clone
|
||||
this.criteriaChain = new LinkedList<>();
|
||||
this.criteriaChain.add(this);
|
||||
this.key = N1QLExpression.WRAPPER();
|
||||
this.operator = "";
|
||||
this.format = null;
|
||||
this.value = new Object[] { qc };
|
||||
this.chainOperator = null;
|
||||
|
||||
}
|
||||
|
||||
// wrap criteria (including the criteriaChain) in a new QueryCriteria and set the queryChain of the original criteria
|
||||
// to just itself. The new query will be the value[0] of the original query.
|
||||
private static QueryCriteria wrap(QueryCriteria criteria) {
|
||||
QueryCriteria qc = new QueryCriteria(new LinkedList<>(), criteria.key, criteria.value, null, criteria.operator,
|
||||
criteria.format);
|
||||
return qc;
|
||||
QueryCriteria qc = new QueryCriteria(criteria.criteriaChain, criteria.key, criteria.value, criteria.chainOperator,
|
||||
criteria.operator, criteria.format);
|
||||
criteria.criteriaChain.remove(qc);
|
||||
int idx = criteria.criteriaChain.indexOf(criteria);
|
||||
criteria.criteriaChain.add(idx, qc);
|
||||
criteria.criteriaChain.remove(criteria); // we replaced it with the clone
|
||||
criteria.criteriaChain = new LinkedList<>();
|
||||
criteria.criteriaChain.add(criteria);
|
||||
criteria.key = N1QLExpression.WRAPPER();
|
||||
criteria.operator = "";
|
||||
criteria.format = null;
|
||||
criteria.value = new Object[] { qc };
|
||||
criteria.chainOperator = null;
|
||||
return criteria;
|
||||
}
|
||||
|
||||
public QueryCriteria and(String key) {
|
||||
@@ -102,11 +142,40 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
}
|
||||
|
||||
public QueryCriteria and(N1QLExpression key) {
|
||||
// this.criteriaChain.getLast().setChainOperator(ChainOperator.AND);
|
||||
return new QueryCriteria(this.criteriaChain, key, null, ChainOperator.AND);
|
||||
}
|
||||
|
||||
public QueryCriteria and(QueryCriteria criteria) {
|
||||
return new QueryCriteria(this.criteriaChain, null, criteria, ChainOperator.AND);
|
||||
if (this.criteriaChain != null && !this.criteriaChain.contains(this)) {
|
||||
throw new RuntimeException("criteria chain does not include this");
|
||||
}
|
||||
if (this.criteriaChain == null) {
|
||||
this.criteriaChain = new LinkedList<>();
|
||||
this.criteriaChain.add(this);
|
||||
}
|
||||
QueryCriteria newThis = wrap(this);
|
||||
QueryCriteria qc = wrap(criteria);
|
||||
newThis.criteriaChain.add(qc);
|
||||
qc.setChainOperator(ChainOperator.AND);
|
||||
newThis.chainOperator = ChainOperator.AND;// otherwise we get "A chain operator must be present when chaining"
|
||||
return newThis;
|
||||
}
|
||||
|
||||
// upper and lower should work like and/or by pushing "this" to a 'value' and changing "this" to upper()/lower()
|
||||
public QueryCriteria upper() {
|
||||
key = x("UPPER(" + key + ")");
|
||||
operator = "UPPER";
|
||||
value = new Object[] {};
|
||||
return this;
|
||||
}
|
||||
|
||||
// upper and lower should work like and/or by pushing "this" to a 'value' and changing "this" to upper()/lower()
|
||||
public QueryCriteria lower() {
|
||||
key = x("LOWER(" + key + ")");
|
||||
operator = "LOWER";
|
||||
value = new Object[] {};
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryCriteria or(String key) {
|
||||
@@ -114,11 +183,25 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
}
|
||||
|
||||
public QueryCriteria or(N1QLExpression key) {
|
||||
// this.criteriaChain.getLast().setChainOperator(ChainOperator.OR);
|
||||
return new QueryCriteria(this.criteriaChain, key, null, ChainOperator.OR);
|
||||
}
|
||||
|
||||
public QueryCriteria or(QueryCriteria criteria) {
|
||||
return new QueryCriteria(this.criteriaChain, null, criteria, ChainOperator.OR);
|
||||
if (this.criteriaChain != null && !this.criteriaChain.contains(this)) {
|
||||
throw new RuntimeException("criteria chain does not include this");
|
||||
}
|
||||
if (this.criteriaChain == null) {
|
||||
this.criteriaChain = new LinkedList<>();
|
||||
this.criteriaChain.add(this);
|
||||
}
|
||||
QueryCriteria newThis = wrap(this);
|
||||
QueryCriteria qc = wrap(criteria);
|
||||
qc.criteriaChain = newThis.criteriaChain;
|
||||
newThis.criteriaChain.add(qc);
|
||||
qc.setChainOperator(ChainOperator.OR);
|
||||
newThis.chainOperator = ChainOperator.OR;// otherwise we get "A chain operator must be present when chaining"
|
||||
return newThis;
|
||||
}
|
||||
|
||||
public QueryCriteria eq(@Nullable Object o) {
|
||||
@@ -204,12 +287,28 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
}
|
||||
|
||||
public QueryCriteria notContaining(@Nullable Object o) {
|
||||
value = new QueryCriteria[] { wrap(containing(o)) };
|
||||
replaceThisAsWrapperOf(containing(o));
|
||||
operator = "NOT";
|
||||
format = "not( %3$s )";
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryCriteria negate() {
|
||||
replaceThisAsWrapperOf(this);
|
||||
operator = "NOT";
|
||||
format = "not( %3$s )";
|
||||
// criteriaChain = new LinkedList<>();
|
||||
// criteriaChain.add(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryCriteria size() {
|
||||
replaceThisAsWrapperOf(this);
|
||||
operator = "LENGTH";
|
||||
format = "LENGTH( %3$s )";
|
||||
return this;
|
||||
}
|
||||
|
||||
public QueryCriteria like(@Nullable Object o) {
|
||||
operator = "LIKE";
|
||||
value = new Object[] { o };
|
||||
@@ -315,10 +414,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
}
|
||||
|
||||
public QueryCriteria notIn(@Nullable Object... o) {
|
||||
value = new QueryCriteria[] { wrap(in(o)) };
|
||||
operator = "NOT";
|
||||
format = "not( %3$s )"; // field = 1$, operator = 2$, value=$3, $4, ...
|
||||
return this;
|
||||
return in(o).negate();
|
||||
}
|
||||
|
||||
public QueryCriteria TRUE() { // true/false are reserved, use TRUE/FALSE
|
||||
@@ -329,9 +425,9 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
}
|
||||
|
||||
public QueryCriteria FALSE() {
|
||||
value = new QueryCriteria[] { wrap(TRUE()) };
|
||||
operator = "not";
|
||||
format = "not( %3$s )";
|
||||
value = null;
|
||||
operator = "NOT";
|
||||
format = "not(%1$s)"; // field = 1$, operator = 2$, value=$3, $4, ...
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -352,7 +448,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
for (QueryCriteria c : this.criteriaChain) {
|
||||
if (!first) {
|
||||
if (c.chainOperator == null) {
|
||||
throw new IllegalStateException("A chain operator must be present when chaining!");
|
||||
throw new IllegalStateException("A chain operator must be present when chaining! \n" + c);
|
||||
}
|
||||
// the consistent place to output this would be in the c.exportSingle(output) about five lines down
|
||||
output.append(" ").append(c.chainOperator.representation).append(" ");
|
||||
@@ -509,7 +605,12 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
enum ChainOperator {
|
||||
@Override
|
||||
public void setChainOperator(ChainOperator chainOperator) {
|
||||
this.chainOperator = chainOperator;
|
||||
}
|
||||
|
||||
public enum ChainOperator {
|
||||
AND("and"), OR("or");
|
||||
|
||||
private final String representation;
|
||||
@@ -518,9 +619,25 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
||||
this.representation = representation;
|
||||
}
|
||||
|
||||
public static ChainOperator from(String operator) {
|
||||
return valueOf(operator.substring(1).toUpperCase());
|
||||
}
|
||||
|
||||
public String getRepresentation() {
|
||||
return representation;
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append("{key: " + this.key);
|
||||
sb.append(", operator: " + this.operator);
|
||||
sb.append(", value: " + this.value);
|
||||
sb.append(", criteriaChain.size(): " + this.criteriaChain.size() + "\n");
|
||||
for (QueryCriteria qc : criteriaChain) {
|
||||
sb.append(" key: " + qc.key + " operator: " + qc.operator + "\n");
|
||||
}
|
||||
sb.append("}");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2010-2020 the original author or authors.
|
||||
* Copyright 2010-2022 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.
|
||||
@@ -15,9 +15,10 @@
|
||||
*/
|
||||
package org.springframework.data.couchbase.core.query;
|
||||
|
||||
import com.couchbase.client.java.json.JsonValue;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
|
||||
import com.couchbase.client.java.json.JsonValue;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Christoph Strobl
|
||||
@@ -44,4 +45,6 @@ public interface QueryCriteriaDefinition {
|
||||
* @return string containing part of N1QL query
|
||||
*/
|
||||
String export();
|
||||
|
||||
void setChainOperator(QueryCriteria.ChainOperator chainOperator);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors.
|
||||
* Copyright 2020-2022 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.
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
import static org.springframework.util.ObjectUtils.nullSafeEquals;
|
||||
import static org.springframework.util.ObjectUtils.nullSafeHashCode;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.couchbase.core.query.Query;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* BasicQuery for Querydsl
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class BasicQuery extends Query {
|
||||
|
||||
Map<String, String> projectionFields;
|
||||
|
||||
/**
|
||||
* Create a new {@link BasicQuery} given a query {@link CouchbaseDocument} and field specification
|
||||
* {@link CouchbaseDocument}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param projectionFields must not be {@literal null}.
|
||||
* @throws IllegalArgumentException when {@code sortObject} or {@code fieldsObject} is {@literal null}.
|
||||
*/
|
||||
public BasicQuery(Query query, Map<String, String> projectionFields) {
|
||||
super(query);
|
||||
Assert.notNull(projectionFields, "Field document must not be null");
|
||||
this.projectionFields = projectionFields;
|
||||
}
|
||||
|
||||
public BasicQuery(QueryCriteriaDefinition criteria, Map<String, String> projectionFields) {
|
||||
addCriteria(criteria);
|
||||
this.projectionFields = projectionFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort {@link CouchbaseDocument}.
|
||||
*
|
||||
* @param sort must not be {@literal null}.
|
||||
* @throws IllegalArgumentException when {@code sortObject} is {@literal null}.
|
||||
*/
|
||||
public void setSort(Sort sort) {
|
||||
Assert.notNull(sort, "Sort must not be null");
|
||||
with(sort);
|
||||
}
|
||||
|
||||
/*
|
||||
* indicates if the query is sorted
|
||||
*/
|
||||
public boolean isSorted() {
|
||||
return sort != null && sort != Sort.unsorted();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fields (projection) {@link CouchbaseDocument}.
|
||||
*
|
||||
* @param projectionFields must not be {@literal null}.
|
||||
* @throws IllegalArgumentException when {@code fieldsObject} is {@literal null}.
|
||||
*/
|
||||
public void setProjectionFields(Map<String, String> projectionFields) {
|
||||
Assert.notNull(projectionFields, "Field document must not be null");
|
||||
this.projectionFields = projectionFields;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (!(o instanceof BasicQuery)) {
|
||||
return false;
|
||||
}
|
||||
BasicQuery that = (BasicQuery) o;
|
||||
return querySettingsEquals(that) && //
|
||||
nullSafeEquals(projectionFields, that.projectionFields) && //
|
||||
nullSafeEquals(sort, that.sort);
|
||||
}
|
||||
|
||||
private boolean querySettingsEquals(BasicQuery that) {
|
||||
return super.equals(that);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
|
||||
int result = super.hashCode();
|
||||
result = 31 * result + nullSafeHashCode(getCriteriaList());
|
||||
result = 31 * result + nullSafeHashCode(projectionFields);
|
||||
result = 31 * result + nullSafeHashCode(sort);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2011-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||
import javax.annotation.processing.SupportedSourceVersion;
|
||||
import javax.lang.model.SourceVersion;
|
||||
import javax.tools.Diagnostic;
|
||||
|
||||
import org.springframework.data.couchbase.core.mapping.Document;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.querydsl.apt.AbstractQuerydslProcessor;
|
||||
import com.querydsl.apt.Configuration;
|
||||
import com.querydsl.apt.DefaultConfiguration;
|
||||
import com.querydsl.core.annotations.QueryEmbeddable;
|
||||
import com.querydsl.core.annotations.QueryEmbedded;
|
||||
import com.querydsl.core.annotations.QueryEntities;
|
||||
import com.querydsl.core.annotations.QuerySupertype;
|
||||
import com.querydsl.core.annotations.QueryTransient;
|
||||
|
||||
/**
|
||||
* Annotation processor to create Querydsl query types for QueryDsl annotated classes.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@SupportedAnnotationTypes({ "com.querydsl.core.annotations.*", "org.springframework.data.couchbase.core.mapping.*" })
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_6)
|
||||
public class CouchbaseAnnotationProcessor extends AbstractQuerydslProcessor {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.apt.AbstractQuerydslProcessor#createConfiguration(javax.annotation.processing.RoundEnvironment)
|
||||
*/
|
||||
@Override
|
||||
protected Configuration createConfiguration(@Nullable RoundEnvironment roundEnv) {
|
||||
|
||||
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Running " + getClass().getSimpleName());
|
||||
|
||||
DefaultConfiguration configuration = new DefaultConfiguration(processingEnv, roundEnv, Collections.emptySet(),
|
||||
QueryEntities.class, Document.class, QuerySupertype.class, QueryEmbeddable.class, QueryEmbedded.class,
|
||||
QueryTransient.class);
|
||||
configuration.setUnknownAsEmbedded(true);
|
||||
|
||||
return configuration;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2022 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.
|
||||
@@ -16,10 +16,13 @@
|
||||
|
||||
package org.springframework.data.couchbase.repository.support;
|
||||
|
||||
import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
@@ -30,9 +33,11 @@ import org.springframework.data.couchbase.repository.query.PartTreeCouchbaseQuer
|
||||
import org.springframework.data.couchbase.repository.query.StringBasedCouchbaseQuery;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionFactory;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.repository.core.NamedQueries;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
import org.springframework.data.repository.core.support.RepositoryComposition;
|
||||
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
|
||||
@@ -53,6 +58,8 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
|
||||
private static final SpelExpressionParser SPEL_PARSER = new SpelExpressionParser();
|
||||
|
||||
private final CouchbaseOperations operations;
|
||||
|
||||
/**
|
||||
* Holds the reference to the template.
|
||||
*/
|
||||
@@ -76,7 +83,7 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
this.couchbaseOperationsMapping = couchbaseOperationsMapping;
|
||||
this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor();
|
||||
mappingContext = this.couchbaseOperationsMapping.getMappingContext();
|
||||
|
||||
operations = this.couchbaseOperationsMapping.getDefault();
|
||||
addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor);
|
||||
}
|
||||
|
||||
@@ -168,4 +175,44 @@ public class CouchbaseRepositoryFactory extends RepositoryFactorySupport {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getRepositoryFragments(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
*/
|
||||
@Override
|
||||
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
|
||||
return getRepositoryFragments(metadata, operations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link RepositoryComposition.RepositoryFragments} based on {@link RepositoryMetadata} to add
|
||||
* Couchbase-specific extensions. Typically adds a {@link QuerydslCouchbasePredicateExecutor} if the repository
|
||||
* interface uses Querydsl.
|
||||
* <p>
|
||||
* Can be overridden by subclasses to customize {@link RepositoryComposition.RepositoryFragments}.
|
||||
*
|
||||
* @param metadata repository metadata.
|
||||
* @param operations the Couchbase operations manager.
|
||||
* @return
|
||||
*/
|
||||
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata,
|
||||
CouchbaseOperations operations) {
|
||||
|
||||
boolean isQueryDslRepository = QUERY_DSL_PRESENT
|
||||
&& QuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
|
||||
|
||||
if (isQueryDslRepository) {
|
||||
|
||||
if (metadata.isReactiveRepository()) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"Cannot combine Querydsl and reactive repository support in a single interface");
|
||||
}
|
||||
|
||||
return RepositoryComposition.RepositoryFragments
|
||||
.just(new QuerydslCouchbasePredicateExecutor<>(getEntityInformation(metadata.getDomainType()), operations));
|
||||
}
|
||||
|
||||
return RepositoryComposition.RepositoryFragments.empty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
/**
|
||||
* DB references
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class DBRef {
|
||||
Object id;
|
||||
|
||||
public Object getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
|
||||
/**
|
||||
* Querydsl fluent api
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
abstract class FetchableFluentQuerySupport<P, T> implements FluentQuery.FetchableFluentQuery<T> {
|
||||
|
||||
private final P predicate;
|
||||
private final Sort sort;
|
||||
private final Class<T> resultType;
|
||||
private final List<String> fieldsToInclude;
|
||||
|
||||
FetchableFluentQuerySupport(P predicate, Sort sort, Class<T> resultType, List<String> fieldsToInclude) {
|
||||
this.predicate = predicate;
|
||||
this.sort = sort;
|
||||
this.resultType = resultType;
|
||||
this.fieldsToInclude = fieldsToInclude;
|
||||
}
|
||||
|
||||
protected abstract <R> FetchableFluentQuerySupport<P, R> create(P predicate, Sort sort, Class<R> resultType,
|
||||
List<String> fieldsToInclude);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
|
||||
*/
|
||||
public abstract T oneValue();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#firstValue()
|
||||
*/
|
||||
public abstract T firstValue();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
|
||||
*/
|
||||
public abstract List<T> all();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
public abstract Page<T> page(Pageable pageable);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
|
||||
*/
|
||||
public abstract Stream<T> stream();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
|
||||
*/
|
||||
public abstract long count();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
|
||||
*/
|
||||
public abstract boolean exists();
|
||||
|
||||
P getPredicate() {
|
||||
return predicate;
|
||||
}
|
||||
|
||||
Sort getSort() {
|
||||
return sort;
|
||||
}
|
||||
|
||||
Class<T> getResultType() {
|
||||
return resultType;
|
||||
}
|
||||
|
||||
List<String> getFieldsToInclude() {
|
||||
return fieldsToInclude;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
/*
|
||||
* Copyright 2017-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.dao.IncorrectResultSizeDataAccessException;
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.repository.query.CouchbaseEntityInformation;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.querydsl.EntityPathResolver;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.querydsl.SimpleEntityPathResolver;
|
||||
import org.springframework.data.repository.query.FluentQuery;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.querydsl.core.NonUniqueResultException;
|
||||
import com.querydsl.core.types.EntityPath;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
/**
|
||||
* Couchbase-specific {@link QuerydslPredicateExecutor} that allows execution {@link Predicate}s in various forms.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
* @since 5.0
|
||||
*/
|
||||
public class QuerydslCouchbasePredicateExecutor<T> extends QuerydslPredicateExecutorSupport<T>
|
||||
implements QuerydslPredicateExecutor<T> {
|
||||
|
||||
private final CouchbaseOperations couchbaseOperations;
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuerydslCouchbasePredicateExecutor} for the given {@link CouchbaseEntityInformation} and
|
||||
* {@link CouchbaseOperations}. Uses the {@link SimpleEntityPathResolver} to create an {@link EntityPath} for the
|
||||
* given domain class.
|
||||
*
|
||||
* @param entityInformation must not be {@literal null}.
|
||||
* @param couchbaseOperations must not be {@literal null}.
|
||||
*/
|
||||
public QuerydslCouchbasePredicateExecutor(CouchbaseEntityInformation<T, ?> entityInformation,
|
||||
CouchbaseOperations couchbaseOperations) {
|
||||
this(entityInformation, couchbaseOperations, SimpleEntityPathResolver.INSTANCE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link QuerydslCouchbasePredicateExecutor} for the given {@link CouchbaseEntityInformation},
|
||||
* {@linkCouchbaseOperations} and {@link EntityPathResolver}.
|
||||
*
|
||||
* @param entityInformation must not be {@literal null}.
|
||||
* @param couchbaseOperations must not be {@literal null}.
|
||||
* @param resolver must not be {@literal null}.
|
||||
*/
|
||||
public QuerydslCouchbasePredicateExecutor(CouchbaseEntityInformation<T, ?> entityInformation,
|
||||
CouchbaseOperations couchbaseOperations, EntityPathResolver resolver) {
|
||||
super(couchbaseOperations.getConverter(), pathBuilderFor(resolver.createPath(entityInformation.getJavaType())),
|
||||
entityInformation);
|
||||
this.couchbaseOperations = couchbaseOperations;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findById(com.querydsl.core.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public Optional<T> findOne(Predicate predicate) {
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
try {
|
||||
return Optional.ofNullable(createQueryFor(predicate).fetchOne());
|
||||
} catch (NonUniqueResultException ex) {
|
||||
throw new IncorrectResultSizeDataAccessException(ex.getMessage(), 1, ex);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public List<T> findAll(Predicate predicate) {
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
return createQueryFor(predicate).fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, com.querydsl.core.types.OrderSpecifier<?>[])
|
||||
*/
|
||||
@Override
|
||||
public List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
Assert.notNull(orders, "Order specifiers must not be null!");
|
||||
return createQueryFor(predicate).orderBy(orders).fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Sort)
|
||||
*/
|
||||
@Override
|
||||
public List<T> findAll(Predicate predicate, Sort sort) {
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
Assert.notNull(sort, "Sort must not be null!");
|
||||
return applySorting(createQueryFor(predicate), sort).fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.OrderSpecifier[])
|
||||
*/
|
||||
@Override
|
||||
public Iterable<T> findAll(OrderSpecifier<?>... orders) {
|
||||
Assert.notNull(orders, "Order specifiers must not be null!");
|
||||
return createQuery().orderBy(orders).fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findAll(com.querydsl.core.types.Predicate, org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public Page<T> findAll(Predicate predicate, Pageable pageable) {
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
SpringDataCouchbaseQuery<T> query = createQueryFor(predicate);
|
||||
return PageableExecutionUtils.getPage(applyPagination(query, pageable).fetch(), pageable, query::fetchCount);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#count(com.querydsl.core.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public long count(Predicate predicate) {
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
return createQueryFor(predicate).fetchCount();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#exists(com.querydsl.core.types.Predicate)
|
||||
*/
|
||||
@Override
|
||||
public boolean exists(Predicate predicate) {
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
return createQueryFor(predicate).fetchCount() > 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.querydsl.QuerydslPredicateExecutor#findBy(com.querydsl.core.types.Predicate, java.util.function.Function)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <S extends T, R> R findBy(Predicate predicate,
|
||||
Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
Assert.notNull(queryFunction, "Query function must not be null!");
|
||||
return queryFunction.apply(new FluentQuerydsl<>(predicate, (Class<S>) typeInformation().getJavaType()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SpringDataCouchbaseQuery} for the given {@link Predicate}.
|
||||
*
|
||||
* @param predicate
|
||||
* @return
|
||||
*/
|
||||
private SpringDataCouchbaseQuery<T> createQueryFor(Predicate predicate) {
|
||||
return createQuery().where(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SpringDataCouchbaseQuery}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private SpringDataCouchbaseQuery<T> createQuery() {
|
||||
return new SpringDataCouchbaseQuery<>(couchbaseOperations, typeInformation().getJavaType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given {@link Pageable} to the given {@link SpringDataCouchbaseQuery}.
|
||||
*
|
||||
* @param query
|
||||
* @param pageable
|
||||
* @return
|
||||
*/
|
||||
private SpringDataCouchbaseQuery<T> applyPagination(SpringDataCouchbaseQuery<T> query, Pageable pageable) {
|
||||
if (pageable.isUnpaged()) {
|
||||
return query;
|
||||
}
|
||||
query = query.offset(pageable.getOffset()).limit(pageable.getPageSize());
|
||||
return applySorting(query, pageable.getSort());
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given {@link Sort} to the given {@link SpringDataCouchbaseQuery}.
|
||||
*
|
||||
* @param query
|
||||
* @param sort
|
||||
* @return
|
||||
*/
|
||||
private SpringDataCouchbaseQuery<T> applySorting(SpringDataCouchbaseQuery<T> query, Sort sort) {
|
||||
toOrderSpecifiers(sort).forEach(query::orderBy);
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery} using Querydsl
|
||||
* {@link Predicate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 3.3
|
||||
*/
|
||||
class FluentQuerydsl<T> extends FetchableFluentQuerySupport<Predicate, T> {
|
||||
|
||||
FluentQuerydsl(Predicate predicate, Class<T> resultType) {
|
||||
this(predicate, Sort.unsorted(), resultType, Collections.emptyList());
|
||||
}
|
||||
|
||||
FluentQuerydsl(Predicate predicate, Sort sort, Class<T> resultType, List<String> fieldsToInclude) {
|
||||
super(predicate, sort, resultType, fieldsToInclude);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected <R> FluentQuerydsl<R> create(Predicate predicate, Sort sort, Class<R> resultType,
|
||||
List<String> fieldsToInclude) {
|
||||
return new FluentQuerydsl<>(predicate, sort, resultType, fieldsToInclude);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchableFluentQuery sortBy(Sort sort) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchableFluentQuery project(Collection properties) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FetchableFluentQuery as(Class resultType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#oneValue()
|
||||
*/
|
||||
@Override
|
||||
public T oneValue() {
|
||||
return createQuery().fetchOne();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#firstValue()
|
||||
*/
|
||||
@Override
|
||||
public T firstValue() {
|
||||
return createQuery().fetchFirst();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#all()
|
||||
*/
|
||||
@Override
|
||||
public List<T> all() {
|
||||
return createQuery().fetch();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#page(org.springframework.data.domain.Pageable)
|
||||
*/
|
||||
@Override
|
||||
public Page<T> page(Pageable pageable) {
|
||||
|
||||
Assert.notNull(pageable, "Pageable must not be null!");
|
||||
|
||||
return createQuery().fetchPage(pageable);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#stream()
|
||||
*/
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return createQuery().stream();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#count()
|
||||
*/
|
||||
@Override
|
||||
public long count() {
|
||||
return createQuery().fetchCount();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery#exists()
|
||||
*/
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return count() > 0;
|
||||
}
|
||||
|
||||
private SpringDataCouchbaseQuery<T> createQuery() {
|
||||
return new SpringDataCouchbaseQuery<>(couchbaseOperations, typeInformation().getJavaType(), getResultType(),
|
||||
"collection", this::customize).where(getPredicate());
|
||||
}
|
||||
|
||||
private void customize(BasicQuery query) {
|
||||
|
||||
List<String> fieldsToInclude = getFieldsToInclude();
|
||||
if (!fieldsToInclude.isEmpty()) {
|
||||
Map<String, String> fields = new HashMap();
|
||||
fieldsToInclude.forEach(field -> fields.put(field, field));
|
||||
query.setProjectionFields(fields);
|
||||
}
|
||||
|
||||
if (getSort().isSorted()) {
|
||||
query.with(getSort());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
import com.querydsl.core.types.EntityPath;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.core.types.dsl.PathBuilder;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.querydsl.QSort;
|
||||
import org.springframework.data.repository.core.EntityInformation;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class QuerydslPredicateExecutorSupport<T> {
|
||||
|
||||
private final SpringDataCouchbaseSerializer serializer;
|
||||
private final PathBuilder<T> builder;
|
||||
private final EntityInformation<T, ?> entityInformation;
|
||||
|
||||
QuerydslPredicateExecutorSupport(CouchbaseConverter converter, PathBuilder<T> builder,
|
||||
EntityInformation<T, ?> entityInformation) {
|
||||
|
||||
this.serializer = new SpringDataCouchbaseSerializer(converter);
|
||||
this.builder = builder;
|
||||
this.entityInformation = entityInformation;
|
||||
}
|
||||
|
||||
protected static <E> PathBuilder<E> pathBuilderFor(EntityPath<E> path) {
|
||||
return new PathBuilder<>(path.getType(), path.getMetadata());
|
||||
}
|
||||
|
||||
protected EntityInformation<T, ?> typeInformation() {
|
||||
return entityInformation;
|
||||
}
|
||||
|
||||
protected SpringDataCouchbaseSerializer cuchbaseSerializer() {
|
||||
return serializer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a plain {@link Sort.Order} into a Querydsl specific {@link OrderSpecifier}.
|
||||
*
|
||||
* @param order
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
protected OrderSpecifier<?> toOrder(Sort.Order order) {
|
||||
|
||||
Expression<Object> property = builder.get(order.getProperty());
|
||||
|
||||
return new OrderSpecifier(
|
||||
order.isAscending() ? com.querydsl.core.types.Order.ASC : com.querydsl.core.types.Order.DESC, property);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given {@link Sort} to {@link OrderSpecifier}.
|
||||
*
|
||||
* @param sort
|
||||
* @return
|
||||
*/
|
||||
protected List<OrderSpecifier<?>> toOrderSpecifiers(Sort sort) {
|
||||
|
||||
if (sort instanceof QSort) {
|
||||
return ((QSort) sort).getOrderSpecifiers();
|
||||
}
|
||||
|
||||
return sort.stream().map(this::toOrder).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
import static com.couchbase.client.core.io.CollectionIdentifier.DEFAULT_COLLECTION;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.data.couchbase.core.CouchbaseOperations;
|
||||
import org.springframework.data.couchbase.core.ExecutableFindByQueryOperation;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.support.PageableExecutionUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import com.mysema.commons.lang.CloseableIterator;
|
||||
import com.mysema.commons.lang.EmptyCloseableIterator;
|
||||
import com.querydsl.core.Fetchable;
|
||||
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.OrderSpecifier;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class SpringDataCouchbaseQuery<T> extends SpringDataCouchbaseQuerySupport<SpringDataCouchbaseQuery<T>>
|
||||
implements Fetchable<T> {
|
||||
|
||||
private final CouchbaseOperations couchbaseOperations;
|
||||
private final Consumer<BasicQuery> queryCustomizer;
|
||||
private final ExecutableFindByQueryOperation.ExecutableFindByQuery<T> find;// ExecutableFindOperation.FindWithQuery<T>
|
||||
// find;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SpringDataCouchbaseQuery}.
|
||||
*
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public SpringDataCouchbaseQuery(CouchbaseOperations operations, Class<? extends T> type) {
|
||||
this(operations, type, DEFAULT_COLLECTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SpringDataCouchbaseQuery} to query the given collection.
|
||||
*
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} or empty.
|
||||
*/
|
||||
public SpringDataCouchbaseQuery(CouchbaseOperations operations, Class<? extends T> type, String collectionName) {
|
||||
this(operations, type, type, collectionName, it -> {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SpringDataCouchbaseQuery}.
|
||||
*
|
||||
* @param operations must not be {@literal null}.
|
||||
* @param domainType must not be {@literal null}.
|
||||
* @param resultType must not be {@literal null}.
|
||||
* @param collectionName must not be {@literal null} or empty.
|
||||
* @since 3.3
|
||||
*/
|
||||
SpringDataCouchbaseQuery(CouchbaseOperations operations, Class<?> domainType, Class<? extends T> resultType,
|
||||
String collectionName, Consumer<BasicQuery> queryCustomizer) {
|
||||
super(new SpringDataCouchbaseSerializer(operations.getConverter()));
|
||||
|
||||
Class<T> resultType1 = (Class<T>) resultType;
|
||||
this.couchbaseOperations = operations;
|
||||
this.queryCustomizer = queryCustomizer;
|
||||
this.find = (ExecutableFindByQueryOperation.ExecutableFindByQuery<T>) couchbaseOperations.findByQuery(domainType)
|
||||
.as(resultType1).inCollection(collectionName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.Fetchable#iterable()
|
||||
*/
|
||||
@Override
|
||||
public CloseableIterator<T> iterate() {
|
||||
|
||||
try {
|
||||
Stream<T> stream = stream();
|
||||
Iterator<T> iterator = stream.iterator();
|
||||
|
||||
return new CloseableIterator<T>() {
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return iterator.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("Cannot remove from iterator while streaming data.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
stream.close();
|
||||
}
|
||||
};
|
||||
} catch (RuntimeException e) {
|
||||
return handleException(e, new EmptyCloseableIterator<>());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.Fetchable#iterable()
|
||||
*/
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
|
||||
try {
|
||||
return find.matching(createQuery()).stream();
|
||||
} catch (RuntimeException e) {
|
||||
return handleException(e, Stream.empty());
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.Fetchable#fetch()
|
||||
*/
|
||||
@Override
|
||||
public List<T> fetch() {
|
||||
try {
|
||||
return find.matching(createQuery()).all();
|
||||
} catch (RuntimeException e) {
|
||||
return handleException(e, Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a {@link Page}.
|
||||
*
|
||||
* @param pageable
|
||||
* @return
|
||||
*/
|
||||
public Page<T> fetchPage(Pageable pageable) {
|
||||
|
||||
try {
|
||||
|
||||
List<T> content = find.matching(createQuery().with(pageable)).all();
|
||||
|
||||
return PageableExecutionUtils.getPage(content, pageable, this::fetchCount);
|
||||
} catch (RuntimeException e) {
|
||||
return handleException(e, new PageImpl<>(Collections.emptyList(), pageable, 0));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.Fetchable#fetchFirst()
|
||||
*/
|
||||
@Override
|
||||
public T fetchFirst() {
|
||||
try {
|
||||
return find.matching(createQuery()).firstValue();
|
||||
} catch (RuntimeException e) {
|
||||
return handleException(e, null);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.Fetchable#fetchOne()
|
||||
*/
|
||||
@Override
|
||||
public T fetchOne() {
|
||||
try {
|
||||
return find.matching(createQuery()).oneValue();
|
||||
} catch (RuntimeException e) {
|
||||
return handleException(e, null);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.Fetchable#fetchResults()
|
||||
*/
|
||||
@Override
|
||||
public QueryResults<T> fetchResults() {
|
||||
|
||||
long total = fetchCount();
|
||||
return total > 0L ? new QueryResults<>(fetch(), getQueryMixin().getMetadata().getModifiers(), total)
|
||||
: QueryResults.emptyResults();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.core.Fetchable#fetchCount()
|
||||
*/
|
||||
@Override
|
||||
public long fetchCount() {
|
||||
try {
|
||||
return find.matching(createQuery().skip(-1).limit(-1)).count();
|
||||
} catch (RuntimeException e) {
|
||||
return handleException(e, 0L);
|
||||
}
|
||||
}
|
||||
|
||||
protected org.springframework.data.couchbase.core.query.Query createQuery() {
|
||||
|
||||
QueryMetadata metadata = getQueryMixin().getMetadata();
|
||||
|
||||
return createQuery(createFilter(metadata), metadata.getProjection(), metadata.getModifiers(),
|
||||
metadata.getOrderBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Predicate createFilter(QueryMetadata metadata) {
|
||||
return metadata.getWhere();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Object> getIds(Class<?> var1, Predicate var2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected org.springframework.data.couchbase.core.query.Query createQuery(@Nullable Predicate filter,
|
||||
@Nullable Expression<?> projection, QueryModifiers modifiers, List<OrderSpecifier<?>> orderBy) {
|
||||
|
||||
Map<String, String> fields = createProjection(projection);
|
||||
BasicQuery basicQuery = new BasicQuery(createCriteria(filter), fields);
|
||||
|
||||
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.setSort(createSort(orderBy));
|
||||
}
|
||||
queryCustomizer.accept(basicQuery);
|
||||
return basicQuery;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// protected CouchbaseDocument createQuery(Predicate filter);
|
||||
|
||||
// @Override
|
||||
// protected Map<String,String> createProjection(Expression<?> projection);
|
||||
|
||||
// @Override
|
||||
// protected CouchbaseDocument createSort(List<OrderSpecifier<?>> orderBy);
|
||||
|
||||
/*
|
||||
* Fetch the list of ids matching a given condition.
|
||||
*
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @param condition must not be {@literal null}.
|
||||
* @return empty {@link List} if none found.
|
||||
|
||||
protected List<Object> getIds(Class<?> targetType, Predicate condition) {
|
||||
Query query = createQuery(condition, null, QueryModifiers.EMPTY, Collections.emptyList());
|
||||
return couchbaseOperations.findByQuery(targetType).matching(query).all(); // findDistinct(query, "_id", targetType, Object.class);
|
||||
}
|
||||
*/
|
||||
private static <T> T handleException(RuntimeException e, T defaultValue) {
|
||||
|
||||
if (e.getClass().getName().endsWith("$NoResults")) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright 2012-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbaseDocument;
|
||||
import org.springframework.data.domain.Sort;
|
||||
|
||||
import com.querydsl.core.support.QueryMixin;
|
||||
import com.querydsl.core.types.OrderSpecifier;
|
||||
import com.querydsl.couchbase.document.AbstractCouchbaseQueryDSL;
|
||||
import com.querydsl.couchbase.document.CouchbaseDocumentSerializer;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
abstract class SpringDataCouchbaseQuerySupport<Q extends SpringDataCouchbaseQuerySupport<Q>>
|
||||
extends AbstractCouchbaseQueryDSL<Q> {
|
||||
|
||||
private final QueryMixin<Q> superQueryMixin;
|
||||
|
||||
// TODO private static final JsonWriterSettings JSON_WRITER_SETTINGS =
|
||||
// JsonWriterSettings.builder().outputMode(JsonMode.SHELL)
|
||||
// .build();
|
||||
|
||||
private final CouchbaseDocumentSerializer serializer;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
SpringDataCouchbaseQuerySupport(CouchbaseDocumentSerializer serializer) {
|
||||
super(serializer);
|
||||
this.serializer = serializer;
|
||||
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(this);
|
||||
this.superQueryMixin = (QueryMixin<Q>) fieldAccessor.getPropertyValue("queryMixin");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the representation of the query. <br />
|
||||
* The following query
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* where(p.lastname.eq("Matthews")).orderBy(p.firstname.asc()).offset(1).limit(5);
|
||||
* </pre>
|
||||
*
|
||||
* results in
|
||||
*
|
||||
* <pre class="code">
|
||||
*
|
||||
* find({"lastname" : "Matthews"}).sort({"firstname" : 1}).skip(1).limit(5)
|
||||
* </pre>
|
||||
*
|
||||
* Note that encoding to {@link String} may fail when using data types that cannot be encoded or DBRef's without an
|
||||
* identifier.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
Map<String, String> projection = createProjection(getQueryMixin().getMetadata().getProjection());
|
||||
Sort sort = createSort(getQueryMixin().getMetadata().getOrderBy());
|
||||
// TODO DocumentCodec codec = new DocumentCodec(ClientSettings.getDefaultCodecRegistry());
|
||||
|
||||
// TODO
|
||||
// StringBuilder sb = new StringBuilder("find(" + asDocument().toJson(JSON_WRITER_SETTINGS, codec));
|
||||
StringBuilder sb = new StringBuilder("find(" + asDocument().toString());
|
||||
// if (projection != null && projection.isEmpty()) {
|
||||
// sb.append(", ").append(projection.toJson(JSON_WRITER_SETTINGS, codec));
|
||||
// }
|
||||
sb.append(")");
|
||||
// TODO
|
||||
// if (!sort.isEmpty()) {
|
||||
// sb.append(".sort(").append(sort.toJson(JSON_WRITER_SETTINGS, codec)).append(")");
|
||||
// }
|
||||
if (getQueryMixin().getMetadata().getModifiers().getOffset() != null) {
|
||||
sb.append(".skip(").append(getQueryMixin().getMetadata().getModifiers().getOffset()).append(")");
|
||||
}
|
||||
if (getQueryMixin().getMetadata().getModifiers().getLimit() != null) {
|
||||
sb.append(".limit(").append(getQueryMixin().getMetadata().getModifiers().getLimit()).append(")");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the where definition as a Document instance
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public CouchbaseDocument asDocument() {
|
||||
return createQuery(getQueryMixin().getMetadata().getWhere());
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the json query representation.
|
||||
*
|
||||
* @return never {@literal null}. public String toJson() { return toJson(JSON_WRITER_SETTINGS); }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Obtain the json query representation applying given {@link JsonWriterSettings settings}.
|
||||
*
|
||||
* @param settings must not be {@literal null}.
|
||||
* @return never {@literal null}. public String toJson(JsonWriterSettings settings) { return
|
||||
* asDocument().toJson(settings); }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Compute the sort {@link CouchbaseDocument} from the given list of {@link OrderSpecifier order specifiers}.
|
||||
*
|
||||
* @param orderSpecifiers can be {@literal null}.
|
||||
* @return an empty {@link CouchbaseDocument} if predicate is {@literal null}. see
|
||||
* CouchbaseDocumentSerializer#toSort(List)
|
||||
*/
|
||||
protected Sort createSort(List<OrderSpecifier<?>> orderSpecifiers) {
|
||||
return null; // TODO serializer.toSort(orderSpecifiers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* Copyright 2011-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.support;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.querydsl.couchbase.document.CouchbaseDocumentSerializer;
|
||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
import com.querydsl.core.types.Constant;
|
||||
import com.querydsl.core.types.Expression;
|
||||
import com.querydsl.core.types.Operation;
|
||||
import com.querydsl.core.types.Path;
|
||||
import com.querydsl.core.types.PathMetadata;
|
||||
import com.querydsl.core.types.PathType;
|
||||
|
||||
/**
|
||||
* Custom {@link CouchbaseDocumentSerializer} to take mapping information into account when building keys for
|
||||
* constraints.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class SpringDataCouchbaseSerializer extends CouchbaseDocumentSerializer {
|
||||
|
||||
private static final String ID_KEY = "_id";
|
||||
private static final Set<PathType> PATH_TYPES;
|
||||
|
||||
static {
|
||||
|
||||
Set<PathType> pathTypes = new HashSet<>();
|
||||
pathTypes.add(PathType.VARIABLE);
|
||||
pathTypes.add(PathType.PROPERTY);
|
||||
|
||||
PATH_TYPES = Collections.unmodifiableSet(pathTypes);
|
||||
}
|
||||
|
||||
private final CouchbaseConverter converter;
|
||||
private final MappingContext<? extends CouchbasePersistentEntity<?>, CouchbasePersistentProperty> mappingContext;
|
||||
// private final QueryMapper mapper;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SpringDataCouchbaseSerializer} for the given {@link CouchbaseConverter}.
|
||||
*
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
public SpringDataCouchbaseSerializer(CouchbaseConverter converter) {
|
||||
|
||||
Assert.notNull(converter, "CouchbaseConverter must not be null!");
|
||||
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
this.converter = converter;
|
||||
// this.mapper = new QueryMapper(converter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.couchbase.CouchbaseSerializer#visit(com.querydsl.core.types.Constant, java.lang.Void)
|
||||
*/
|
||||
@Override
|
||||
public Object visit(Constant<?> expr, Void context) {
|
||||
|
||||
if (!ClassUtils.isAssignable(Enum.class, expr.getType())) {
|
||||
return super.visit(expr, context);
|
||||
}
|
||||
|
||||
return converter.convertForWriteIfNeeded(expr.getConstant());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.couchbase.CouchbaseSerializer#getKeyForPath(com.querydsl.core.types.Path, com.querydsl.core.types.PathMetadata)
|
||||
*/
|
||||
@Override
|
||||
protected String getKeyForPath(Path<?> expr, PathMetadata metadata) {
|
||||
// TODO - substitutions for meta().id, meta().expiry, meta().cas
|
||||
if (!metadata.getPathType().equals(PathType.PROPERTY)) {
|
||||
return super.getKeyForPath(expr, metadata);
|
||||
}
|
||||
|
||||
Path<?> parent = metadata.getParent();
|
||||
CouchbasePersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(parent.getType());
|
||||
CouchbasePersistentProperty property = entity.getPersistentProperty(metadata.getName());
|
||||
|
||||
return property == null ? super.getKeyForPath(expr, metadata) : property.getFieldName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.couchbase.repository.support.CouchbaseSerializer#asDocument(java.lang.String, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected QueryCriteriaDefinition asDocument(@Nullable String key, @Nullable Object value) {
|
||||
|
||||
value = value instanceof Optional ? ((Optional) value).orElse(null) : value;
|
||||
|
||||
return super.asDocument(key, value instanceof Pattern ? value : converter.convertForWriteIfNeeded(value));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.couchbase.CouchbaseSerializer#isReference(com.querydsl.core.types.Path)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isReference(@Nullable Path<?> path) {
|
||||
|
||||
CouchbasePersistentProperty property = getPropertyForPotentialDbRef(path);
|
||||
return property == null ? false : property.isAssociation();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.couchbase.CouchbaseSerializer#asReference(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
protected DBRef asReference(@Nullable Object constant) {
|
||||
return asReference(constant, null);
|
||||
}
|
||||
|
||||
protected DBRef asReference(Object constant, Path<?> path) {
|
||||
return null; // converter.toDBRef(constant, getPropertyForPotentialDbRef(path));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.couchbase.CouchbaseSerializer#asDBKey(com.querydsl.core.types.Operation, int)
|
||||
*/
|
||||
@Override
|
||||
protected String asDBKey(@Nullable Operation<?> expr, int index) {
|
||||
|
||||
Expression<?> arg = expr.getArg(index);
|
||||
String key = super.asDBKey(expr, index);
|
||||
|
||||
if (!(arg instanceof Path)) {
|
||||
return key;
|
||||
}
|
||||
|
||||
Path<?> path = (Path<?>) arg;
|
||||
|
||||
if (!isReference(path)) {
|
||||
return key;
|
||||
}
|
||||
|
||||
CouchbasePersistentProperty property = getPropertyFor(path);
|
||||
|
||||
return property.isIdProperty() ? key.replaceAll("." + ID_KEY + "$", "") : key;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.querydsl.couchbase.CouchbaseSerializer#convert(com.querydsl.core.types.Path, com.querydsl.core.types.Constant)
|
||||
*/
|
||||
protected Object convert(@Nullable Path<?> path, @Nullable Constant<?> constant) {
|
||||
|
||||
if (!isReference(path)) {
|
||||
return null;
|
||||
// return super.convert(path, constant);
|
||||
}
|
||||
|
||||
CouchbasePersistentProperty property = getPropertyFor(path);
|
||||
|
||||
return property.isIdProperty() ? asReference(constant.getConstant(), path.getMetadata().getParent())
|
||||
: asReference(constant.getConstant(), path);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CouchbasePersistentProperty getPropertyFor(Path<?> path) {
|
||||
|
||||
Path<?> parent = path.getMetadata().getParent();
|
||||
|
||||
if (parent == null || !PATH_TYPES.contains(path.getMetadata().getPathType())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CouchbasePersistentEntity<?> entity = mappingContext.getPersistentEntity(parent.getType());
|
||||
return entity != null ? entity.getPersistentProperty(path.getMetadata().getName()) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the given {@literal path} for referencing the {@literal id} property of a {@link DBRef} referenced object.
|
||||
* If so it returns the referenced {@link CouchbasePersistentProperty} of the {@link DBRef} instead of the
|
||||
* {@literal id} property.
|
||||
*
|
||||
* @param path
|
||||
* @return
|
||||
*/
|
||||
private CouchbasePersistentProperty getPropertyForPotentialDbRef(Path<?> path) {
|
||||
|
||||
if (path == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CouchbasePersistentProperty property = getPropertyFor(path);
|
||||
PathMetadata metadata = path.getMetadata();
|
||||
|
||||
if (property != null && property.isIdProperty() && metadata != null && metadata.getParent() != null) {
|
||||
return getPropertyFor(metadata.getParent());
|
||||
}
|
||||
|
||||
return property;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2020 the original author or authors.
|
||||
* Copyright 2013-2022 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.
|
||||
@@ -190,78 +190,78 @@ public class BasicCouchbasePersistentEntityTests {
|
||||
@Configuration
|
||||
static class Config {}
|
||||
|
||||
public static class SimpleDocument {}
|
||||
class SimpleDocument {}
|
||||
|
||||
@Document(expiry = 10)
|
||||
public static class SimpleDocumentWithExpiry {}
|
||||
class SimpleDocumentWithExpiry {}
|
||||
|
||||
@Document(expiry = 10, touchOnRead = true)
|
||||
public static class SimpleDocumentWithTouchOnRead {}
|
||||
class SimpleDocumentWithTouchOnRead {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test default expiry.
|
||||
*/
|
||||
@Document
|
||||
private class DefaultExpiry {}
|
||||
class DefaultExpiry {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test default expiry unit.
|
||||
*/
|
||||
@Document(expiry = 78)
|
||||
private class DefaultExpiryUnit {}
|
||||
class DefaultExpiryUnit {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test limit expiry.
|
||||
*/
|
||||
@Document(expiry = 30, expiryUnit = TimeUnit.DAYS)
|
||||
private class LimitDaysExpiry {}
|
||||
class LimitDaysExpiry {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test larger than 30 days expiry.
|
||||
*/
|
||||
@Document(expiry = 31, expiryUnit = TimeUnit.DAYS)
|
||||
public class OverLimitDaysExpiry {}
|
||||
class OverLimitDaysExpiry {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test larger than 30 days expiry defined as an expression
|
||||
*/
|
||||
@Document(expiryExpression = "${document.expiry.larger.than.30days:31}", expiryUnit = TimeUnit.DAYS)
|
||||
public class OverLimitDaysExpiryExpression {}
|
||||
class OverLimitDaysExpiryExpression {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test larger than 30 days expiry, when expressed in default time unit (SECONDS).
|
||||
*/
|
||||
@Document(expiry = 31 * 24 * 60 * 60)
|
||||
public class OverLimitSecondsExpiry {}
|
||||
class OverLimitSecondsExpiry {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test constant expiry expression
|
||||
*/
|
||||
@Document(expiryExpression = "10")
|
||||
private class ConstantExpiryExpression {}
|
||||
class ConstantExpiryExpression {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test valid expiry expression by resolving simple property from environment
|
||||
*/
|
||||
@Document(expiryExpression = "${valid.document.expiry}")
|
||||
private class ExpiryWithValidExpression {}
|
||||
class ExpiryWithValidExpression {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test invalid expiry expression
|
||||
*/
|
||||
@Document(expiryExpression = "${invalid.document.expiry}")
|
||||
private class ExpiryWithInvalidExpression {}
|
||||
class ExpiryWithInvalidExpression {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test expiry expression logic failure to resolve property placeholder
|
||||
*/
|
||||
@Document(expiryExpression = "${missing.expiry}")
|
||||
private class ExpiryWithMissingProperty {}
|
||||
class ExpiryWithMissingProperty {}
|
||||
|
||||
/**
|
||||
* Simple POJO to test that expiry and expiry expression cannot be used simultaneously
|
||||
*/
|
||||
@Document(expiry = 10, expiryExpression = "10")
|
||||
private class ExpiryAndExpression {}
|
||||
class ExpiryAndExpression {}
|
||||
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ import com.couchbase.client.java.json.JsonArray;
|
||||
|
||||
/**
|
||||
* @author Mauro Monti
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
class QueryCriteriaTests {
|
||||
|
||||
@@ -69,7 +70,7 @@ class QueryCriteriaTests {
|
||||
void testNestedAndCriteria() {
|
||||
QueryCriteria c = where(i("name")).is("Bubba").and(where(i("age")).gt(12).or(i("country")).is("Austria"));
|
||||
JsonArray parameters = JsonArray.create();
|
||||
assertEquals("`name` = $1 and (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
|
||||
assertEquals(" (`name` = $1) and (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
|
||||
assertEquals("[\"Bubba\",12,\"Austria\"]", parameters.toString());
|
||||
}
|
||||
|
||||
@@ -77,19 +78,33 @@ class QueryCriteriaTests {
|
||||
void testNestedOrCriteria() {
|
||||
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12).or(i("country")).is("Austria"));
|
||||
JsonArray parameters = JsonArray.create();
|
||||
assertEquals("`name` = $1 or (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
|
||||
assertEquals(" (`name` = $1) or (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
|
||||
assertEquals("[\"Bubba\",12,\"Austria\"]", parameters.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedNotIn() {
|
||||
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12).or(i("country")).is("Austria"))
|
||||
.and(where(i("state")).notIn((Object) new String[] { "Alabama", "Florida" }));
|
||||
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12).and(i("country")).is("Austria"))
|
||||
.and(where(i("state")).notIn(new String[] { "Alabama", "Florida" }));
|
||||
JsonArray parameters = JsonArray.create();
|
||||
assertEquals("`name` = $1 or (`age` > $2 or `country` = $3) and (not( (`state` in $4) ))",
|
||||
assertEquals(" ( (`name` = $1) or (`age` > $2 and `country` = $3)) and (not( (`state` in $4) ))",
|
||||
c.export(new int[1], parameters, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedNotIn2() {
|
||||
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12)).and(where(i("state")).eq("1"));
|
||||
JsonArray parameters = JsonArray.create();
|
||||
assertEquals(" ( (`name` = $1) or (`age` > $2)) and (`state` = $3)", c.export(new int[1], parameters, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNestedNotIn3() {
|
||||
QueryCriteria c = where(i("name")).is("Bubba").or(where(i("age")).gt(12)).and(i("state")).eq("1");
|
||||
JsonArray parameters = JsonArray.create();
|
||||
assertEquals(" (`name` = $1) or (`age` > $2) and `state` = $3", c.export(new int[1], parameters, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLt() {
|
||||
QueryCriteria c = where(i("name")).lt("Couch");
|
||||
@@ -252,7 +267,7 @@ class QueryCriteriaTests {
|
||||
@Test
|
||||
void testFalse() {
|
||||
QueryCriteria c = where(i("name")).FALSE();
|
||||
assertEquals("not( (`name`) )", c.export());
|
||||
assertEquals("not(`name`)", c.export());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2020 the original author or authors
|
||||
* Copyright 2012-2022 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.
|
||||
@@ -24,15 +24,21 @@ import org.springframework.data.couchbase.core.mapping.Document;
|
||||
@Document
|
||||
@CompositeQueryIndex(fields = { "id", "name desc" })
|
||||
@CompositeQueryIndex(fields = { "id.something", "name desc" })
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class Airline extends ComparableEntity {
|
||||
@Id String id;
|
||||
|
||||
@QueryIndexed String name;
|
||||
|
||||
String hqCountry;
|
||||
|
||||
@PersistenceConstructor
|
||||
public Airline(String id, String name) {
|
||||
public Airline(String id, String name, String hqCountry) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.hqCountry = hqCountry;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
@@ -43,4 +49,8 @@ public class Airline extends ComparableEntity {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getHqCountry() {
|
||||
return hqCountry;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2017-2022 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.
|
||||
@@ -16,17 +16,23 @@
|
||||
|
||||
package org.springframework.data.couchbase.domain;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.couchbase.repository.DynamicProxyable;
|
||||
import org.springframework.data.couchbase.repository.Query;
|
||||
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||
import org.springframework.data.repository.query.Param;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@Repository
|
||||
public interface AirlineRepository extends PagingAndSortingRepository<Airline, String> {
|
||||
public interface AirlineRepository extends PagingAndSortingRepository<Airline, String>,
|
||||
QuerydslPredicateExecutor<Airline>, DynamicProxyable<AirlineRepository> {
|
||||
|
||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (name = $1)")
|
||||
List<User> getByName(@Param("airline_name")String airlineName);
|
||||
List<User> getByName(@Param("airline_name") String airlineName);
|
||||
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public class CouchbaseRepositoryKeyValueIntegrationTests extends ClusterAwareInt
|
||||
userRepository.delete(user);
|
||||
|
||||
// Airline does not have a version
|
||||
Airline airline = new Airline(UUID.randomUUID().toString(), "MyAirline");
|
||||
Airline airline = new Airline(UUID.randomUUID().toString(), "MyAirline", null);
|
||||
// save the document - we don't care how on this call
|
||||
airlineRepository.save(airline);
|
||||
airlineRepository.save(airline); // If it was an insert it would fail. Can't tell if it is an upsert or replace.
|
||||
|
||||
@@ -67,6 +67,7 @@ import org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbase
|
||||
import org.springframework.data.couchbase.core.query.N1QLExpression;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteria;
|
||||
import org.springframework.data.couchbase.domain.Address;
|
||||
import org.springframework.data.couchbase.domain.AirlineRepository;
|
||||
import org.springframework.data.couchbase.domain.Airport;
|
||||
import org.springframework.data.couchbase.domain.AirportMini;
|
||||
import org.springframework.data.couchbase.domain.AirportRepository;
|
||||
@@ -124,6 +125,8 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
|
||||
|
||||
@Autowired AirportRepository airportRepository;
|
||||
|
||||
@Autowired AirlineRepository airlineRepository;
|
||||
|
||||
@Autowired UserRepository userRepository;
|
||||
|
||||
@Autowired UserSubmissionRepository userSubmissionRepository;
|
||||
|
||||
@@ -76,7 +76,7 @@ public class ReactiveCouchbaseRepositoryKeyValueIntegrationTests extends Cluster
|
||||
userRepository.delete(user);
|
||||
|
||||
// Airline does not have a version
|
||||
Airline airline = new Airline(UUID.randomUUID().toString(), "MyAirline");
|
||||
Airline airline = new Airline(UUID.randomUUID().toString(), "MyAirline", null);
|
||||
// save the document - we don't care how on this call
|
||||
airlineRepository.save(airline).block();
|
||||
airlineRepository.save(airline).block(); // If it was an insert it would fail. Can't tell if an upsert or replace.
|
||||
|
||||
@@ -0,0 +1,647 @@
|
||||
/*
|
||||
* Copyright 2017-2022 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
|
||||
*
|
||||
* https://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.couchbase.repository.query;
|
||||
|
||||
import static com.couchbase.client.java.query.QueryScanConsistency.REQUEST_PLUS;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.springframework.data.couchbase.util.Util.comprises;
|
||||
import static org.springframework.data.couchbase.util.Util.exactly;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
|
||||
import org.springframework.data.couchbase.core.CouchbaseTemplate;
|
||||
import org.springframework.data.couchbase.core.mapping.event.ValidatingCouchbaseEventListener;
|
||||
import org.springframework.data.couchbase.core.query.QueryCriteriaDefinition;
|
||||
import org.springframework.data.couchbase.domain.Airline;
|
||||
import org.springframework.data.couchbase.domain.AirlineRepository;
|
||||
import org.springframework.data.couchbase.domain.NaiveAuditorAware;
|
||||
import org.springframework.data.couchbase.domain.QAirline;
|
||||
import org.springframework.data.couchbase.domain.time.AuditingDateTimeProvider;
|
||||
import org.springframework.data.couchbase.repository.auditing.EnableCouchbaseAuditing;
|
||||
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
|
||||
import org.springframework.data.couchbase.repository.support.BasicQuery;
|
||||
import org.springframework.data.couchbase.repository.support.SpringDataCouchbaseSerializer;
|
||||
import org.springframework.data.couchbase.util.Capabilities;
|
||||
import org.springframework.data.couchbase.util.ClusterType;
|
||||
import org.springframework.data.couchbase.util.IgnoreWhen;
|
||||
import org.springframework.data.couchbase.util.JavaIntegrationTests;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
|
||||
|
||||
import com.couchbase.client.java.env.ClusterEnvironment;
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import com.querydsl.core.types.Predicate;
|
||||
import com.querydsl.core.types.dsl.BooleanExpression;
|
||||
|
||||
/**
|
||||
* Repository tests
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
@SpringJUnitConfig(CouchbaseRepositoryQuerydslIntegrationTests.Config.class)
|
||||
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
|
||||
public class CouchbaseRepositoryQuerydslIntegrationTests extends JavaIntegrationTests {
|
||||
|
||||
@Autowired AirlineRepository airlineRepository;
|
||||
|
||||
static QAirline airline = QAirline.airline;
|
||||
// saved
|
||||
static Airline united = new Airline("1", "United Airlines", "US");
|
||||
static Airline lufthansa = new Airline("2", "Lufthansa", "DE");
|
||||
static Airline emptyStringAirline = new Airline("3", "Empty String", "");
|
||||
static Airline nullStringAirline = new Airline("4", "Null String", null);
|
||||
static Airline unitedLowercase = new Airline("5", "united airlines", "US");
|
||||
static Airline[] saved = new Airline[] { united, lufthansa, emptyStringAirline, nullStringAirline, unitedLowercase };
|
||||
// not saved
|
||||
static Airline flyByNight = new Airline("1001", "Fly By Night", "UK");
|
||||
static Airline sleepByDay = new Airline("1002", "Sleep By Day", "CA");
|
||||
static Airline[] notSaved = new Airline[] { flyByNight, sleepByDay };
|
||||
|
||||
SpringDataCouchbaseSerializer serializer = new SpringDataCouchbaseSerializer(couchbaseTemplate.getConverter());
|
||||
|
||||
@BeforeAll
|
||||
static public void beforeAll() {
|
||||
callSuperBeforeAll(new Object() {});
|
||||
ApplicationContext ac = new AnnotationConfigApplicationContext(
|
||||
CouchbaseRepositoryQuerydslIntegrationTests.Config.class);
|
||||
CouchbaseTemplate template = (CouchbaseTemplate) ac.getBean("couchbaseTemplate");
|
||||
for (Airline airline : saved) {
|
||||
template.insertById(Airline.class).one(airline);
|
||||
}
|
||||
template.findByQuery(Airline.class).withConsistency(REQUEST_PLUS).all();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static public void afterAll() {
|
||||
ApplicationContext ac = new AnnotationConfigApplicationContext(
|
||||
CouchbaseRepositoryQuerydslIntegrationTests.Config.class);
|
||||
CouchbaseTemplate template = (CouchbaseTemplate) ac.getBean("couchbaseTemplate");
|
||||
for (Airline airline : saved) {
|
||||
template.removeById(Airline.class).one(airline.getId());
|
||||
}
|
||||
template.findByQuery(Airline.class).withConsistency(REQUEST_PLUS).all();
|
||||
callSuperAfterAll(new Object() {});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEq() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.eq(flyByNight.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().equals(flyByNight.getName())).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name = $1", bq(predicate));
|
||||
|
||||
}
|
||||
{
|
||||
BooleanExpression predicate = airline.name.eq(united.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().equals(united.getName())).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name = $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
// this gives hqCountry == "" and hqCountry is missing
|
||||
// @Test
|
||||
void testStringIsEmpty() {
|
||||
{
|
||||
BooleanExpression predicate = airline.hqCountry.isEmpty();
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result, emptyStringAirline, nullStringAirline), "[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE UPPER(name) like $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNot() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.eq(united.getName()).and(airline.hqCountry.eq(united.getHqCountry()))
|
||||
.not();
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> !(a.getName().equals(united.getName()) && a.getHqCountry().equals(united.getHqCountry())))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE not( ( (hqCountry = $1) and (name = $2)) )", bq(predicate));
|
||||
}
|
||||
{
|
||||
BooleanExpression predicate = airline.name.in(Arrays.asList(united.getName())).not();
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> !(a.getName().equals(united.getName()))).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE not( (name = $1) )", bq(predicate));
|
||||
}
|
||||
{
|
||||
BooleanExpression predicate = airline.name.eq(united.getName()).not();
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> !(a.getName().equals(united.getName()))).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE not( (name = $1) )", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAnd() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.eq(united.getName()).and(airline.hqCountry.eq(united.getHqCountry()));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().equals(united.getName()) && a.getHqCountry().equals(united.getHqCountry()))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE (name = $1) and (hqCountry = $2)", bq(predicate));
|
||||
}
|
||||
{
|
||||
BooleanExpression predicate = airline.name.eq(united.getName())
|
||||
.and(airline.hqCountry.eq(lufthansa.getHqCountry()));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().equals(united.getName()) && a.getHqCountry().equals(lufthansa.getHqCountry()))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE (name = $1) and (hqCountry = $2)", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOr() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.eq(united.getName())
|
||||
.or(airline.hqCountry.eq(lufthansa.getHqCountry()));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().equals(united.getName()) || a.getName().equals(lufthansa.getName()))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE (name = $1) or (hqCountry = $2)", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNe() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.ne(united.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> !a.getName().equals(united.getName())).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name != $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStartsWith() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.startsWith(united.getName().substring(0, 5));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result, Arrays.stream(saved)
|
||||
.filter(a -> a.getName().startsWith(united.getName().substring(0, 5))).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name like ($1||\"%\")", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testStartsWithIgnoreCase() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.startsWithIgnoreCase(united.getName().substring(0, 5));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().toUpperCase().startsWith(united.getName().toUpperCase().substring(0, 5)))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE UPPER(name) like ($1||\"%\")", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEndsWith() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.endsWith(united.getName().substring(1));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result, Arrays.stream(saved).filter(a -> a.getName().endsWith(united.getName().substring(1)))
|
||||
.toArray(Airline[]::new)), "[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name like (\"%\"||$1)", bq(predicate));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEndsWithIgnoreCase() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.endsWithIgnoreCase(united.getName().substring(1));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().toUpperCase().endsWith(united.getName().toUpperCase().substring(1)))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE UPPER(name) like (\"%\"||$1)", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEqIgnoreCase() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.equalsIgnoreCase(flyByNight.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().equalsIgnoreCase(flyByNight.getName())).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE UPPER(name) = $1", bq(predicate));
|
||||
}
|
||||
{
|
||||
BooleanExpression predicate = airline.name.equalsIgnoreCase(united.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().equalsIgnoreCase(united.getName())).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE UPPER(name) = $1", bq(predicate));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testContains() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.contains("United");
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result, Arrays.stream(saved).filter(a -> a.getName().contains("United")).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE contains(name, $1)", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testContainsIgnoreCase() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.containsIgnoreCase("united");
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().toUpperCase(Locale.ROOT).contains("united".toUpperCase(Locale.ROOT)))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE contains(UPPER(name), $1)", bq(predicate));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLike() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.like("%nited%");
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result, Arrays.stream(saved).filter(a -> a.getName().contains("nited")).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name like $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLikeIgnoreCase() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.likeIgnoreCase("%Airlines");
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().toUpperCase(Locale.ROOT).endsWith("Airlines".toUpperCase(Locale.ROOT)))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE UPPER(name) like $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
// This is 'between' is inclusive
|
||||
@Test
|
||||
void testBetween() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.between(flyByNight.getName(), united.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(
|
||||
a -> a.getName().compareTo(flyByNight.getName()) >= 0 && a.getName().compareTo(united.getName()) <= 0)
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name between $1 and $2", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIn() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.in(Arrays.asList(united.getName()));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().equals(united.getName())).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name = $1", bq(predicate));
|
||||
}
|
||||
|
||||
{
|
||||
BooleanExpression predicate = airline.name.in(Arrays.asList(united.getName(), lufthansa.getName()));
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().equals(united.getName()) || a.getName().equals(lufthansa.getName()))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name in $1", bq(predicate));
|
||||
}
|
||||
|
||||
{
|
||||
BooleanExpression predicate = airline.name.in("Fly By Night", "Sleep By Day");
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> a.getName().equals(flyByNight.getName()) || a.getName().equals(sleepByDay.getName()))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name in $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNotIn() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.notIn("Fly By Night", "Sleep By Day");
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved)
|
||||
.filter(a -> !(a.getName().equals(flyByNight.getName()) || a.getName().equals(sleepByDay.getName())))
|
||||
.toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE not( (name in $1) )", bq(predicate));
|
||||
}
|
||||
{
|
||||
BooleanExpression predicate = airline.name.notIn(united.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> !a.getName().equals(united.getName())).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name != $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void testColIsEmpty() {}
|
||||
|
||||
@Test
|
||||
void testLt() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.lt(lufthansa.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().compareTo(lufthansa.getName()) < 0).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name < $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGt() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.gt(lufthansa.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(
|
||||
comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().compareTo(lufthansa.getName()) > 0).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name > $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testLoe() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.loe(lufthansa.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().compareTo(lufthansa.getName()) <= 0).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name <= $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testGoe() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.goe(lufthansa.getName());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().compareTo(lufthansa.getName()) >= 0).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name >= $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
// when hqCountry == null, no value is stored therefore isNull is false. Only hqCountry:null gives isNull
|
||||
// and we don't have that. Conversely, only hqCountry has a value (which is not 'null') gives isNotNull
|
||||
// so isNull and isNotNull are *not* compliments
|
||||
@Test
|
||||
@Disabled
|
||||
void testIsNull() {
|
||||
{
|
||||
BooleanExpression predicate = airline.hqCountry.isNull();
|
||||
Optional<Airline> result = airlineRepository.findOne(predicate);
|
||||
assertNull(exactly(result, nullStringAirline), "[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE name = $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
// when hqCountry == null, no value is stored therefore isNull is false. Only hqCountry:null gives isNull
|
||||
// and we don't have that. Conversely, only hqCountry has a value (which is not 'null') gives isNotNull
|
||||
// so isNull and isNotNull are *not* compliments
|
||||
@Test
|
||||
void testIsNotNull() {
|
||||
{
|
||||
BooleanExpression predicate = airline.hqCountry.isNotNull();
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result, Arrays.stream(saved).filter(a -> a.getHqCountry() != null).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE hqCountry is not null", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
void testContainsKey() {}
|
||||
|
||||
@Test
|
||||
void testStringLength() {
|
||||
{
|
||||
BooleanExpression predicate = airline.name.length().eq(united.getName().length());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result,
|
||||
Arrays.stream(saved).filter(a -> a.getName().length() == united.getName().length()).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE LENGTH(name) = $1", bq(predicate));
|
||||
}
|
||||
{
|
||||
BooleanExpression predicate = airline.name.length().eq(flyByNight.getName().length());
|
||||
Iterable<Airline> result = airlineRepository.findAll(predicate);
|
||||
assertNull(comprises(result, Arrays.stream(saved)
|
||||
.filter(a -> a.getName().length() == flyByNight.getName().length()).toArray(Airline[]::new)),
|
||||
"[unexpected] -> [missing]");
|
||||
assertEquals(" WHERE LENGTH(name) = $1", bq(predicate));
|
||||
}
|
||||
}
|
||||
|
||||
private void sleep(int millis) {
|
||||
try {
|
||||
Thread.sleep(millis); // so they are executed out-of-order
|
||||
} catch (InterruptedException ie) {
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
|
||||
@EnableCouchbaseAuditing(dateTimeProviderRef = "dateTimeProviderRef")
|
||||
static class Config extends AbstractCouchbaseConfiguration {
|
||||
|
||||
@Override
|
||||
public String getConnectionString() {
|
||||
return connectionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserName() {
|
||||
return config().adminUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return config().adminPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBucketName() {
|
||||
return bucketName();
|
||||
}
|
||||
|
||||
@Bean(name = "auditorAwareRef")
|
||||
public NaiveAuditorAware testAuditorAware() {
|
||||
return new NaiveAuditorAware();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureEnvironment(final ClusterEnvironment.Builder builder) {
|
||||
builder.ioConfig().maxHttpConnections(11).idleHttpConnectionTimeout(Duration.ofSeconds(4));
|
||||
return;
|
||||
}
|
||||
|
||||
@Bean(name = "dateTimeProviderRef")
|
||||
public DateTimeProvider testDateTimeProvider() {
|
||||
return new AuditingDateTimeProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public LocalValidatorFactoryBean validator() {
|
||||
return new LocalValidatorFactoryBean();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ValidatingCouchbaseEventListener validationEventListener() {
|
||||
return new ValidatingCouchbaseEventListener(validator());
|
||||
}
|
||||
}
|
||||
|
||||
String bq(Predicate predicate) {
|
||||
BasicQuery basicQuery = new BasicQuery((QueryCriteriaDefinition) serializer.handle(predicate), null);
|
||||
return basicQuery.export(new int[1]);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
|
||||
@EnableCouchbaseAuditing(auditorAwareRef = "auditorAwareRef", dateTimeProviderRef = "dateTimeProviderRef")
|
||||
static class ConfigRequestPlus extends AbstractCouchbaseConfiguration {
|
||||
|
||||
@Override
|
||||
public String getConnectionString() {
|
||||
return connectionString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserName() {
|
||||
return config().adminUsername();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return config().adminPassword();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBucketName() {
|
||||
return bucketName();
|
||||
}
|
||||
|
||||
@Bean(name = "auditorAwareRef")
|
||||
public NaiveAuditorAware testAuditorAware() {
|
||||
return new NaiveAuditorAware();
|
||||
}
|
||||
|
||||
@Bean(name = "dateTimeProviderRef")
|
||||
public DateTimeProvider testDateTimeProvider() {
|
||||
return new AuditingDateTimeProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueryScanConsistency getDefaultConsistency() {
|
||||
return REQUEST_PLUS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2017-2022 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.
|
||||
@@ -23,7 +23,6 @@ import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -58,6 +57,8 @@ import org.springframework.data.repository.query.QueryMethodEvaluationContextPro
|
||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||
|
||||
/**
|
||||
* @author Michael Nitschinger
|
||||
* @author Michael Reiche
|
||||
@@ -82,7 +83,7 @@ class StringN1qlQueryCreatorTests extends ClusterAwareIntegrationTests {
|
||||
@Test
|
||||
@IgnoreWhen(missesCapabilities = Capabilities.QUERY, clusterTypes = ClusterType.MOCKED)
|
||||
void findUsingStringNq1l() throws Exception {
|
||||
Airline airline = new Airline(UUID.randomUUID().toString(), "Continental");
|
||||
Airline airline = new Airline(UUID.randomUUID().toString(), "Continental", "USA");
|
||||
try {
|
||||
Airline modified = couchbaseTemplate.upsertById(Airline.class).one(airline);
|
||||
|
||||
@@ -99,8 +100,8 @@ class StringN1qlQueryCreatorTests extends ClusterAwareIntegrationTests {
|
||||
|
||||
Query query = creator.createQuery();
|
||||
|
||||
ExecutableFindByQuery q = (ExecutableFindByQuery) couchbaseTemplate
|
||||
.findByQuery(Airline.class).withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query);
|
||||
ExecutableFindByQuery q = (ExecutableFindByQuery) couchbaseTemplate.findByQuery(Airline.class)
|
||||
.withConsistency(QueryScanConsistency.REQUEST_PLUS).matching(query);
|
||||
|
||||
Optional<Airline> al = q.one();
|
||||
assertEquals(airline.toString(), al.get().toString());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020 the original author or authors
|
||||
* Copyright 2020-2022 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.
|
||||
@@ -16,84 +16,134 @@
|
||||
|
||||
package org.springframework.data.couchbase.util;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.awaitility.Awaitility.with;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.data.util.Pair;
|
||||
|
||||
/**
|
||||
* Provides a bunch of utility APIs that help with testing.
|
||||
*
|
||||
* @author Michael Reiche
|
||||
*/
|
||||
public class Util {
|
||||
|
||||
/**
|
||||
* Waits and sleeps for a little bit of time until the given condition is met.
|
||||
*
|
||||
* <p>Sleeps 1ms between "false" invocations. It will wait at most one minute to prevent hanging forever in case
|
||||
* the condition never becomes true.
|
||||
*
|
||||
* @param supplier return true once it should stop waiting.
|
||||
*/
|
||||
public static void waitUntilCondition(final BooleanSupplier supplier) {
|
||||
waitUntilCondition(supplier, Duration.ofMinutes(1));
|
||||
}
|
||||
/**
|
||||
* Waits and sleeps for a little bit of time until the given condition is met.
|
||||
* <p>
|
||||
* Sleeps 1ms between "false" invocations. It will wait at most one minute to prevent hanging forever in case the
|
||||
* condition never becomes true.
|
||||
*
|
||||
* @param supplier return true once it should stop waiting.
|
||||
*/
|
||||
public static void waitUntilCondition(final BooleanSupplier supplier) {
|
||||
waitUntilCondition(supplier, Duration.ofMinutes(1));
|
||||
}
|
||||
|
||||
public static void waitUntilCondition(final BooleanSupplier supplier, Duration atMost) {
|
||||
with().pollInterval(Duration.ofMillis(1)).await().atMost(atMost).until(supplier::getAsBoolean);
|
||||
}
|
||||
public static void waitUntilCondition(final BooleanSupplier supplier, Duration atMost) {
|
||||
with().pollInterval(Duration.ofMillis(1)).await().atMost(atMost).until(supplier::getAsBoolean);
|
||||
}
|
||||
|
||||
public static void waitUntilCondition(final BooleanSupplier supplier, Duration atMost, Duration delay) {
|
||||
with().pollInterval(delay).await().atMost(atMost).until(supplier::getAsBoolean);
|
||||
}
|
||||
public static void waitUntilCondition(final BooleanSupplier supplier, Duration atMost, Duration delay) {
|
||||
with().pollInterval(delay).await().atMost(atMost).until(supplier::getAsBoolean);
|
||||
}
|
||||
|
||||
public static void waitUntilThrows(final Class<? extends Exception> clazz, final Supplier<Object> supplier) {
|
||||
with()
|
||||
.pollInterval(Duration.ofMillis(1))
|
||||
.await()
|
||||
.atMost(Duration.ofMinutes(1))
|
||||
.until(() -> {
|
||||
try {
|
||||
supplier.get();
|
||||
} catch (final Exception ex) {
|
||||
return ex.getClass().isAssignableFrom(clazz);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
public static void waitUntilThrows(final Class<? extends Exception> clazz, final Supplier<Object> supplier) {
|
||||
with().pollInterval(Duration.ofMillis(1)).await().atMost(Duration.ofMinutes(1)).until(() -> {
|
||||
try {
|
||||
supplier.get();
|
||||
} catch (final Exception ex) {
|
||||
return ex.getClass().isAssignableFrom(clazz);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a thread with the given name is currently running.
|
||||
*
|
||||
* @param name the name of the thread.
|
||||
* @return true if running, false otherwise.
|
||||
*/
|
||||
public static boolean threadRunning(final String name) {
|
||||
for (Thread t : Thread.getAllStackTraces().keySet()) {
|
||||
if (t.getName().equalsIgnoreCase(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Returns true if a thread with the given name is currently running.
|
||||
*
|
||||
* @param name the name of the thread.
|
||||
* @return true if running, false otherwise.
|
||||
*/
|
||||
public static boolean threadRunning(final String name) {
|
||||
for (Thread t : Thread.getAllStackTraces().keySet()) {
|
||||
if (t.getName().equalsIgnoreCase(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a file from the resources folder (in the same path as the requesting test class).
|
||||
*
|
||||
* <p>The class will be automatically loaded relative to the namespace and converted
|
||||
* to a string.
|
||||
*
|
||||
* @param filename the filename of the resource.
|
||||
* @param clazz the reference class.
|
||||
* @return the loaded string.
|
||||
*/
|
||||
public static String readResource(final String filename, final Class<?> clazz) {
|
||||
String path = "/" + clazz.getPackage().getName().replace(".", "/") + "/" + filename;
|
||||
InputStream stream = clazz.getResourceAsStream(path);
|
||||
java.util.Scanner s = new java.util.Scanner(stream, UTF_8.name()).useDelimiter("\\A");
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
/**
|
||||
* Reads a file from the resources folder (in the same path as the requesting test class).
|
||||
* <p>
|
||||
* The class will be automatically loaded relative to the namespace and converted to a string.
|
||||
* </p>
|
||||
*
|
||||
* @param filename the filename of the resource.
|
||||
* @param clazz the reference class.
|
||||
* @return the loaded string.
|
||||
*/
|
||||
public static String readResource(final String filename, final Class<?> clazz) {
|
||||
String path = "/" + clazz.getPackage().getName().replace(".", "/") + "/" + filename;
|
||||
InputStream stream = clazz.getResourceAsStream(path);
|
||||
java.util.Scanner s = new java.util.Scanner(stream, UTF_8.name()).useDelimiter("\\A");
|
||||
return s.hasNext() ? s.next() : "";
|
||||
}
|
||||
|
||||
public static <T> Pair<List<T>, List<T>> comprises(Iterable<T> source, T... airlines) {
|
||||
List<T> unexpected = new LinkedList<>();
|
||||
List<T> missing = new LinkedList<T>();
|
||||
source.forEach(unexpected::add);
|
||||
for (T t : airlines) {
|
||||
if (!unexpected.contains(t)) {
|
||||
missing.add(t);
|
||||
} else {
|
||||
unexpected.remove(t);
|
||||
}
|
||||
}
|
||||
if (unexpected.isEmpty() && missing.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return Pair.of(unexpected, missing);
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Pair<List<T>, List<T>> exactly(Optional<T> result, T... airlines) {
|
||||
List<T> source = new LinkedList<>();
|
||||
if (result.isPresent()) {
|
||||
source.add(result.get());
|
||||
}
|
||||
return comprises(source, airlines);
|
||||
}
|
||||
|
||||
// should return null if items in source match (allAirlines - notAirlines)
|
||||
public static <T> Pair<List<T>, List<T>> comprisesNot(Iterable<T> source, T[] allAirlines, T... notAirlines) {
|
||||
List<T> expected = new LinkedList<>(Arrays.asList(allAirlines));
|
||||
expected.removeAll(Arrays.asList(notAirlines));
|
||||
List<T> unexpected = new LinkedList<>();
|
||||
List<T> missing = new LinkedList<T>();
|
||||
source.forEach(unexpected::add);// initially, everything returned is unexpected
|
||||
for (T t : expected) {
|
||||
if (!unexpected.contains(t)) {
|
||||
missing.add(t); // if not returned, then it is missing
|
||||
} else {
|
||||
unexpected.remove(t); // if returned, then remove from unexpected
|
||||
}
|
||||
}
|
||||
if (unexpected.isEmpty() && missing.isEmpty()) {
|
||||
return null;
|
||||
} else {
|
||||
return Pair.of(unexpected, missing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user