DATACOUCH-603 Do not cast query parameters in N1qlQueryCreator.
For IN and NOT_IN - they can take varargs, an array or a JsonArray Don't cast query criteria parameters, let parameter accessor handle that. Fixed conversion of query criteria values to parameters Cleaned up QueryCriteria Co-authored-by: mikereiche <michael.reiche@couchbase.com>
This commit is contained in:
committed by
mikereiche
parent
87397023e8
commit
e4068b9a4e
@@ -26,6 +26,7 @@ import com.couchbase.client.java.json.JsonValue;
|
|||||||
import com.couchbase.client.java.query.QueryOptions;
|
import com.couchbase.client.java.query.QueryOptions;
|
||||||
import com.couchbase.client.java.query.QueryScanConsistency;
|
import com.couchbase.client.java.query.QueryScanConsistency;
|
||||||
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
import org.springframework.data.couchbase.core.ReactiveCouchbaseTemplate;
|
||||||
|
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentEntity;
|
||||||
import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser;
|
import org.springframework.data.couchbase.repository.query.StringBasedN1qlQueryParser;
|
||||||
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
|
import org.springframework.data.couchbase.repository.support.MappingCouchbaseEntityInformation;
|
||||||
@@ -189,7 +190,7 @@ public class Query {
|
|||||||
sb.deleteCharAt(sb.length() - 1);
|
sb.deleteCharAt(sb.length() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void appendWhere(final StringBuilder sb, int[] paramIndexPtr) {
|
public void appendWhere(final StringBuilder sb, int[] paramIndexPtr, CouchbaseConverter converter) {
|
||||||
if (!criteria.isEmpty()) {
|
if (!criteria.isEmpty()) {
|
||||||
appendWhereOrAnd(sb);
|
appendWhereOrAnd(sb);
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
@@ -199,16 +200,11 @@ public class Query {
|
|||||||
} else {
|
} else {
|
||||||
sb.append(" AND ");
|
sb.append(" AND ");
|
||||||
}
|
}
|
||||||
sb.append(c.export(paramIndexPtr));
|
sb.append(c.export(paramIndexPtr, parameters, converter));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void appendCriteria(StringBuilder sb, QueryCriteria criteria) {
|
|
||||||
appendWhereOrAnd(sb);
|
|
||||||
sb.append(criteria.export());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void appendWhereString(StringBuilder sb, String whereString) {
|
public void appendWhereString(StringBuilder sb, String whereString) {
|
||||||
appendWhereOrAnd(sb);
|
appendWhereOrAnd(sb);
|
||||||
sb.append(whereString);
|
sb.append(whereString);
|
||||||
@@ -257,9 +253,9 @@ public class Query {
|
|||||||
return true; // is not quoted
|
return true; // is not quoted
|
||||||
}
|
}
|
||||||
|
|
||||||
public String export() {
|
public String export(int[]... paramIndexPtrHolder) { // used only by tests
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
appendWhere(sb, null);
|
appendWhere(sb, paramIndexPtrHolder.length > 0 ? paramIndexPtrHolder[0] : null, null);
|
||||||
appendSort(sb);
|
appendSort(sb);
|
||||||
appendSkipAndLimit(sb);
|
appendSkipAndLimit(sb);
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
@@ -270,7 +266,7 @@ public class Query {
|
|||||||
final StringBuilder statement = new StringBuilder();
|
final StringBuilder statement = new StringBuilder();
|
||||||
appendString(statement, n1ql.selectEntity); // select ...
|
appendString(statement, n1ql.selectEntity); // select ...
|
||||||
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
|
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
|
||||||
appendWhere(statement, new int[] { 0 }); // criteria on this Query
|
appendWhere(statement, new int[] { 0 }, template.getConverter()); // criteria on this Query
|
||||||
appendSort(statement);
|
appendSort(statement);
|
||||||
appendSkipAndLimit(statement);
|
appendSkipAndLimit(statement);
|
||||||
return statement.toString();
|
return statement.toString();
|
||||||
@@ -281,7 +277,7 @@ public class Query {
|
|||||||
final StringBuilder statement = new StringBuilder();
|
final StringBuilder statement = new StringBuilder();
|
||||||
appendString(statement, n1ql.delete); // delete ...
|
appendString(statement, n1ql.delete); // delete ...
|
||||||
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
|
appendWhereString(statement, n1ql.filter); // typeKey = typeValue
|
||||||
appendWhere(statement, null); // criteria on this Query
|
appendWhere(statement, null, template.getConverter()); // criteria on this Query
|
||||||
appendString(statement, n1ql.returning);
|
appendString(statement, n1ql.returning);
|
||||||
return statement.toString();
|
return statement.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ import java.util.Formatter;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.couchbase.client.core.error.InvalidArgumentException;
|
||||||
|
import com.couchbase.client.java.json.JsonArray;
|
||||||
|
import com.couchbase.client.java.json.JsonObject;
|
||||||
|
import com.couchbase.client.java.json.JsonValue;
|
||||||
|
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,6 +65,10 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
this.format = format;
|
this.format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object[] getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static factory method to create a Criteria using the provided key.
|
* Static factory method to create a Criteria using the provided key.
|
||||||
*/
|
*/
|
||||||
@@ -68,8 +77,8 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static QueryCriteria wrap(QueryCriteria criteria) {
|
private static QueryCriteria wrap(QueryCriteria criteria) {
|
||||||
QueryCriteria qc = new QueryCriteria(new LinkedList<QueryCriteria>(), criteria.key, criteria.value, null,
|
QueryCriteria qc = new QueryCriteria(new LinkedList<>(), criteria.key, criteria.value, null, criteria.operator,
|
||||||
criteria.operator, criteria.format);
|
criteria.format);
|
||||||
return qc;
|
return qc;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +176,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
public QueryCriteria notContaining(@Nullable Object o) {
|
public QueryCriteria notContaining(@Nullable Object o) {
|
||||||
value = new QueryCriteria[] { wrap(containing(o)) };
|
value = new QueryCriteria[] { wrap(containing(o)) };
|
||||||
operator = "NOT";
|
operator = "NOT";
|
||||||
format = format = "not( %3$s )";
|
format = "not( %3$s )";
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +205,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
operator = "IS_NOT_NULL";
|
operator = "IS_NOT_NULL";
|
||||||
value = null;
|
value = null;
|
||||||
format = "%1$s is not null";
|
format = "%1$s is not null";
|
||||||
return (QueryCriteria) this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryCriteria isMissing() {
|
public QueryCriteria isMissing() {
|
||||||
@@ -210,7 +219,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
operator = "IS_NOT_MiSSING";
|
operator = "IS_NOT_MiSSING";
|
||||||
value = null;
|
value = null;
|
||||||
format = "%1$s is not missing";
|
format = "%1$s is not missing";
|
||||||
return (QueryCriteria) this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryCriteria isValued() {
|
public QueryCriteria isValued() {
|
||||||
@@ -224,68 +233,74 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
operator = "IS_NOT_VALUED";
|
operator = "IS_NOT_VALUED";
|
||||||
value = null;
|
value = null;
|
||||||
format = "%1$s is not valued";
|
format = "%1$s is not valued";
|
||||||
return (QueryCriteria) this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryCriteria within(@Nullable Object o) {
|
public QueryCriteria within(@Nullable Object o) {
|
||||||
operator = "WITHIN";
|
operator = "WITHIN";
|
||||||
value = new Object[] { o };
|
value = new Object[] { o };
|
||||||
format = "%1$s within $3$s";
|
format = "%1$s within %3$s";
|
||||||
return (QueryCriteria) this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryCriteria between(@Nullable Object o1, @Nullable Object o2) {
|
public QueryCriteria between(@Nullable Object o1, @Nullable Object o2) {
|
||||||
operator = "BETWEEN";
|
operator = "BETWEEN";
|
||||||
value = new Object[] { o1, o2 };
|
value = new Object[] { o1, o2 };
|
||||||
format = "%1$s between %3$s and %4$s";
|
format = "%1$s between %3$s and %4$s";
|
||||||
return (QueryCriteria) this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryCriteria in(@Nullable Object... o) {
|
public QueryCriteria in(@Nullable Object... o) {
|
||||||
operator = "IN";
|
operator = "IN";
|
||||||
value = o;
|
format = "%1$s in ( %3$s )";
|
||||||
StringBuilder sb = new StringBuilder("%1$s in ( [ ");
|
// IN takes a single argument that is a list
|
||||||
for (int i = 1; i <= value.length; i++) { // format indices start at 1
|
if (o.length > 0) {
|
||||||
if (i > 1)
|
if (o[0] instanceof JsonArray || o[0] instanceof List || o[0] instanceof Object[]) {
|
||||||
sb.append(", ");
|
if (o.length != 1) {
|
||||||
sb.append("%" + (i + 2) + "$s"); // the first is fieldName, second is operator, args start at 3
|
throw new RuntimeException("IN cannot take multiple lists");
|
||||||
|
}
|
||||||
|
value = o;
|
||||||
|
} else {
|
||||||
|
value = new Object[1];
|
||||||
|
value[0] = o; // JsonArray.from(o);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
format = sb.append(" ] )").toString();
|
return this;
|
||||||
return (QueryCriteria) this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryCriteria notIn(@Nullable Object... o) {
|
public QueryCriteria notIn(@Nullable Object... o) {
|
||||||
value = new QueryCriteria[] { wrap(in(o)) };
|
value = new QueryCriteria[] { wrap(in(o)) };
|
||||||
operator = "NOT";
|
operator = "NOT";
|
||||||
format = format = "not( %3$s )"; // field = 1$, operator = 2$, value=$3, $4, ...
|
format = "not( %3$s )"; // field = 1$, operator = 2$, value=$3, $4, ...
|
||||||
return (QueryCriteria) this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryCriteria TRUE() { // true/false are reserved, use TRUE/FALSE
|
public QueryCriteria TRUE() { // true/false are reserved, use TRUE/FALSE
|
||||||
value = null;
|
value = null;
|
||||||
operator = null;
|
operator = null;
|
||||||
format = format = "%1$s"; // field = 1$, operator = 2$, value=$3, $4, ...
|
format = "%1$s"; // field = 1$, operator = 2$, value=$3, $4, ...
|
||||||
return (QueryCriteria) this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryCriteria FALSE() {
|
public QueryCriteria FALSE() {
|
||||||
value = new QueryCriteria[] { wrap(TRUE()) };
|
value = new QueryCriteria[] { wrap(TRUE()) };
|
||||||
operator = "not";
|
operator = "not";
|
||||||
format = format = "not( %3$s )";
|
format = "not( %3$s )";
|
||||||
return (QueryCriteria) this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This exports the query criteria into a string to be appended to the beginning of an N1QL statement
|
* This exports the query criteria chain into a string to be appended to the beginning of an N1QL statement
|
||||||
*
|
*
|
||||||
* @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters
|
* @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters There may
|
||||||
* There may already be positional parameters in the beginning of the statement,
|
* already be positional parameters in the beginning of the statement, so it may not always start at 1. If it
|
||||||
* so it may not always start at 1. If it has the value -1, the query is using
|
* has the value -1, the query is using named parameters. If the pointer is null, the query is not using
|
||||||
* named parameters. If the pointer is null, the query is not using parameters.
|
* parameters.
|
||||||
|
* @param parameters - parameters of the query. If operands are parameterized, their values are added to parameters
|
||||||
* @return string containing part of N1QL query
|
* @return string containing part of N1QL query
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String export(int[] paramIndexPtr) {
|
public String export(int[] paramIndexPtr, JsonValue parameters, CouchbaseConverter converter) {
|
||||||
StringBuilder output = new StringBuilder();
|
StringBuilder output = new StringBuilder();
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
for (QueryCriteria c : this.criteriaChain) {
|
for (QueryCriteria c : this.criteriaChain) {
|
||||||
@@ -298,7 +313,7 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
} else {
|
} else {
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
c.exportSingle(output, paramIndexPtr);
|
c.exportSingle(output, paramIndexPtr, parameters, converter);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.toString();
|
return output.toString();
|
||||||
@@ -310,12 +325,24 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
* @return string containing part of N1QL query
|
* @return string containing part of N1QL query
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String export() {
|
public String export() { // used only by tests
|
||||||
return export(null);
|
return export(null, null, null);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private StringBuilder exportSingle(StringBuilder sb, int[] paramIndexPtr) {
|
/**
|
||||||
|
* Appends the query criteria to a StringBuilder which will be appended to a N1QL statement
|
||||||
|
*
|
||||||
|
* @param sb - the string builder
|
||||||
|
* @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters There may
|
||||||
|
* already be positional parameters in the beginning of the statement, so it may not always start at 1. If it
|
||||||
|
* has the value -1, the query is using named parameters. If the pointer is null, the query is not using
|
||||||
|
* parameters.
|
||||||
|
* @param parameters - parameters of the query. If operands are parameterized, their values are added to parameters
|
||||||
|
* @return string containing part of N1QL query
|
||||||
|
*/
|
||||||
|
private StringBuilder exportSingle(StringBuilder sb, int[] paramIndexPtr, JsonValue parameters,
|
||||||
|
CouchbaseConverter converter) {
|
||||||
String fieldName = maybeQuote(key);
|
String fieldName = maybeQuote(key);
|
||||||
int valueLen = value == null ? 0 : value.length;
|
int valueLen = value == null ? 0 : value.length;
|
||||||
Object[] v = new Object[valueLen + 2];
|
Object[] v = new Object[valueLen + 2];
|
||||||
@@ -323,9 +350,9 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
v[1] = operator;
|
v[1] = operator;
|
||||||
for (int i = 0; i < valueLen; i++) {
|
for (int i = 0; i < valueLen; i++) {
|
||||||
if (value[i] instanceof QueryCriteria) {
|
if (value[i] instanceof QueryCriteria) {
|
||||||
v[i + 2] = "(" + ((QueryCriteria) value[i]).export(paramIndexPtr) + ")";
|
v[i + 2] = "(" + ((QueryCriteria) value[i]).export(paramIndexPtr, parameters, converter) + ")";
|
||||||
} else {
|
} else {
|
||||||
v[i + 2] = maybeWrapValue(key, value[i], paramIndexPtr);
|
v[i + 2] = maybeWrapValue(key, value[i], paramIndexPtr, parameters, converter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -340,24 +367,84 @@ public class QueryCriteria implements QueryCriteriaDefinition {
|
|||||||
return sb;
|
return sb;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String maybeWrapValue(String key, Object value, int[] paramIndexPtr) {
|
/**
|
||||||
|
* Possibly convert an operand to a positional or named parameter
|
||||||
|
*
|
||||||
|
* @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters There may
|
||||||
|
* already be positional parameters in the beginning of the statement, so it may not always start at 1. If it
|
||||||
|
* has the value -1, the query is using named parameters. If the pointer is null, the query is not using
|
||||||
|
* parameters.
|
||||||
|
* @param parameters - parameters of the query. If operands are parameterized, their values are added to parameters
|
||||||
|
* @return string containing part of N1QL query
|
||||||
|
*/
|
||||||
|
private String maybeWrapValue(String key, Object value, int[] paramIndexPtr, JsonValue parameters,
|
||||||
|
CouchbaseConverter converter) {
|
||||||
if (paramIndexPtr != null) {
|
if (paramIndexPtr != null) {
|
||||||
if (paramIndexPtr[0] >= 0) {
|
if (paramIndexPtr[0] >= 0) {
|
||||||
|
JsonArray params = (JsonArray) parameters;
|
||||||
|
// from StringBasedN1qlQueryParser.getPositionalPlaceholderValues()
|
||||||
|
try {
|
||||||
|
params.add(convert(converter, value));
|
||||||
|
} catch (InvalidArgumentException iae) {
|
||||||
|
if (value instanceof Object[]) {
|
||||||
|
addAsArray(params, value, converter);
|
||||||
|
} else {
|
||||||
|
throw iae;
|
||||||
|
}
|
||||||
|
}
|
||||||
return "$" + (++paramIndexPtr[0]); // these are generated in order
|
return "$" + (++paramIndexPtr[0]); // these are generated in order
|
||||||
} else {
|
} else {
|
||||||
|
JsonObject params = (JsonObject) parameters;
|
||||||
|
// from StringBasedN1qlQueryParser.getNamedPlaceholderValues()
|
||||||
|
try {
|
||||||
|
params.put(key, convert(converter, value));
|
||||||
|
} catch (InvalidArgumentException iae) {
|
||||||
|
if (value instanceof Object[]) {
|
||||||
|
params.put(key, JsonArray.from((Object[]) value));
|
||||||
|
} else {
|
||||||
|
throw iae;
|
||||||
|
}
|
||||||
|
}
|
||||||
return "$" + key;
|
return "$" + key;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Did not convert to a parameter. Add quotes or whatever it might need.
|
||||||
|
|
||||||
if (value instanceof String) {
|
if (value instanceof String) {
|
||||||
return "\"" + value + "\"";
|
return "\"" + value + "\"";
|
||||||
} else if (value == null) {
|
} else if (value == null) {
|
||||||
return "null";
|
return "null";
|
||||||
|
} else if (value instanceof Object[]) { // convert array into sequence of comma-separated values
|
||||||
|
StringBuffer l = new StringBuffer();
|
||||||
|
l.append("[");
|
||||||
|
Object[] array = (Object[]) value;
|
||||||
|
for (int i = 0; i < array.length; i++) {
|
||||||
|
if (i > 0) {
|
||||||
|
l.append(",");
|
||||||
|
}
|
||||||
|
l.append(maybeWrapValue(null, array[i], null, null, converter));
|
||||||
|
}
|
||||||
|
l.append("]");
|
||||||
|
return l.toString();
|
||||||
} else {
|
} else {
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Object convert(CouchbaseConverter converter, Object value) {
|
||||||
|
return converter != null ? converter.convertForWriteIfNeeded(value) : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAsArray(JsonArray posValues, Object o, CouchbaseConverter converter) {
|
||||||
|
Object[] array = (Object[]) o;
|
||||||
|
JsonArray ja = JsonValue.ja();
|
||||||
|
for (Object e : array) {
|
||||||
|
ja.add(String.valueOf(convert(converter, e)));
|
||||||
|
}
|
||||||
|
posValues.add(ja);
|
||||||
|
}
|
||||||
|
|
||||||
private String maybeQuote(String value) {
|
private String maybeQuote(String value) {
|
||||||
if (value == null || (value.startsWith("\"") && value.endsWith("\""))) {
|
if (value == null || (value.startsWith("\"") && value.endsWith("\""))) {
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.couchbase.core.query;
|
package org.springframework.data.couchbase.core.query;
|
||||||
|
|
||||||
|
import com.couchbase.client.java.json.JsonValue;
|
||||||
|
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Oliver Gierke
|
* @author Oliver Gierke
|
||||||
* @author Christoph Strobl
|
* @author Christoph Strobl
|
||||||
@@ -25,13 +28,15 @@ public interface QueryCriteriaDefinition {
|
|||||||
/**
|
/**
|
||||||
* This exports the query criteria into a string to be appended to the beginning of an N1QL statement
|
* This exports the query criteria into a string to be appended to the beginning of an N1QL statement
|
||||||
*
|
*
|
||||||
* @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters
|
* @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters There may
|
||||||
* There may already be positional parameters in the beginning of the statement,
|
* already be positional parameters in the beginning of the statement, so it may not always start at 1. If it
|
||||||
* so it may not always start at 1. If it has the value -1, the query is using
|
* has the value -1, the query is using named parameters. If the pointer is null, the query is not using
|
||||||
* named parameters. If the pointer is null, the query is not using parameters.
|
* parameters.
|
||||||
|
* @param parameters - query parameters. Criteria values that are converted to arguments are added to parameters
|
||||||
|
* @param converter - converter to use for converting criteria values
|
||||||
* @return string containing part of N1QL query
|
* @return string containing part of N1QL query
|
||||||
*/
|
*/
|
||||||
String export(int[] paramIndexPtr);
|
String export(int[] paramIndexPtr, JsonValue parameters, CouchbaseConverter converter);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export the query criteria to a string without using positional or named parameters.
|
* Export the query criteria to a string without using positional or named parameters.
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ public class StringQuery extends Query {
|
|||||||
} else { // named parameters or no parameters, no index required
|
} else { // named parameters or no parameters, no index required
|
||||||
paramIndexPtr = new int[] { -1 };
|
paramIndexPtr = new int[] { -1 };
|
||||||
}
|
}
|
||||||
appendWhere(statement, paramIndexPtr); // criteria on this Query - should be empty for StringQuery
|
appendWhere(statement, paramIndexPtr, template.getConverter()); // criteria on this Query - should be empty for
|
||||||
|
// StringQuery
|
||||||
appendSort(statement);
|
appendSort(statement);
|
||||||
appendSkipAndLimit(statement);
|
appendSkipAndLimit(statement);
|
||||||
return statement.toString();
|
return statement.toString();
|
||||||
|
|||||||
@@ -17,12 +17,8 @@ package org.springframework.data.couchbase.repository.query;
|
|||||||
|
|
||||||
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
|
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
|
||||||
import com.couchbase.client.core.error.InvalidArgumentException;
|
|
||||||
import com.couchbase.client.java.json.JsonArray;
|
|
||||||
import com.couchbase.client.java.json.JsonValue;
|
|
||||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||||
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
import org.springframework.data.couchbase.core.mapping.CouchbasePersistentProperty;
|
||||||
import org.springframework.data.couchbase.core.query.Query;
|
import org.springframework.data.couchbase.core.query.Query;
|
||||||
@@ -30,7 +26,6 @@ import org.springframework.data.couchbase.core.query.QueryCriteria;
|
|||||||
import org.springframework.data.domain.Sort;
|
import org.springframework.data.domain.Sort;
|
||||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||||
import org.springframework.data.mapping.context.MappingContext;
|
import org.springframework.data.mapping.context.MappingContext;
|
||||||
import org.springframework.data.repository.query.Parameter;
|
|
||||||
import org.springframework.data.repository.query.ParameterAccessor;
|
import org.springframework.data.repository.query.ParameterAccessor;
|
||||||
import org.springframework.data.repository.query.QueryMethod;
|
import org.springframework.data.repository.query.QueryMethod;
|
||||||
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
import org.springframework.data.repository.query.parser.AbstractQueryCreator;
|
||||||
@@ -59,8 +54,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected QueryCriteria create(final Part part, final Iterator<Object> iterator) {
|
protected QueryCriteria create(final Part part, final Iterator<Object> iterator) {
|
||||||
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(
|
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||||
part.getProperty());
|
|
||||||
CouchbasePersistentProperty property = path.getLeafProperty();
|
CouchbasePersistentProperty property = path.getLeafProperty();
|
||||||
return from(part, property, where(path.toDotPath()), iterator);
|
return from(part, property, where(path.toDotPath()), iterator);
|
||||||
}
|
}
|
||||||
@@ -71,8 +65,7 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
|
|||||||
return create(part, iterator);
|
return create(part, iterator);
|
||||||
}
|
}
|
||||||
|
|
||||||
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(
|
PersistentPropertyPath<CouchbasePersistentProperty> path = context.getPersistentPropertyPath(part.getProperty());
|
||||||
part.getProperty());
|
|
||||||
CouchbasePersistentProperty property = path.getLeafProperty();
|
CouchbasePersistentProperty property = path.getLeafProperty();
|
||||||
|
|
||||||
return from(part, property, base.and(path.toDotPath()), iterator);
|
return from(part, property, base.and(path.toDotPath()), iterator);
|
||||||
@@ -85,92 +78,70 @@ public class N1qlQueryCreator extends AbstractQueryCreator<Query, QueryCriteria>
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Query complete(QueryCriteria criteria, Sort sort) {
|
protected Query complete(QueryCriteria criteria, Sort sort) {
|
||||||
JsonArray params = (JsonArray) getPositionalPlaceholderValues(accessor);
|
return (criteria == null ? new Query() : new Query().addCriteria(criteria)).with(sort);
|
||||||
return (criteria == null ? new Query() : new Query().addCriteria(criteria)).with(sort).setPositionalParameters(
|
|
||||||
params);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private QueryCriteria from(final Part part, final CouchbasePersistentProperty property,
|
private QueryCriteria from(final Part part, final CouchbasePersistentProperty property, final QueryCriteria criteria,
|
||||||
final QueryCriteria criteria, final Iterator<Object> parameters) {
|
final Iterator<Object> parameters) {
|
||||||
|
|
||||||
final Part.Type type = part.getType();
|
final Part.Type type = part.getType();
|
||||||
/*
|
/*
|
||||||
NEAR(new String[]{"IsNear", "Near"}),
|
NEAR(new String[]{"IsNear", "Near"}),
|
||||||
*/
|
*/
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case GREATER_THAN:
|
case GREATER_THAN:
|
||||||
case AFTER:
|
case AFTER:
|
||||||
return criteria.gt(parameters.next());
|
return criteria.gt(parameters.next());
|
||||||
case GREATER_THAN_EQUAL:
|
case GREATER_THAN_EQUAL:
|
||||||
return criteria.gte(parameters.next());
|
return criteria.gte(parameters.next());
|
||||||
case LESS_THAN:
|
case LESS_THAN:
|
||||||
case BEFORE:
|
case BEFORE:
|
||||||
return criteria.lt(parameters.next());
|
return criteria.lt(parameters.next());
|
||||||
case LESS_THAN_EQUAL:
|
case LESS_THAN_EQUAL:
|
||||||
return criteria.lte(parameters.next());
|
return criteria.lte(parameters.next());
|
||||||
case SIMPLE_PROPERTY:
|
case SIMPLE_PROPERTY:
|
||||||
return criteria.eq(parameters.next());
|
return criteria.eq(parameters.next());
|
||||||
case NEGATING_SIMPLE_PROPERTY:
|
case NEGATING_SIMPLE_PROPERTY:
|
||||||
return criteria.ne(parameters.next());
|
return criteria.ne(parameters.next());
|
||||||
case CONTAINING:
|
case CONTAINING:
|
||||||
return criteria.containing(parameters.next());
|
return criteria.containing(parameters.next());
|
||||||
case NOT_CONTAINING:
|
case NOT_CONTAINING:
|
||||||
return criteria.notContaining(parameters.next());
|
return criteria.notContaining(parameters.next());
|
||||||
case STARTING_WITH:
|
case STARTING_WITH:
|
||||||
return criteria.startingWith(parameters.next());
|
return criteria.startingWith(parameters.next());
|
||||||
case ENDING_WITH:
|
case ENDING_WITH:
|
||||||
return criteria.endingWith(parameters.next());
|
return criteria.endingWith(parameters.next());
|
||||||
case LIKE:
|
case LIKE:
|
||||||
return criteria.like(parameters.next());
|
return criteria.like(parameters.next());
|
||||||
case NOT_LIKE:
|
case NOT_LIKE:
|
||||||
return criteria.notLike(parameters.next());
|
return criteria.notLike(parameters.next());
|
||||||
case WITHIN:
|
case WITHIN:
|
||||||
return criteria.within(parameters.next());
|
return criteria.within(parameters.next());
|
||||||
case IS_NULL:
|
case IS_NULL:
|
||||||
return criteria.isNull(/*parameters.next()*/);
|
return criteria.isNull(/*parameters.next()*/);
|
||||||
case IS_NOT_NULL:
|
case IS_NOT_NULL:
|
||||||
return criteria.isNotNull(/*parameters.next()*/);
|
return criteria.isNotNull(/*parameters.next()*/);
|
||||||
case IS_EMPTY:
|
case IS_EMPTY:
|
||||||
return criteria.isNotValued(/*parameters.next()*/);
|
return criteria.isNotValued(/*parameters.next()*/);
|
||||||
case IS_NOT_EMPTY:
|
case IS_NOT_EMPTY:
|
||||||
return criteria.isValued(/*parameters.next()*/);
|
return criteria.isValued(/*parameters.next()*/);
|
||||||
case EXISTS:
|
case EXISTS:
|
||||||
return criteria.isNotMissing(/*parameters.next()*/);
|
return criteria.isNotMissing(/*parameters.next()*/);
|
||||||
case REGEX:
|
case REGEX:
|
||||||
return criteria.regex(parameters.next());
|
return criteria.regex(parameters.next());
|
||||||
case BETWEEN:
|
case BETWEEN:
|
||||||
return criteria.between(parameters.next(), parameters.next());
|
return criteria.between(parameters.next(), parameters.next());
|
||||||
case IN:
|
case IN:
|
||||||
return criteria.in((Object[]) parameters.next());
|
return criteria.in(parameters.next());
|
||||||
case NOT_IN:
|
case NOT_IN:
|
||||||
return criteria.notIn((Object[]) parameters.next());
|
return criteria.notIn(parameters.next());
|
||||||
case TRUE:
|
case TRUE:
|
||||||
return criteria.TRUE();
|
return criteria.TRUE();
|
||||||
case FALSE:
|
case FALSE:
|
||||||
return criteria.FALSE();
|
return criteria.FALSE();
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported keyword!");
|
throw new IllegalArgumentException("Unsupported keyword!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from StringN1qlQueryParser
|
|
||||||
private JsonValue getPositionalPlaceholderValues(ParameterAccessor accessor) {
|
|
||||||
JsonArray posValues = JsonArray.create();
|
|
||||||
if (queryMethod == null)
|
|
||||||
return posValues;
|
|
||||||
for (Parameter parameter : this.queryMethod.getParameters().getBindableParameters()) {
|
|
||||||
try {
|
|
||||||
posValues.add(converter.convertForWriteIfNeeded(accessor.getBindableValue(parameter.getIndex())));
|
|
||||||
} catch (InvalidArgumentException iae) {
|
|
||||||
Object o = accessor.getBindableValue(parameter.getIndex());
|
|
||||||
if (o instanceof Object[]) {
|
|
||||||
Object[] array = (Object[]) o;
|
|
||||||
for (Object e : array) {
|
|
||||||
posValues.add(converter.convertForWriteIfNeeded(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return posValues;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,14 +189,14 @@ public class StringBasedN1qlQueryParser {
|
|||||||
if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes)) {
|
if (checkNotQuoted(placeholder, namedMatcher.start(), namedMatcher.end(), quotes)) {
|
||||||
LOGGER.trace("{}: Found named placeholder {}", this.queryMethod.getName(), placeholder);
|
LOGGER.trace("{}: Found named placeholder {}", this.queryMethod.getName(), placeholder);
|
||||||
namedCount++;
|
namedCount++;
|
||||||
parameterNames.add(placeholder.substring(1));//save without the leading $
|
parameterNames.add(placeholder.substring(1));// save without the leading $
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (posCount > 0 && namedCount > 0) { // actual values from parameterNames might be more useful
|
if (posCount > 0 && namedCount > 0) { // actual values from parameterNames might be more useful
|
||||||
throw new IllegalArgumentException("Using both named (" + namedCount + ") and positional (" + posCount
|
throw new IllegalArgumentException("Using both named (" + namedCount + ") and positional (" + posCount
|
||||||
+ ") placeholders is not supported, please choose one over the other in "
|
+ ") placeholders is not supported, please choose one over the other in " + queryMethod.getClass().getName()
|
||||||
+ queryMethod.getClass().getName() + "." + this.queryMethod.getName() + "()");
|
+ "." + this.queryMethod.getName() + "()");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (posCount > 0) {
|
if (posCount > 0) {
|
||||||
@@ -211,8 +211,7 @@ public class StringBasedN1qlQueryParser {
|
|||||||
private boolean checkNotQuoted(String item, int start, int end, List<int[]> quotes) {
|
private boolean checkNotQuoted(String item, int start, int end, List<int[]> quotes) {
|
||||||
for (int[] quote : quotes) {
|
for (int[] quote : quotes) {
|
||||||
if (quote[0] <= start && quote[1] >= end) {
|
if (quote[0] <= start && quote[1] >= end) {
|
||||||
LOGGER.trace("{}: potential placeholder {} is inside quotes, ignored", this.queryMethod.getName(),
|
LOGGER.trace("{}: potential placeholder {} is inside quotes, ignored", this.queryMethod.getName(), item);
|
||||||
item);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,7 +219,7 @@ public class StringBasedN1qlQueryParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Postional argument placeholders to use for parameters. $1, $2 etc.
|
* Get Postional argument placeholders to use for parameters. $1, $2 etc.
|
||||||
*
|
*
|
||||||
* @param accessor
|
* @param accessor
|
||||||
* @return - JsonValue holding parameters.
|
* @return - JsonValue holding parameters.
|
||||||
@@ -230,7 +229,7 @@ public class StringBasedN1qlQueryParser {
|
|||||||
for (Parameter parameter : this.queryMethod.getParameters().getBindableParameters()) {
|
for (Parameter parameter : this.queryMethod.getParameters().getBindableParameters()) {
|
||||||
Object rawValue = accessor.getBindableValue(parameter.getIndex());
|
Object rawValue = accessor.getBindableValue(parameter.getIndex());
|
||||||
Object value = couchbaseConverter.convertForWriteIfNeeded(rawValue);
|
Object value = couchbaseConverter.convertForWriteIfNeeded(rawValue);
|
||||||
putPositionalValue(accessor, parameter, posValues, value);
|
putPositionalValue(posValues, value);
|
||||||
}
|
}
|
||||||
return posValues;
|
return posValues;
|
||||||
}
|
}
|
||||||
@@ -251,22 +250,20 @@ public class StringBasedN1qlQueryParser {
|
|||||||
|
|
||||||
if (placeholder != null && placeholder.charAt(0) == ':') {
|
if (placeholder != null && placeholder.charAt(0) == ':') {
|
||||||
placeholder = placeholder.replaceFirst(":", "");
|
placeholder = placeholder.replaceFirst(":", "");
|
||||||
putNamedValue(accessor, parameter, namedValues, placeholder, value);
|
putNamedValue(namedValues, placeholder, value);
|
||||||
if (pNames.contains(placeholder)) {
|
if (pNames.contains(placeholder)) {
|
||||||
pNames.remove(placeholder);
|
pNames.remove(placeholder);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException("parameter named " + placeholder + " does not match any named parameter "
|
||||||
"parameter named " + placeholder + " does not match any named parameter " + parameterNames
|
+ parameterNames + " in " + statement);
|
||||||
+ " in " + statement);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (parameter.getName().isPresent()) {
|
if (parameter.getName().isPresent()) {
|
||||||
putNamedValue(accessor, parameter, namedValues, parameter.getName().get(), value);
|
putNamedValue(namedValues, parameter.getName().get(), value);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException(
|
throw new RuntimeException("cannot determine argument for named parameter. " + "Argument "
|
||||||
"cannot determine argument for named parameter. " + "Argument " + parameter.getIndex()
|
+ parameter.getIndex() + " to " + queryMethod.getClass().getName() + "." + queryMethod.getName()
|
||||||
+ " to " + queryMethod.getClass().getName() + "." + queryMethod.getName()
|
+ "() needs @Param(\"name\") that matches a named parameter in " + statement);
|
||||||
+ "() needs @Param(\"name\") that matches a named parameter in " + statement);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,18 +275,17 @@ public class StringBasedN1qlQueryParser {
|
|||||||
|
|
||||||
protected JsonValue getPlaceholderValues(ParameterAccessor accessor) {
|
protected JsonValue getPlaceholderValues(ParameterAccessor accessor) {
|
||||||
switch (this.placeHolderType) {
|
switch (this.placeHolderType) {
|
||||||
case NAMED:
|
case NAMED:
|
||||||
return getNamedPlaceholderValues(accessor);
|
return getNamedPlaceholderValues(accessor);
|
||||||
case POSITIONAL:
|
case POSITIONAL:
|
||||||
return getPositionalPlaceholderValues(accessor);
|
return getPositionalPlaceholderValues(accessor);
|
||||||
case NONE:
|
case NONE:
|
||||||
default:
|
default:
|
||||||
return JsonArray.create();
|
return JsonArray.create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putPositionalValue(ParameterAccessor accessor, Parameter parameter, JsonArray posValues,
|
private void putPositionalValue(JsonArray posValues, Object value) {
|
||||||
Object value) {
|
|
||||||
try {
|
try {
|
||||||
posValues.add(value);
|
posValues.add(value);
|
||||||
} catch (InvalidArgumentException iae) {
|
} catch (InvalidArgumentException iae) {
|
||||||
@@ -310,8 +306,7 @@ public class StringBasedN1qlQueryParser {
|
|||||||
posValues.add(ja);
|
posValues.add(ja);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putNamedValue(ParameterAccessor accessor, Parameter parameter, JsonObject namedValues,
|
private void putNamedValue(JsonObject namedValues, String placeholder, Object value) {
|
||||||
String placeholder, Object value) {
|
|
||||||
try {
|
try {
|
||||||
namedValues.put(placeholder, value);
|
namedValues.put(placeholder, value);
|
||||||
} catch (InvalidArgumentException iae) {
|
} catch (InvalidArgumentException iae) {
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ public class StringN1qlQueryCreator extends AbstractQueryCreator<Query, QueryCri
|
|||||||
final Part.Type type = part.getType();
|
final Part.Type type = part.getType();
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case SIMPLE_PROPERTY:
|
case SIMPLE_PROPERTY:
|
||||||
return criteria; //.eq(parameters.next()); // this will be the dummy from PartTree
|
return criteria; // this will be the dummy from PartTree
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Unsupported keyword!");
|
throw new IllegalArgumentException("Unsupported keyword!");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,18 @@ package org.springframework.data.couchbase.core.query;
|
|||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
|
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
|
||||||
|
|
||||||
|
import com.couchbase.client.java.json.JsonArray;
|
||||||
|
import com.couchbase.client.java.json.JsonObject;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.data.couchbase.domain.User;
|
||||||
|
import org.springframework.data.couchbase.domain.UserRepository;
|
||||||
|
import org.springframework.data.couchbase.repository.query.N1qlQueryCreator;
|
||||||
|
import org.springframework.data.repository.query.parser.PartTree;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
class QueryCriteriaTests {
|
class QueryCriteriaTests {
|
||||||
|
|
||||||
@@ -56,21 +67,25 @@ class QueryCriteriaTests {
|
|||||||
@Test
|
@Test
|
||||||
void testNestedAndCriteria() {
|
void testNestedAndCriteria() {
|
||||||
QueryCriteria c = where("name").is("Bubba").and(where("age").gt(12).or("country").is("Austria"));
|
QueryCriteria c = where("name").is("Bubba").and(where("age").gt(12).or("country").is("Austria"));
|
||||||
assertEquals("`name` = \"Bubba\" and (`age` > 12 or `country` = \"Austria\")", c.export());
|
JsonArray parameters = JsonArray.create();
|
||||||
|
assertEquals("`name` = $1 and (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
|
||||||
|
assertEquals("[\"Bubba\",12,\"Austria\"]", parameters.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNestedOrCriteria() {
|
void testNestedOrCriteria() {
|
||||||
QueryCriteria c = where("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria"));
|
QueryCriteria c = where("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria"));
|
||||||
assertEquals("`name` = \"Bubba\" or (`age` > 12 or `country` = \"Austria\")", c.export());
|
JsonArray parameters = JsonArray.create();
|
||||||
|
assertEquals("`name` = $1 or (`age` > $2 or `country` = $3)", c.export(new int[1], parameters, null));
|
||||||
|
assertEquals("[\"Bubba\",12,\"Austria\"]", parameters.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNestedNotIn() {
|
void testNestedNotIn() {
|
||||||
QueryCriteria c = where("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria")).and(
|
QueryCriteria c = where("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria"))
|
||||||
where("state").notIn(new String[] { "Alabama", "Florida" }));
|
.and(where("state").notIn(new String[] { "Alabama", "Florida" }));
|
||||||
assertEquals("`name` = \"Bubba\" or (`age` > 12 or `country` = \"Austria\") and "
|
assertEquals("`name` = \"Bubba\" or (`age` > 12 or `country` = \"Austria\") and "
|
||||||
+ "(not( (`state` in ( [ \"Alabama\", \"Florida\" ] )) ))", c.export());
|
+ "(not( (`state` in ( [\"Alabama\",\"Florida\"] )) ))", c.export());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -110,13 +125,13 @@ class QueryCriteriaTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* cannot do this properly yet because in arg to when() in
|
/* cannot do this properly yet because in arg to when() in
|
||||||
* startingWith() cannot be a QueryCriteria
|
* startingWith() cannot be a QueryCriteria
|
||||||
@Test
|
@Test
|
||||||
void testStartingWithExpr() {
|
void testStartingWithExpr() {
|
||||||
QueryCriteria c = where("name").startingWith(where("name").plus("xxx"));
|
QueryCriteria c = where("name").startingWith(where("name").plus("xxx"));
|
||||||
assertEquals("`name` like (((`name` || "xxx") || ""%""))", c.export());
|
assertEquals("`name` like (((`name` || "xxx") || ""%""))", c.export());
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testEndingWith() {
|
void testEndingWith() {
|
||||||
@@ -204,14 +219,22 @@ class QueryCriteriaTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testIn() {
|
void testIn() {
|
||||||
QueryCriteria c = where("name").in(new String[] { "gump", "davis" });
|
String[] args = new String[] { "gump", "davis" };
|
||||||
assertEquals("`name` in ( [ \"gump\", \"davis\" ] )", c.export());
|
QueryCriteria c = where("name").in(args);
|
||||||
|
assertEquals("`name` in ( [\"gump\",\"davis\"] )", c.export());
|
||||||
|
JsonArray parameters = JsonArray.create();
|
||||||
|
assertEquals("`name` in ( $1 )", c.export(new int[1], parameters, null));
|
||||||
|
assertEquals(arrayToString(args), parameters.get(0).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testNotIn() {
|
void testNotIn() {
|
||||||
QueryCriteria c = where("name").notIn(new String[] { "gump", "davis" });
|
String[] args = new String[] { "gump", "davis" };
|
||||||
assertEquals("not( (`name` in ( [ \"gump\", \"davis\" ] )) )", c.export());
|
QueryCriteria c = where("name").notIn(args);
|
||||||
|
assertEquals("not( (`name` in ( [\"gump\",\"davis\"] )) )", c.export());
|
||||||
|
JsonArray parameters = JsonArray.create();
|
||||||
|
assertEquals("not( (`name` in ( $1 )) )", c.export(new int[1], parameters, null));
|
||||||
|
assertEquals(arrayToString(args), parameters.get(0).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -225,4 +248,28 @@ class QueryCriteriaTests {
|
|||||||
QueryCriteria c = where("name").FALSE();
|
QueryCriteria c = where("name").FALSE();
|
||||||
assertEquals("not( (`name`) )", c.export());
|
assertEquals("not( (`name`) )", c.export());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String arrayToString(Object[] array) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
if (array != null) {
|
||||||
|
sb.append("[");
|
||||||
|
boolean first = true;
|
||||||
|
for (Object e : array) {
|
||||||
|
if (!first) {
|
||||||
|
sb.append(",");
|
||||||
|
}
|
||||||
|
first = false;
|
||||||
|
if (e instanceof Number)
|
||||||
|
sb.append(e);
|
||||||
|
else {
|
||||||
|
sb.append("\"");
|
||||||
|
sb.append(e);
|
||||||
|
sb.append("\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
}
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ package org.springframework.data.couchbase.domain;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.couchbase.client.java.json.JsonArray;
|
||||||
import org.springframework.data.couchbase.repository.Query;
|
import org.springframework.data.couchbase.repository.Query;
|
||||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
import org.springframework.data.repository.PagingAndSortingRepository;
|
||||||
import org.springframework.data.repository.query.Param;
|
import org.springframework.data.repository.query.Param;
|
||||||
@@ -34,6 +35,10 @@ public interface UserRepository extends PagingAndSortingRepository<User, String>
|
|||||||
|
|
||||||
List<User> findByFirstname(String firstname);
|
List<User> findByFirstname(String firstname);
|
||||||
|
|
||||||
|
List<User> findByFirstnameIn(String... firstnames);
|
||||||
|
|
||||||
|
List<User> findByFirstnameIn(JsonArray firstnames);
|
||||||
|
|
||||||
List<User> findByFirstnameAndLastname(String firstname, String lastname);
|
List<User> findByFirstnameAndLastname(String firstname, String lastname);
|
||||||
|
|
||||||
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
|
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
|
||||||
|
|||||||
@@ -19,7 +19,13 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
|
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.couchbase.client.java.json.JsonArray;
|
||||||
|
import com.couchbase.client.java.json.JsonObject;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
|
||||||
@@ -62,6 +68,69 @@ class N1qlQueryCreatorTests {
|
|||||||
assertEquals(query.export(), " WHERE " + where("firstname").is("Oliver").export());
|
assertEquals(query.export(), " WHERE " + where("firstname").is("Oliver").export());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queryParametersArray() throws Exception {
|
||||||
|
String input = "findByFirstnameIn";
|
||||||
|
PartTree tree = new PartTree(input, User.class);
|
||||||
|
Method method = UserRepository.class.getMethod(input, String[].class);
|
||||||
|
Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
|
||||||
|
N1qlQueryCreator creator = new N1qlQueryCreator(tree,
|
||||||
|
getAccessor(getParameters(method), new Object[] { new Object[] { "Oliver", "Charles" } }), null, converter);
|
||||||
|
Query query = creator.createQuery();
|
||||||
|
|
||||||
|
// Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
|
||||||
|
assertEquals(expected.export(new int[1]), query.export(new int[1]));
|
||||||
|
JsonObject expectedOptions = JsonObject.create();
|
||||||
|
expected.buildQueryOptions(null).build().injectParams(expectedOptions);
|
||||||
|
JsonObject actualOptions = JsonObject.create();
|
||||||
|
expected.buildQueryOptions(null).build().injectParams(actualOptions);
|
||||||
|
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queryParametersJsonArray() throws Exception {
|
||||||
|
String input = "findByFirstnameIn";
|
||||||
|
PartTree tree = new PartTree(input, User.class);
|
||||||
|
Method method = UserRepository.class.getMethod(input, JsonArray.class);
|
||||||
|
|
||||||
|
JsonArray jsonArray = JsonArray.create();
|
||||||
|
jsonArray.add("Oliver");
|
||||||
|
jsonArray.add("Charles");
|
||||||
|
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), jsonArray), null,
|
||||||
|
converter);
|
||||||
|
Query query = creator.createQuery();
|
||||||
|
|
||||||
|
Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
|
||||||
|
assertEquals(expected.export(new int[1]), query.export(new int[1]));
|
||||||
|
JsonObject expectedOptions = JsonObject.create();
|
||||||
|
expected.buildQueryOptions(null).build().injectParams(expectedOptions);
|
||||||
|
JsonObject actualOptions = JsonObject.create();
|
||||||
|
expected.buildQueryOptions(null).build().injectParams(actualOptions);
|
||||||
|
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void queryParametersList() throws Exception {
|
||||||
|
String input = "findByFirstnameIn";
|
||||||
|
PartTree tree = new PartTree(input, User.class);
|
||||||
|
Method method = UserRepository.class.getMethod(input, String[].class);
|
||||||
|
List<String> list = new LinkedList<>();
|
||||||
|
list.add("Oliver");
|
||||||
|
list.add("Charles");
|
||||||
|
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), new Object[] { list }),
|
||||||
|
null, converter);
|
||||||
|
Query query = creator.createQuery();
|
||||||
|
|
||||||
|
Query expected = (new Query()).addCriteria(where("firstname").in("Oliver", "Charles"));
|
||||||
|
|
||||||
|
assertEquals(expected.export(new int[1]), query.export(new int[1]));
|
||||||
|
JsonObject expectedOptions = JsonObject.create();
|
||||||
|
expected.buildQueryOptions(null).build().injectParams(expectedOptions);
|
||||||
|
JsonObject actualOptions = JsonObject.create();
|
||||||
|
expected.buildQueryOptions(null).build().injectParams(actualOptions);
|
||||||
|
assertEquals(expectedOptions.removeKey("client_context_id"), actualOptions.removeKey("client_context_id"));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void createsAndQueryCorrectly() throws Exception {
|
void createsAndQueryCorrectly() throws Exception {
|
||||||
String input = "findByFirstnameAndLastname";
|
String input = "findByFirstnameAndLastname";
|
||||||
@@ -71,7 +140,7 @@ class N1qlQueryCreatorTests {
|
|||||||
converter);
|
converter);
|
||||||
Query query = creator.createQuery();
|
Query query = creator.createQuery();
|
||||||
|
|
||||||
assertEquals(query.export(), " WHERE " + where("firstname").is("John").and("lastname").is("Doe").export());
|
assertEquals(" WHERE " + where("firstname").is("John").and("lastname").is("Doe").export(), query.export());
|
||||||
}
|
}
|
||||||
|
|
||||||
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {
|
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {
|
||||||
|
|||||||
Reference in New Issue
Block a user