Skip to content

Commit d3969d0

Browse files
committed
DATACOUCH-603 Do not cast query parameters in N1qlQueryCreator.
For IN and NOT_IN - they can take varargs, and array or a JsonArray - don't cast the parameter, let parameter accessor handle that. - JsonArray.toString() provides the brackets, don't include them in the format - Object[] array needs explicit conversion, as toString() is not sufficient Also cleaned up some weirdness in QueryCriteria
1 parent ae43ed9 commit d3969d0

File tree

5 files changed

+92
-36
lines changed

5 files changed

+92
-36
lines changed

src/main/java/org/springframework/data/couchbase/core/query/QueryCriteria.java

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.LinkedList;
2121
import java.util.List;
2222

23+
import com.couchbase.client.java.json.JsonArray;
2324
import org.springframework.lang.Nullable;
2425

2526
/**
@@ -68,8 +69,8 @@ public static QueryCriteria where(String key) {
6869
}
6970

7071
private static QueryCriteria wrap(QueryCriteria criteria) {
71-
QueryCriteria qc = new QueryCriteria(new LinkedList<QueryCriteria>(), criteria.key, criteria.value, null,
72-
criteria.operator, criteria.format);
72+
QueryCriteria qc = new QueryCriteria(new LinkedList<>(), criteria.key, criteria.value, null, criteria.operator,
73+
criteria.format);
7374
return qc;
7475
}
7576

@@ -167,7 +168,7 @@ public QueryCriteria containing(@Nullable Object o) {
167168
public QueryCriteria notContaining(@Nullable Object o) {
168169
value = new QueryCriteria[] { wrap(containing(o)) };
169170
operator = "NOT";
170-
format = format = "not( %3$s )";
171+
format = "not( %3$s )";
171172
return this;
172173
}
173174

@@ -196,7 +197,7 @@ public QueryCriteria isNotNull() {
196197
operator = "IS_NOT_NULL";
197198
value = null;
198199
format = "%1$s is not null";
199-
return (QueryCriteria) this;
200+
return this;
200201
}
201202

202203
public QueryCriteria isMissing() {
@@ -210,7 +211,7 @@ public QueryCriteria isNotMissing() {
210211
operator = "IS_NOT_MiSSING";
211212
value = null;
212213
format = "%1$s is not missing";
213-
return (QueryCriteria) this;
214+
return this;
214215
}
215216

216217
public QueryCriteria isValued() {
@@ -224,64 +225,73 @@ public QueryCriteria isNotValued() {
224225
operator = "IS_NOT_VALUED";
225226
value = null;
226227
format = "%1$s is not valued";
227-
return (QueryCriteria) this;
228+
return this;
228229
}
229230

230231
public QueryCriteria within(@Nullable Object o) {
231232
operator = "WITHIN";
232233
value = new Object[] { o };
233234
format = "%1$s within $3$s";
234-
return (QueryCriteria) this;
235+
return this;
235236
}
236237

237238
public QueryCriteria between(@Nullable Object o1, @Nullable Object o2) {
238239
operator = "BETWEEN";
239240
value = new Object[] { o1, o2 };
240241
format = "%1$s between %3$s and %4$s";
241-
return (QueryCriteria) this;
242+
return this;
242243
}
243244

244245
public QueryCriteria in(@Nullable Object... o) {
245246
operator = "IN";
246247
value = o;
247-
StringBuilder sb = new StringBuilder("%1$s in ( [ ");
248+
StringBuilder sb = new StringBuilder("%1$s in ( ");
249+
boolean notArray = false;
250+
// if parameter is an array, then toString() will provide brackets [ ... ]
251+
if (!(value.length == 1 && (value[0] instanceof Object[] || value[0] instanceof JsonArray))) {
252+
notArray = true;
253+
sb.append("[");
254+
}
248255
for (int i = 1; i <= value.length; i++) { // format indices start at 1
249256
if (i > 1)
250-
sb.append(", ");
257+
sb.append(",");
251258
sb.append("%" + (i + 2) + "$s"); // the first is fieldName, second is operator, args start at 3
252259
}
253-
format = sb.append(" ] )").toString();
254-
return (QueryCriteria) this;
260+
if (notArray) {
261+
sb.append("]");
262+
}
263+
format = sb.append(" )").toString();
264+
return this;
255265
}
256266

257267
public QueryCriteria notIn(@Nullable Object... o) {
258268
value = new QueryCriteria[] { wrap(in(o)) };
259269
operator = "NOT";
260-
format = format = "not( %3$s )"; // field = 1$, operator = 2$, value=$3, $4, ...
261-
return (QueryCriteria) this;
270+
format = "not( %3$s )"; // field = 1$, operator = 2$, value=$3, $4, ...
271+
return this;
262272
}
263273

264274
public QueryCriteria TRUE() { // true/false are reserved, use TRUE/FALSE
265275
value = null;
266276
operator = null;
267-
format = format = "%1$s"; // field = 1$, operator = 2$, value=$3, $4, ...
268-
return (QueryCriteria) this;
277+
format = "%1$s"; // field = 1$, operator = 2$, value=$3, $4, ...
278+
return this;
269279
}
270280

271281
public QueryCriteria FALSE() {
272282
value = new QueryCriteria[] { wrap(TRUE()) };
273283
operator = "not";
274-
format = format = "not( %3$s )";
275-
return (QueryCriteria) this;
284+
format = "not( %3$s )";
285+
return this;
276286
}
277287

278288
/**
279289
* This exports the query criteria into a string to be appended to the beginning of an N1QL statement
280290
*
281-
* @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters
282-
* There may already be positional parameters in the beginning of the statement,
283-
* so it may not always start at 1. If it has the value -1, the query is using
284-
* named parameters. If the pointer is null, the query is not using parameters.
291+
* @param paramIndexPtr - this is a reference to the parameter index to be used for positional parameters There may
292+
* already be positional parameters in the beginning of the statement, so it may not always start at 1. If it
293+
* has the value -1, the query is using named parameters. If the pointer is null, the query is not using
294+
* parameters.
285295
* @return string containing part of N1QL query
286296
*/
287297
@Override
@@ -353,6 +363,18 @@ private String maybeWrapValue(String key, Object value, int[] paramIndexPtr) {
353363
return "\"" + value + "\"";
354364
} else if (value == null) {
355365
return "null";
366+
} else if (value instanceof Object[]) {
367+
StringBuffer l = new StringBuffer();
368+
l.append("[");
369+
Object[] array = (Object[]) value;
370+
for (int i = 0; i < array.length; i++) {
371+
if (i > 0) {
372+
l.append(",");
373+
}
374+
l.append(maybeWrapValue(null, array[i], null));
375+
}
376+
l.append("]");
377+
return l.toString();
356378
} else {
357379
return value.toString();
358380
}

