DATACOUCH-296 Add support for delete n1ql queries

Changes to support delete query by query derivation and SpEL based
queries.
- Adds a N1qlMutateQueryCreator which constucts the delete query
using dsl.
- Adds new SpEL values delete and returning required for delete
queries.

Original pull request: #144.
This commit is contained in:
Subhashni Balakrishnan
2017-04-14 13:50:50 -07:00
parent 2febc3709c
commit 70343b0ac1
16 changed files with 553 additions and 303 deletions

View File

@@ -123,10 +123,6 @@ public abstract class AbstractN1qlBasedQuery implements RepositoryQuery {
protected Object executeDependingOnType(N1qlQuery query, N1qlQuery countQuery, QueryMethod queryMethod,
Pageable pageable, Class<?> typeToRead) {
if (queryMethod.isModifyingQuery()) {
throw new UnsupportedOperationException("Modifying queries not yet supported");
}
if (queryMethod.isPageQuery()) {
return executePaged(query, countQuery, pageable, typeToRead);
} else if (queryMethod.isSliceQuery()) {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors
* Copyright 2012-2017 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,8 +23,11 @@ import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
/**
* An {@link Iterator Iterator&lt;Object&gt;} that {@link CouchbaseConverter#convertForWriteIfNeeded(Object) converts}
* values to their stored Class if warranted.
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
*/
class ConvertingIterator implements Iterator<Object> {
public class ConvertingIterator implements Iterator<Object> {
private final Iterator<Object> delegate;
private final CouchbaseConverter converter;

View File

@@ -0,0 +1,77 @@
/*
* Copyright 2017 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.repository.query;
import java.util.Iterator;
import com.couchbase.client.java.query.dsl.Expression;
import com.couchbase.client.java.query.dsl.path.*;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.repository.query.support.N1qlQueryCreatorUtils;
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
import org.springframework.data.repository.query.parser.PartTree;
/**
* N1qlMutateQueryCreator allows to create queries for delete operations.
*
* See {@link N1qlQueryCreator} for part types supported
*
* @author Subhashni Balakrishnan
*/
public class N1qlMutateQueryCreator extends AbstractQueryCreator<MutateLimitPath, Expression> {
private final MutateWherePath mutateFrom;
private final CouchbaseConverter converter;
private final CouchbaseQueryMethod queryMethod;
private final ParameterAccessor accessor;
public N1qlMutateQueryCreator(PartTree tree, ParameterAccessor parameters, MutateWherePath mutateFrom,
CouchbaseConverter converter, CouchbaseQueryMethod queryMethod) {
super(tree, parameters);
this.mutateFrom = mutateFrom;
this.converter = converter;
this.queryMethod = queryMethod;
this.accessor = parameters;
}
@Override
protected Expression create(Part part, Iterator<Object> iterator) {
return N1qlQueryCreatorUtils.prepareExpression(this.converter, part, iterator);
}
@Override
protected Expression and(Part part, Expression base, Iterator<Object> iterator) {
if (base == null) {
return create(part, iterator);
}
return base.and(create(part, iterator));
}
@Override
protected Expression or(Expression base, Expression criteria) {
return base.or(criteria);
}
@Override
protected MutateLimitPath complete(Expression criteria, Sort sort) {
Expression whereCriteria = N1qlUtils.createWhereFilterForEntity(criteria, this.converter, this.queryMethod.getEntityInformation());
return mutateFrom.where(whereCriteria);
}
}

View File

@@ -16,27 +16,19 @@
package org.springframework.data.couchbase.repository.query;
import static com.couchbase.client.java.query.dsl.Expression.s;
import static com.couchbase.client.java.query.dsl.Expression.x;
import java.util.Collection;
import java.util.Iterator;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.query.dsl.Expression;
import com.couchbase.client.java.query.dsl.functions.PatternMatchingFunctions;
import com.couchbase.client.java.query.dsl.functions.StringFunctions;
import com.couchbase.client.java.query.dsl.path.LimitPath;
import com.couchbase.client.java.query.dsl.path.OrderByPath;
import com.couchbase.client.java.query.dsl.path.WherePath;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.repository.query.support.N1qlQueryCreatorUtils;
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
import org.springframework.data.repository.query.parser.Part;
@@ -88,6 +80,7 @@ import org.springframework.data.repository.query.parser.PartTree;
* </p>
*
* @author Simon Baslé
* @author Subhashni Balakrishnan
* @author Mark Paluch
*/
public class N1qlQueryCreator extends AbstractQueryCreator<LimitPath, Expression> {
@@ -108,7 +101,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<LimitPath, Expression
@Override
protected Expression create(Part part, Iterator<Object> iterator) {
return prepareExpression(part, iterator);
return N1qlQueryCreatorUtils.prepareExpression(converter, part, iterator);
}
@Override
@@ -144,161 +137,4 @@ public class N1qlQueryCreator extends AbstractQueryCreator<LimitPath, Expression
return selectFromWhere;
}
protected Expression prepareExpression(Part part, Iterator<Object> iterator) {
PersistentPropertyPath<CouchbasePersistentProperty> path = N1qlUtils.getPathWithAlternativeFieldNames(
this.converter, part.getProperty());
ConvertingIterator parameterValues = new ConvertingIterator(iterator, converter);
//get the whole doted path with fieldNames instead of potentially wrong propNames
String fieldNamePath = N1qlUtils.getDottedPathWithAlternativeFieldNames(path);
//deal with ignore case
boolean ignoreCase = false;
Class<?> leafType = converter.getWriteClassFor(path.getLeafProperty().getType());
boolean isString = leafType == String.class;
if (part.shouldIgnoreCase() == Part.IgnoreCaseType.WHEN_POSSIBLE) {
ignoreCase = isString;
} else if (part.shouldIgnoreCase() == Part.IgnoreCaseType.ALWAYS) {
if (!isString) {
throw new IllegalArgumentException(String.format("Part %s must be of type String but was %s", fieldNamePath, leafType));
}
ignoreCase = true;
}
return createExpression(part.getType(), fieldNamePath, ignoreCase, parameterValues);
}
protected static Expression createExpression(Part.Type partType, String fieldNamePath, boolean ignoreCase, Iterator<Object> parameterValues) {
//create the left hand side of the expression, taking ignoreCase into account
Expression left = ignoreCase ? StringFunctions.lower(x(fieldNamePath)) : x(fieldNamePath);
switch (partType) {
case BETWEEN:
return left.between(leftAndRight(parameterValues, ignoreCase));
case IS_NOT_NULL:
return left.isNotNull();
case IS_NULL:
return left.isNull();
case NEGATING_SIMPLE_PROPERTY:
return left.ne(right(parameterValues, ignoreCase));
case SIMPLE_PROPERTY:
return left.eq(right(parameterValues, ignoreCase));
case BEFORE:
case LESS_THAN:
return left.lt(right(parameterValues, ignoreCase));
case LESS_THAN_EQUAL:
return left.lte(right(parameterValues, ignoreCase));
case GREATER_THAN_EQUAL:
return left.gte(right(parameterValues, ignoreCase));
case AFTER:
case GREATER_THAN:
return left.gt(right(parameterValues, ignoreCase));
case NOT_LIKE:
return left.notLike(right(parameterValues, ignoreCase));
case LIKE:
return left.like(right(parameterValues, ignoreCase));
case STARTING_WITH:
return left.like(like(parameterValues, ignoreCase, false, true));
case ENDING_WITH:
return left.like(like(parameterValues, ignoreCase, true, false));
case NOT_CONTAINING:
return left.notLike(like(parameterValues, ignoreCase, true, true));
case CONTAINING:
return left.like(like(parameterValues, ignoreCase, true, true));
case NOT_IN:
return left.notIn(rightArray(parameterValues));
case IN:
return left.in(rightArray(parameterValues));
case TRUE:
return left.eq(true);
case FALSE:
return left.eq(false);
case REGEX:
return regexp(fieldNamePath, parameterValues);
case EXISTS:
return left.isNotMissing();
case WITHIN:
case NEAR:
default:
throw new IllegalArgumentException("Unsupported keyword in N1QL query derivation");
}
}
protected static Expression regexp(String left, Iterator<Object> parameterValues) {
Object next = parameterValues.next();
String pattern;
if (next == null) {
pattern = "";
} else {
pattern = String.valueOf(next);
}
return PatternMatchingFunctions.regexpLike(left, pattern);
}
protected static Expression leftAndRight(Iterator<Object> parameterValues, boolean ignoreCase) {
return right(parameterValues, ignoreCase).and(right(parameterValues, ignoreCase));
}
protected static Expression like(Iterator<Object> parameterValues, boolean ignoreCase,
boolean anyPrefix, boolean anySuffix) {
Object next = parameterValues.next();
if (next == null) {
return Expression.NULL();
}
Expression converted;
if (next instanceof String) {
String pattern = (String) next;
if (anyPrefix) {
pattern = "%" + pattern;
}
if (anySuffix) {
pattern = pattern + "%";
}
converted = s(pattern);
} else {
converted = x(String.valueOf(next));
}
if (ignoreCase) {
return StringFunctions.lower(converted);
}
return converted;
}
protected static Expression right(Iterator<Object> parameterValues, boolean ignoreCase) {
Object next = parameterValues.next();
if (next == null) {
return Expression.NULL();
}
Expression converted;
if (next instanceof String) {
converted = s((String) next);
} else {
converted = x(String.valueOf(next));
}
if (ignoreCase) {
return StringFunctions.lower(converted);
}
return converted;
}
protected static JsonArray rightArray(Iterator<Object> parameterValues) {
Object next = parameterValues.next();
Object[] values;
if (next instanceof Collection) {
values = ((Collection<?>) next).toArray();
} else if (next.getClass().isArray()) {
values = (Object[]) next;
} else {
values = new Object[] {next};
}
return JsonArray.from(values);
}
}

View File

@@ -16,9 +16,11 @@
package org.springframework.data.couchbase.repository.query;
import static com.couchbase.client.java.query.Delete.deleteFrom;
import static com.couchbase.client.java.query.Select.select;
import static com.couchbase.client.java.query.dsl.Expression.i;
import static com.couchbase.client.java.query.dsl.functions.AggregateFunctions.count;
import static org.springframework.data.couchbase.repository.query.support.N1qlUtils.createReturningExpressionForDelete;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.document.json.JsonValue;
import com.couchbase.client.java.query.Statement;
@@ -26,7 +28,8 @@ import com.couchbase.client.java.query.dsl.Expression;
import com.couchbase.client.java.query.dsl.path.FromPath;
import com.couchbase.client.java.query.dsl.path.LimitPath;
import com.couchbase.client.java.query.dsl.path.WherePath;
import com.couchbase.client.java.query.dsl.path.MutateLimitPath;
import com.couchbase.client.java.query.dsl.path.DeleteUsePath;
import org.springframework.data.couchbase.core.CouchbaseOperations;
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
import org.springframework.data.domain.Pageable;
@@ -71,30 +74,40 @@ public class PartTreeN1qlBasedQuery extends AbstractN1qlBasedQuery {
String bucketName = getCouchbaseOperations().getCouchbaseBucket().name();
Expression bucket = N1qlUtils.escapedBucket(bucketName);
FromPath select;
if (partTree.isCountProjection()) {
select = select(count("*"));
if (partTree.isDelete()) {
DeleteUsePath deleteUsePath = deleteFrom(bucket);
N1qlMutateQueryCreator queryCreator = new N1qlMutateQueryCreator(partTree, accessor, deleteUsePath, getCouchbaseOperations().getConverter(), getQueryMethod());
MutateLimitPath mutateFromWhereOrderBy = queryCreator.createQuery();
if (partTree.isLimiting()) {
return mutateFromWhereOrderBy.limit(partTree.getMaxResults());
} else {
return mutateFromWhereOrderBy.returning(createReturningExpressionForDelete(bucketName));
}
} else {
FromPath select;
if (partTree.isCountProjection()) {
select = select(count("*"));
} else {
select = N1qlUtils.createSelectClauseForEntity(bucketName, returnedType, this.getCouchbaseOperations().getConverter());
}
WherePath selectFrom = select.from(bucket);
}
WherePath selectFrom = select.from(bucket);
N1qlQueryCreator queryCreator = new N1qlQueryCreator(partTree, accessor, selectFrom,
getCouchbaseOperations().getConverter(), getQueryMethod());
LimitPath selectFromWhereOrderBy = queryCreator.createQuery();
N1qlQueryCreator queryCreator = new N1qlQueryCreator(partTree, accessor, selectFrom,
getCouchbaseOperations().getConverter(), getQueryMethod());
LimitPath selectFromWhereOrderBy = queryCreator.createQuery();
if (queryMethod.isPageQuery()) {
Pageable pageable = accessor.getPageable();
Assert.notNull(pageable, "Pageable must not be null!");
return selectFromWhereOrderBy.limit(pageable.getPageSize()).offset(Math.toIntExact(pageable.getOffset()));
} else if (queryMethod.isSliceQuery() && accessor.getPageable().isPaged()) {
Pageable pageable = accessor.getPageable();
Assert.notNull(pageable, "Pageable must not be null!");
return selectFromWhereOrderBy.limit(pageable.getPageSize() + 1).offset(Math.toIntExact(pageable.getOffset()));
} else if (partTree.isLimiting()) {
return selectFromWhereOrderBy.limit(partTree.getMaxResults());
} else {
return selectFromWhereOrderBy;
if (queryMethod.isPageQuery()) {
Pageable pageable = accessor.getPageable();
Assert.notNull(pageable, "Pageable must not be null!");
return selectFromWhereOrderBy.limit(pageable.getPageSize()).offset(Math.toIntExact(pageable.getOffset()));
} else if (queryMethod.isSliceQuery() && accessor.getPageable().isPaged()) {
Pageable pageable = accessor.getPageable();
Assert.notNull(pageable, "Pageable must not be null!");
return selectFromWhereOrderBy.limit(pageable.getPageSize() + 1).offset(Math.toIntExact(pageable.getOffset()));
} else if (partTree.isLimiting()) {
return selectFromWhereOrderBy.limit(partTree.getMaxResults());
} else {
return selectFromWhereOrderBy;
}
}
}

View File

@@ -15,6 +15,8 @@
*/
package org.springframework.data.couchbase.repository.query;
import static com.couchbase.client.java.query.Delete.deleteFrom;
import static com.couchbase.client.java.query.dsl.Expression.i;
import static org.springframework.data.couchbase.core.support.TemplateUtils.*;
import java.util.ArrayList;
@@ -27,6 +29,7 @@ import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.document.json.JsonObject;
import com.couchbase.client.java.document.json.JsonValue;
import org.slf4j.LoggerFactory;
import org.springframework.data.couchbase.repository.query.support.N1qlUtils;
import org.springframework.data.repository.query.Parameter;
import org.springframework.data.repository.query.ParameterAccessor;
import org.springframework.data.repository.query.QueryMethod;
@@ -71,6 +74,22 @@ public class StringBasedN1qlQueryParser {
*/
public static final String SPEL_FILTER = "#" + SPEL_PREFIX + ".filter";
/**
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query}
* annotation's inline statement. This will be replaced by the correct <code>delete</code> expression needed
* Eg. <code>"#{{@value SPEL_DELETE}} WHERE test = true"</code>.
* Note this only makes sense once, as the beginning of the statement.
*/
public static final String SPEL_DELETE = "#" + SPEL_PREFIX + ".delete";
/**
* Use this variable in a SpEL expression in a {@link org.springframework.data.couchbase.core.query.Query @Query}
* annotation's inline statement. This will be replaced by the correct <code>returning</code> clause needed
* for entity mapping. Eg. <code>"#{{@value SPEL_RETURNING}} WHERE test = true"</code>.
* Note this only makes sense once, as the beginning of the statement.
*/
public static final String SPEL_RETURNING = "#" + SPEL_PREFIX + ".returning";
/** regexp that detect $named placeholder (starts with a letter, then alphanum chars) */
public static final Pattern NAMED_PLACEHOLDER_PATTERN = Pattern.compile("\\W(\\$\\p{Alpha}\\p{Alnum}*)\\b");
@@ -116,10 +135,12 @@ public class StringBasedN1qlQueryParser {
} else {
selectEntity = "SELECT " + entity + ", " + b + ".* FROM " + b;
}
String typeSelection = "`" + typeField + "` = \"" + typeValue.getName() + "\"";
return new N1qlSpelValues(selectEntity, entity, b, typeSelection);
String delete = deleteFrom(i(bucketName)).toString();
String returning = " returning " + N1qlUtils.createReturningExpressionForDelete(bucketName).toString();
return new N1qlSpelValues(selectEntity, entity, b, typeSelection, delete, returning);
}
//this static method can be used to test the parsing behavior for Couchbase specific spel variables
@@ -265,17 +286,30 @@ public class StringBasedN1qlQueryParser {
public final String bucket;
/**
* <code>#{{@value SPEL_FILTER}.
* <code>#{{@value SPEL_FILTER}}.
* filter</code> will be replaced by an expression allowing to select only entries matching the entity in a WHERE clause.
*/
public final String filter;
public N1qlSpelValues(String selectClause, String entityFields, String bucket, String filter) {
/**
* <code>#{{@value SPEL_DELETE}}.
* delete</code> will be replaced by a delete expression.
*/
public final String delete;
/**
* <code>#{{@value SPEL_RETURNING}}.
* returning</code> will be replaced by a returning expression allowing to return the entity and meta information on deletes.
*/
public final String returning;
public N1qlSpelValues(String selectClause, String entityFields, String bucket, String filter, String delete, String returning) {
this.selectEntity = selectClause;
this.fields = entityFields;
this.bucket = bucket;
this.filter = filter;
this.delete = delete;
this.returning = returning;
}
}
}

View File

@@ -16,6 +16,13 @@
package org.springframework.data.couchbase.repository.query;
import static com.couchbase.client.java.query.Delete.deleteFrom;
import static com.couchbase.client.java.query.dsl.Expression.i;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.couchbase.client.java.document.json.JsonValue;
import com.couchbase.client.java.query.N1qlQuery;

View File

@@ -0,0 +1,198 @@
/*
* Copyright 2017 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.couchbase.repository.query.support;
import static com.couchbase.client.java.query.dsl.Expression.*;
import java.util.Collection;
import java.util.Iterator;
import com.couchbase.client.java.document.json.JsonArray;
import com.couchbase.client.java.query.dsl.Expression;
import com.couchbase.client.java.query.dsl.functions.PatternMatchingFunctions;
import com.couchbase.client.java.query.dsl.functions.StringFunctions;
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
import org.springframework.data.couchbase.repository.query.ConvertingIterator;
import org.springframework.data.mapping.context.PersistentPropertyPath;
import org.springframework.data.repository.query.parser.Part;
/**
* Utils for creating part tree expressions
*
* @author Subhashni Balakrishnan
*/
public class N1qlQueryCreatorUtils {
public static Expression prepareExpression(CouchbaseConverter converter, Part part, Iterator<Object> iterator) {
PersistentPropertyPath<CouchbasePersistentProperty> path = N1qlUtils.getPathWithAlternativeFieldNames(
converter, part.getProperty());
ConvertingIterator parameterValues = new ConvertingIterator(iterator, converter);
//get the whole doted path with fieldNames instead of potentially wrong propNames
String fieldNamePath = N1qlUtils.getDottedPathWithAlternativeFieldNames(path);
//deal with ignore case
boolean ignoreCase = false;
Class<?> leafType = converter.getWriteClassFor(path.getLeafProperty().getType());
boolean isString = leafType == String.class;
if (part.shouldIgnoreCase() == Part.IgnoreCaseType.WHEN_POSSIBLE) {
ignoreCase = isString;
} else if (part.shouldIgnoreCase() == Part.IgnoreCaseType.ALWAYS) {
if (!isString) {
throw new IllegalArgumentException(String.format("Part %s must be of type String but was %s", fieldNamePath, leafType));
}
ignoreCase = true;
}
return createExpression(part.getType(), fieldNamePath, ignoreCase, parameterValues);
}
public static Expression createExpression(Part.Type partType, String fieldNamePath, boolean ignoreCase, Iterator<Object> parameterValues) {
//create the left hand side of the expression, taking ignoreCase into account
Expression left = ignoreCase ? StringFunctions.lower(x(fieldNamePath)) : x(fieldNamePath);
switch (partType) {
case BETWEEN:
return left.between(leftAndRight(parameterValues, ignoreCase));
case IS_NOT_NULL:
return left.isNotNull();
case IS_NULL:
return left.isNull();
case NEGATING_SIMPLE_PROPERTY:
return left.ne(right(parameterValues, ignoreCase));
case SIMPLE_PROPERTY:
return left.eq(right(parameterValues, ignoreCase));
case BEFORE:
case LESS_THAN:
return left.lt(right(parameterValues, ignoreCase));
case LESS_THAN_EQUAL:
return left.lte(right(parameterValues, ignoreCase));
case GREATER_THAN_EQUAL:
return left.gte(right(parameterValues, ignoreCase));
case AFTER:
case GREATER_THAN:
return left.gt(right(parameterValues, ignoreCase));
case NOT_LIKE:
return left.notLike(right(parameterValues, ignoreCase));
case LIKE:
return left.like(right(parameterValues, ignoreCase));
case STARTING_WITH:
return left.like(like(parameterValues, ignoreCase, false, true));
case ENDING_WITH:
return left.like(like(parameterValues, ignoreCase, true, false));
case NOT_CONTAINING:
return left.notLike(like(parameterValues, ignoreCase, true, true));
case CONTAINING:
return left.like(like(parameterValues, ignoreCase, true, true));
case NOT_IN:
return left.notIn(rightArray(parameterValues));
case IN:
return left.in(rightArray(parameterValues));
case TRUE:
return left.eq(true);
case FALSE:
return left.eq(false);
case REGEX:
return regexp(fieldNamePath, parameterValues);
case EXISTS:
return left.isNotMissing();
case WITHIN:
case NEAR:
default:
throw new IllegalArgumentException("Unsupported keyword in N1QL query derivation");
}
}
protected static Expression regexp(String left, Iterator<Object> parameterValues) {
Object next = parameterValues.next();
String pattern;
if (next == null) {
pattern = "";
} else {
pattern = String.valueOf(next);
}
return PatternMatchingFunctions.regexpLike(left, pattern);
}
protected static Expression leftAndRight(Iterator<Object> parameterValues, boolean ignoreCase) {
return right(parameterValues, ignoreCase).and(right(parameterValues, ignoreCase));
}
protected static Expression like(Iterator<Object> parameterValues, boolean ignoreCase,
boolean anyPrefix, boolean anySuffix) {
Object next = parameterValues.next();
if (next == null) {
return Expression.NULL();
}
Expression converted;
if (next instanceof String) {
String pattern = (String) next;
if (anyPrefix) {
pattern = "%" + pattern;
}
if (anySuffix) {
pattern = pattern + "%";
}
converted = s(pattern);
} else {
converted = x(String.valueOf(next));
}
if (ignoreCase) {
return StringFunctions.lower(converted);
}
return converted;
}
protected static Expression right(Iterator<Object> parameterValues, boolean ignoreCase) {
Object next = parameterValues.next();
if (next == null) {
return Expression.NULL();
}
Expression converted;
if (next instanceof String) {
converted = s((String) next);
} else {
converted = x(String.valueOf(next));
}
if (ignoreCase) {
return StringFunctions.lower(converted);
}
return converted;
}
protected static JsonArray rightArray(Iterator<Object> parameterValues) {
Object next = parameterValues.next();
Object[] values;
if (next instanceof Collection) {
values = ((Collection<?>) next).toArray();
} else if (next.getClass().isArray()) {
values = (Object[]) next;
} else {
values = new Object[] {next};
}
return JsonArray.from(values);
}
}

View File

@@ -123,6 +123,32 @@ public class N1qlUtils {
return select(propertiesExp);
}
/**
* Creates the returning clause for N1ql deletes with all attributes of the entity and meta information
*
* @param bucketName the bucket that stores the entity documents (will be escaped).
* @return the needed returning clause of the statement.
*/
public static Expression createReturningExpressionForDelete(String bucketName) {
Expression fullEntity = path(i(bucketName), "*");
Expression metaId = path(meta(i(bucketName)), "id").as(SELECT_ID);
Expression metaCas = path(meta(i(bucketName)), "cas").as(SELECT_CAS);
List<Expression> expList = new ArrayList<Expression>();
expList.add(fullEntity);
expList.add(metaId);
expList.add(metaCas);
StringBuilder sb = new StringBuilder();
for (Expression exp: expList) {
if (sb.length() != 0) {
sb.append(", ");
}
sb.append(exp.toString());
}
return x(sb.toString());
}
/**
* Produce a {@link Statement} that corresponds to the SELECT clause for looking for Spring Data entities
* stored in Couchbase. Notably it will select the content of the document AND its id and cas.