src/main/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreator.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
import static org.springframework.data.couchbase.core.query.QueryCriteria.*;
1919

20-
import java.lang.reflect.Array;
20+
import java.util.Arrays;
2121
import java.util.Iterator;
2222

2323
import com.couchbase.client.core.error.InvalidArgumentException;
@@ -141,9 +141,9 @@ private QueryCriteria from(final Part part, final CouchbasePersistentProperty pr
141141
case BETWEEN:
142142
return criteria.between(parameters.next(), parameters.next());
143143
case IN:
144-
return criteria.in((Object[]) parameters.next());
144+
return criteria.in(parameters.next());
145145
case NOT_IN:
146-
return criteria.notIn((Object[]) parameters.next());
146+
return criteria.notIn(parameters.next());
147147
case TRUE:
148148
return criteria.TRUE();
149149
case FALSE:
@@ -159,15 +159,14 @@ private JsonValue getPositionalPlaceholderValues(ParameterAccessor accessor) {
159159
if (queryMethod == null)
160160
return posValues;
161161
for (Parameter parameter : this.queryMethod.getParameters().getBindableParameters()) {
162+
Object param = accessor.getBindableValue(parameter.getIndex());
162163
try {
163-
posValues.add(converter.convertForWriteIfNeeded(accessor.getBindableValue(parameter.getIndex())));
164+
posValues.add(converter.convertForWriteIfNeeded(param));
164165
} catch (InvalidArgumentException iae) {
165-
Object o = accessor.getBindableValue(parameter.getIndex());
166-
if (o instanceof Object[]) {
167-
Object[] array = (Object[]) o;
168-
for (Object e : array) {
169-
posValues.add(converter.convertForWriteIfNeeded(e));
170-
}
166+
if (param instanceof Object[]) {
167+
posValues.add(JsonArray.from(Arrays.asList((Object[]) param)));
168+
} else {
169+
throw iae;
171170
}
172171
}
173172
}

src/test/java/org/springframework/data/couchbase/core/query/QueryCriteriaTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ void testNestedNotIn() {
7070
QueryCriteria c = where("name").is("Bubba").or(where("age").gt(12).or("country").is("Austria")).and(
7171
where("state").notIn(new String[] { "Alabama", "Florida" }));
7272
assertEquals("`name` = \"Bubba\" or (`age` > 12 or `country` = \"Austria\") and "
73-
+ "(not( (`state` in ( [ \"Alabama\", \"Florida\" ] )) ))", c.export());
73+
+ "(not( (`state` in ( [\"Alabama\",\"Florida\"] )) ))", c.export());
7474
}
7575

7676
@Test
@@ -205,13 +205,13 @@ void testBetween() {
205205
@Test
206206
void testIn() {
207207
QueryCriteria c = where("name").in(new String[] { "gump", "davis" });
208-
assertEquals("`name` in ( [ \"gump\", \"davis\" ] )", c.export());
208+
assertEquals("`name` in ( [\"gump\",\"davis\"] )", c.export());
209209
}
210210

211211
@Test
212212
void testNotIn() {
213213
QueryCriteria c = where("name").notIn(new String[] { "gump", "davis" });
214-
assertEquals("not( (`name` in ( [ \"gump\", \"davis\" ] )) )", c.export());
214+
assertEquals("not( (`name` in ( [\"gump\",\"davis\"] )) )", c.export());
215215
}
216216

217217
@Test

src/test/java/org/springframework/data/couchbase/domain/UserRepository.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.List;
2020

21+
import com.couchbase.client.java.json.JsonArray;
2122
import org.springframework.data.couchbase.repository.Query;
2223
import org.springframework.data.repository.PagingAndSortingRepository;
2324
import org.springframework.data.repository.query.Param;
@@ -34,6 +35,10 @@ public interface UserRepository extends PagingAndSortingRepository<User, String>
3435

3536
List<User> findByFirstname(String firstname);
3637

38+
List<User> findByFirstnameIn(String... firstnames);
39+
40+
List<User> findByFirstnameIn(JsonArray firstnames);
41+
3742
List<User> findByFirstnameAndLastname(String firstname, String lastname);
3843

3944
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")

src/test/java/org/springframework/data/couchbase/repository/query/N1qlQueryCreatorTests.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import java.lang.reflect.Method;
2222

23+
import com.couchbase.client.java.json.JsonArray;
2324
import org.junit.jupiter.api.BeforeEach;
2425
import org.junit.jupiter.api.Test;
2526
import org.springframework.data.couchbase.core.convert.CouchbaseConverter;
@@ -62,6 +63,35 @@ void createsQueryCorrectly() throws Exception {
6263
assertEquals(query.export(), " WHERE " + where("firstname").is("Oliver").export());
6364
}
6465

66+
@Test
67+
void queryParametersArray() throws Exception {
68+
String input = "findByFirstnameIn";
69+
PartTree tree = new PartTree(input, User.class);
70+
Method method = UserRepository.class.getMethod(input, String[].class);
71+
72+
N1qlQueryCreator creator = new N1qlQueryCreator(tree,
73+
getAccessor(getParameters(method), new Object[] { new String[] { "Oliver", "Charles" } }), null, converter);
74+
Query query = creator.createQuery();
75+
76+
assertEquals(" WHERE " + where("firstname").in("Oliver", "Charles").export(), query.export());
77+
}
78+
79+
@Test
80+
void queryParametersJsonArray() throws Exception {
81+
String input = "findByFirstnameIn";
82+
PartTree tree = new PartTree(input, User.class);
83+
Method method = UserRepository.class.getMethod(input, JsonArray.class);
84+
85+
JsonArray jsonArray = JsonArray.create();
86+
jsonArray.add("Oliver");
87+
jsonArray.add("Charles");
88+
N1qlQueryCreator creator = new N1qlQueryCreator(tree, getAccessor(getParameters(method), jsonArray), null,
89+
converter);
90+
Query query = creator.createQuery();
91+
92+
assertEquals(" WHERE " + where("firstname").in(new String[] { "Oliver", "Charles" }).export(), query.export());
93+
}
94+
6595
@Test
6696
void createsAndQueryCorrectly() throws Exception {
6797
String input = "findByFirstnameAndLastname";
@@ -71,7 +101,7 @@ void createsAndQueryCorrectly() throws Exception {
71101
converter);
72102
Query query = creator.createQuery();
73103

74-
assertEquals(query.export(), " WHERE " + where("firstname").is("John").and("lastname").is("Doe").export());
104+
assertEquals( " WHERE " + where("firstname").is("John").and("lastname").is("Doe").export(), query.export());
75105
}
76106

77107
private ParameterAccessor getAccessor(Parameters<?, ?> params, Object... values) {

0 commit comments

Comments
 (0)