From 0902fee611798fff797af94b40bfc806a23f7c52 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Wed, 24 May 2017 16:13:17 +0200 Subject: [PATCH 1/8] feat: Search $all strings starting with given strings --- Parse/src/main/java/com/parse/ParseQuery.java | 40 ++++++++++++++++++- .../test/java/com/parse/ParseQueryTest.java | 23 ++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/Parse/src/main/java/com/parse/ParseQuery.java b/Parse/src/main/java/com/parse/ParseQuery.java index c255e1d9f..f6b8a8103 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -8,6 +8,8 @@ */ package com.parse; +import android.support.annotation.NonNull; + import org.json.JSONException; import org.json.JSONObject; @@ -1694,6 +1696,31 @@ public ParseQuery whereContainsAll(String key, Collection values) { return this; } + /** + * Add a constraint to the query that requires a particular key's value match another + * {@code ParseQuery}. + *

+ * This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s. + * Add a constraint to the query that requires a particular key's value to contain each one of + * the provided list of values entirely or just starting with given strings. + * + * @param key + * The key to check. This key's value must be an array. + * @param values + * The values that will match. + * @return this, so you can chain this call. + */ + public ParseQuery whereContainsAllStartingWith(String key, Collection values) { + checkIfRunning(); + + ArrayList regexValues = new ArrayList<>(); + for (String value : values) { + regexValues.add(startingWithRegex(value)); + } + + return whereContainsAll(key, regexValues); + } + /** * Add a constraint to the query that requires a particular key's value match another * {@code ParseQuery}. @@ -1947,7 +1974,7 @@ public ParseQuery whereContains(String key, String substring) { * @return this, so you can chain this call. */ public ParseQuery whereStartsWith(String key, String prefix) { - String regex = "^" + Pattern.quote(prefix); + String regex = startingWithRegex(prefix); whereMatches(key, regex); return this; } @@ -2150,4 +2177,15 @@ public ParseQuery setTrace(boolean shouldTrace) { builder.setTracingEnabled(shouldTrace); return this; } + + /** + * Helper method to convert a string to regex for start word matching. + * + * @param prefix String to use as prefix in regex. + * @return The string converted as regex for start word matching. + */ + @NonNull + private String startingWithRegex(String prefix) { + return "^" + Pattern.quote(prefix); + } } diff --git a/Parse/src/test/java/com/parse/ParseQueryTest.java b/Parse/src/test/java/com/parse/ParseQueryTest.java index 0ef99d37b..0a794c5c7 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -8,6 +8,8 @@ */ package com.parse; +import android.support.annotation.NonNull; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -411,6 +413,20 @@ public void testWhereContainsAll() throws Exception { verifyCondition(query, "key", "$all", values); } + @Test + public void testWhereContainsAllStartingWith() throws Exception { + ParseQuery query = new ParseQuery<>("Test"); + List values = Arrays.asList("value", "valueAgain"); + List valuesConverted = Arrays.asList( + buildStartsWithPattern("value"), + buildStartsWithPattern("valueAgain") + ); + + query.whereContainsAllStartingWith("key", values); + + verifyCondition(query, "key", "$all", valuesConverted); + } + @Test public void testWhereNotContainedIn() throws Exception { ParseQuery query = new ParseQuery<>("Test"); @@ -447,7 +463,7 @@ public void testWhereStartsWith() throws Exception { String value = "prefix"; query.whereStartsWith("key", value); - verifyCondition(query, "key", "$regex", "^" + Pattern.quote(value)); + verifyCondition(query, "key", "$regex", buildStartsWithPattern(value)); } @Test @@ -846,4 +862,9 @@ public Task then(Task task) throws Exception { })).cast(); } } + + @NonNull + private String buildStartsWithPattern(String value) { + return "^" + Pattern.quote(value); + } } From 9a53ca23eb708466048f6b5b10b3d3fe4e46c6da Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Wed, 24 May 2017 18:11:17 +0200 Subject: [PATCH 2/8] fix: Use regex for containsAllStartsWith query condition --- .../src/main/java/com/parse/ParseEncoder.java | 10 +++++++++- Parse/src/main/java/com/parse/ParseQuery.java | 10 +++++----- .../test/java/com/parse/ParseEncoderTest.java | 9 +++++++++ .../test/java/com/parse/ParseQueryTest.java | 18 ++++++++++++++++-- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Parse/src/main/java/com/parse/ParseEncoder.java b/Parse/src/main/java/com/parse/ParseEncoder.java index 096092f57..2783917ba 100644 --- a/Parse/src/main/java/com/parse/ParseEncoder.java +++ b/Parse/src/main/java/com/parse/ParseEncoder.java @@ -18,6 +18,7 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * A {@code ParseEncoder} can be used to transform objects such as {@link ParseObjects} into JSON @@ -40,7 +41,8 @@ || value instanceof ParseACL || value instanceof ParseFile || value instanceof ParseGeoPoint - || value instanceof ParseRelation; + || value instanceof ParseRelation + || value instanceof Pattern; } public Object encode(Object object) { @@ -120,6 +122,12 @@ public Object encode(Object object) { return ((ParseQuery.RelationConstraint) object).encode(this); } + if (object instanceof Pattern) { + JSONObject json = new JSONObject(); + json.put("$regex", object.toString()); + return json; + } + if (object == null) { return JSONObject.NULL; } diff --git a/Parse/src/main/java/com/parse/ParseQuery.java b/Parse/src/main/java/com/parse/ParseQuery.java index f6b8a8103..754695cee 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -1710,12 +1710,12 @@ public ParseQuery whereContainsAll(String key, Collection values) { * The values that will match. * @return this, so you can chain this call. */ - public ParseQuery whereContainsAllStartingWith(String key, Collection values) { + public ParseQuery whereContainsAllStartsWith(String key, Collection values) { checkIfRunning(); - ArrayList regexValues = new ArrayList<>(); + ArrayList regexValues = new ArrayList<>(); for (String value : values) { - regexValues.add(startingWithRegex(value)); + regexValues.add(Pattern.compile(buildStartsWithRegex(value))); } return whereContainsAll(key, regexValues); @@ -1974,7 +1974,7 @@ public ParseQuery whereContains(String key, String substring) { * @return this, so you can chain this call. */ public ParseQuery whereStartsWith(String key, String prefix) { - String regex = startingWithRegex(prefix); + String regex = buildStartsWithRegex(prefix); whereMatches(key, regex); return this; } @@ -2185,7 +2185,7 @@ public ParseQuery setTrace(boolean shouldTrace) { * @return The string converted as regex for start word matching. */ @NonNull - private String startingWithRegex(String prefix) { + private String buildStartsWithRegex(String prefix) { return "^" + Pattern.quote(prefix); } } diff --git a/Parse/src/test/java/com/parse/ParseEncoderTest.java b/Parse/src/test/java/com/parse/ParseEncoderTest.java index 23cdd4f09..0a7e05e12 100644 --- a/Parse/src/test/java/com/parse/ParseEncoderTest.java +++ b/Parse/src/test/java/com/parse/ParseEncoderTest.java @@ -22,6 +22,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.regex.Pattern; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -161,6 +162,14 @@ public void testRelationContraint() throws JSONException { assertEquals(">", relationConstraintJSON.getString("key")); } + @Test + public void testPattern() throws JSONException { + Pattern pattern = Pattern.compile("^\\Qvalue\\E"); + JSONObject patternJSON = (JSONObject) testClassObject.encode(pattern); + assertNotNull(patternJSON); + assertEquals("^\\Qvalue\\E", patternJSON.getString("$regex")); + } + @Test public void testNull() throws JSONException { Object object = testClassObject.encode(null); diff --git a/Parse/src/test/java/com/parse/ParseQueryTest.java b/Parse/src/test/java/com/parse/ParseQueryTest.java index 0a794c5c7..ad885e8e9 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -422,9 +422,9 @@ public void testWhereContainsAllStartingWith() throws Exception { buildStartsWithPattern("valueAgain") ); - query.whereContainsAllStartingWith("key", values); + query.whereContainsAllStartsWith("key", values); - verifyCondition(query, "key", "$all", valuesConverted); + verifyConditionAsString(query, "key", "$all", valuesConverted); } @Test @@ -776,6 +776,20 @@ private static void verifyCondition( assertEquals(map.get(constraintKey), values.get(constraintKey)); } } + + private static void verifyConditionAsString( + ParseQuery query, String key, String conditionKey, List values) { + // We generate a state to verify the content of the builder + ParseQuery.State state = query.getBuilder().build(); + ParseQuery.QueryConstraints queryConstraints = state.constraints(); + ParseQuery.KeyConstraints keyConstraints = + (ParseQuery.KeyConstraints) queryConstraints.get(key); + Collection list = (Collection) keyConstraints.get(conditionKey); + assertEquals(values.size(), list.size()); + for (Object listValue : list) { + assertTrue(values.contains(listValue.toString())); + } + } //endregion From bf8be89927d5b4432aae0aab31f0db8e5208525a Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Fri, 26 May 2017 18:20:05 +0200 Subject: [PATCH 3/8] doc: Update containsAll documentation --- Parse/src/main/java/com/parse/ParseQuery.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Parse/src/main/java/com/parse/ParseQuery.java b/Parse/src/main/java/com/parse/ParseQuery.java index 754695cee..0a4572d7f 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -1677,10 +1677,6 @@ public ParseQuery whereContainedIn(String key, Collection v } /** - * Add a constraint to the query that requires a particular key's value match another - * {@code ParseQuery}. - *

- * This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s. * Add a constraint to the query that requires a particular key's value to contain every one of * the provided list of values. * @@ -1697,17 +1693,13 @@ public ParseQuery whereContainsAll(String key, Collection values) { } /** - * Add a constraint to the query that requires a particular key's value match another - * {@code ParseQuery}. - *

- * This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s. * Add a constraint to the query that requires a particular key's value to contain each one of - * the provided list of values entirely or just starting with given strings. + * the provided list of strings entirely or just starting with given strings. * * @param key * The key to check. This key's value must be an array. * @param values - * The values that will match. + * The values that will match entirely or starting with them. * @return this, so you can chain this call. */ public ParseQuery whereContainsAllStartsWith(String key, Collection values) { From c1b2e9d32852e7de15eb9578ddf6d906a4028344 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Fri, 26 May 2017 18:30:04 +0200 Subject: [PATCH 4/8] refactor: Use KeyConstraints in containsAllStartsWith Reverts ParseEncoder changes --- .../src/main/java/com/parse/ParseEncoder.java | 10 +----- Parse/src/main/java/com/parse/ParseQuery.java | 8 +++-- .../test/java/com/parse/ParseEncoderTest.java | 9 ------ .../test/java/com/parse/ParseQueryTest.java | 31 +++++++------------ 4 files changed, 17 insertions(+), 41 deletions(-) diff --git a/Parse/src/main/java/com/parse/ParseEncoder.java b/Parse/src/main/java/com/parse/ParseEncoder.java index 2783917ba..096092f57 100644 --- a/Parse/src/main/java/com/parse/ParseEncoder.java +++ b/Parse/src/main/java/com/parse/ParseEncoder.java @@ -18,7 +18,6 @@ import java.util.Date; import java.util.List; import java.util.Map; -import java.util.regex.Pattern; /** * A {@code ParseEncoder} can be used to transform objects such as {@link ParseObjects} into JSON @@ -41,8 +40,7 @@ || value instanceof ParseACL || value instanceof ParseFile || value instanceof ParseGeoPoint - || value instanceof ParseRelation - || value instanceof Pattern; + || value instanceof ParseRelation; } public Object encode(Object object) { @@ -122,12 +120,6 @@ public Object encode(Object object) { return ((ParseQuery.RelationConstraint) object).encode(this); } - if (object instanceof Pattern) { - JSONObject json = new JSONObject(); - json.put("$regex", object.toString()); - return json; - } - if (object == null) { return JSONObject.NULL; } diff --git a/Parse/src/main/java/com/parse/ParseQuery.java b/Parse/src/main/java/com/parse/ParseQuery.java index 0a4572d7f..01619a985 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -1705,12 +1705,14 @@ public ParseQuery whereContainsAll(String key, Collection values) { public ParseQuery whereContainsAllStartsWith(String key, Collection values) { checkIfRunning(); - ArrayList regexValues = new ArrayList<>(); + ArrayList startsWithConstraints = new ArrayList<>(); for (String value : values) { - regexValues.add(Pattern.compile(buildStartsWithRegex(value))); + KeyConstraints keyConstraints = new KeyConstraints(); + keyConstraints.put("$regex", buildStartsWithRegex(value)); + startsWithConstraints.add(keyConstraints); } - return whereContainsAll(key, regexValues); + return whereContainsAll(key, startsWithConstraints); } /** diff --git a/Parse/src/test/java/com/parse/ParseEncoderTest.java b/Parse/src/test/java/com/parse/ParseEncoderTest.java index 0a7e05e12..23cdd4f09 100644 --- a/Parse/src/test/java/com/parse/ParseEncoderTest.java +++ b/Parse/src/test/java/com/parse/ParseEncoderTest.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Date; import java.util.HashMap; -import java.util.regex.Pattern; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -162,14 +161,6 @@ public void testRelationContraint() throws JSONException { assertEquals(">", relationConstraintJSON.getString("key")); } - @Test - public void testPattern() throws JSONException { - Pattern pattern = Pattern.compile("^\\Qvalue\\E"); - JSONObject patternJSON = (JSONObject) testClassObject.encode(pattern); - assertNotNull(patternJSON); - assertEquals("^\\Qvalue\\E", patternJSON.getString("$regex")); - } - @Test public void testNull() throws JSONException { Object object = testClassObject.encode(null); diff --git a/Parse/src/test/java/com/parse/ParseQueryTest.java b/Parse/src/test/java/com/parse/ParseQueryTest.java index ad885e8e9..3a0296b98 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -416,15 +416,20 @@ public void testWhereContainsAll() throws Exception { @Test public void testWhereContainsAllStartingWith() throws Exception { ParseQuery query = new ParseQuery<>("Test"); - List values = Arrays.asList("value", "valueAgain"); - List valuesConverted = Arrays.asList( - buildStartsWithPattern("value"), - buildStartsWithPattern("valueAgain") - ); + String value = "value"; + String valueAgain = "valueAgain"; + List values = Arrays.asList(value, valueAgain); + + ParseQuery.KeyConstraints valueConverted = new ParseQuery.KeyConstraints(); + valueConverted.put("$regex", buildStartsWithPattern(value)); + ParseQuery.KeyConstraints valueAgainConverted = new ParseQuery.KeyConstraints(); + valueAgainConverted.put("$regex", buildStartsWithPattern(valueAgain)); + List valuesConverted = + Arrays.asList(valueConverted, valueAgainConverted); query.whereContainsAllStartsWith("key", values); - verifyConditionAsString(query, "key", "$all", valuesConverted); + verifyCondition(query, "key", "$all", valuesConverted); } @Test @@ -777,20 +782,6 @@ private static void verifyCondition( } } - private static void verifyConditionAsString( - ParseQuery query, String key, String conditionKey, List values) { - // We generate a state to verify the content of the builder - ParseQuery.State state = query.getBuilder().build(); - ParseQuery.QueryConstraints queryConstraints = state.constraints(); - ParseQuery.KeyConstraints keyConstraints = - (ParseQuery.KeyConstraints) queryConstraints.get(key); - Collection list = (Collection) keyConstraints.get(conditionKey); - assertEquals(values.size(), list.size()); - for (Object listValue : list) { - assertTrue(values.contains(listValue.toString())); - } - } - //endregion /** From d57dad3a2265297b409ff7b3c8609c3cf62a04b2 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Fri, 26 May 2017 19:34:28 +0200 Subject: [PATCH 5/8] refactor: Revert line change --- Parse/src/test/java/com/parse/ParseQueryTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Parse/src/test/java/com/parse/ParseQueryTest.java b/Parse/src/test/java/com/parse/ParseQueryTest.java index 3a0296b98..6da57a1c1 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -781,7 +781,7 @@ private static void verifyCondition( assertEquals(map.get(constraintKey), values.get(constraintKey)); } } - + //endregion /** From caaf453aeb031c2eef558a96a59014e7c81cf02a Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Tue, 11 Jul 2017 12:52:00 +0200 Subject: [PATCH 6/8] fix: containsAllStartingWith for offline datastore --- .../java/com/parse/OfflineQueryLogic.java | 93 +++++++++++++++++-- .../java/com/parse/OfflineQueryLogicTest.java | 85 +++++++++++++++++ 2 files changed, 172 insertions(+), 6 deletions(-) diff --git a/Parse/src/main/java/com/parse/OfflineQueryLogic.java b/Parse/src/main/java/com/parse/OfflineQueryLogic.java index 3a95f1186..11c631660 100644 --- a/Parse/src/main/java/com/parse/OfflineQueryLogic.java +++ b/Parse/src/main/java/com/parse/OfflineQueryLogic.java @@ -231,12 +231,24 @@ private static boolean matchesEqualConstraint(Object constraint, Object value) { && lhs.getLongitude() == rhs.getLongitude(); } - return compare(constraint, value, new Decider() { - @Override - public boolean decide(Object constraint, Object value) { - return constraint.equals(value); - } - }); + Decider decider; + if (isStartsWithRegex(constraint)) { + decider = new Decider() { + @Override + public boolean decide(Object constraint, Object value) { + return ((String) value).matches(constraint.toString()); + } + }; + } else { + decider = new Decider() { + @Override + public boolean decide(Object constraint, Object value) { + return constraint.equals(value); + } + }; + } + + return compare(constraint, value, decider); } /** @@ -342,6 +354,13 @@ private static boolean matchesAllConstraint(Object constraint, Object value) { } if (constraint instanceof Collection) { + if (isAnyValueRegexStartsWith((Collection) constraint)) { + constraint = cleanRegexStartsWith((Collection) constraint); + if (constraint == null) { + throw new IllegalArgumentException("All values in $all queries must be of starting with regex or non regex."); + } + } + for (Object requiredItem : (Collection) constraint) { if (!matchesEqualConstraint(requiredItem, value)) { return false; @@ -352,6 +371,68 @@ private static boolean matchesAllConstraint(Object constraint, Object value) { throw new IllegalArgumentException("Constraint type not supported for $all queries."); } + /** + * Check if any of the collection constraints is a regex to match strings that starts with another + * string. + */ + private static boolean isAnyValueRegexStartsWith(Collection constraints) { + for (Object constraint : constraints) { + if (isStartsWithRegex(constraint)) { + return true; + } + }; + + return false; + } + + /** + * Cleans all regex constraints. If any of the constraints is not a regex, then null is returned. + * All values in a $all constraint must be a starting with another string regex. + */ + private static Collection cleanRegexStartsWith(Collection constraints) { + ArrayList cleanedValues = new ArrayList<>(); + for (Object constraint : constraints) { + String cleanedRegex = cleanRegexStartsWith((String) constraint); + if (cleanedRegex == null) { + return null; + } + + cleanedValues.add(cleanedRegex); + } + + return cleanedValues; + } + + /** + * Creates a regex pattern to match a substring at the beginning of another string. + * + * If given string is not a regex to match a string at the beginning of another string, then null + * is returned. + */ + private static String cleanRegexStartsWith(String regex) { + if (!isStartsWithRegex(regex)) { + return null; + } + + // remove all instances of \Q and \E from the remaining text & escape single quotes + String literalizedString = regex.replaceAll("([^\\\\])(\\\\E)", "$1") + .replaceAll("([^\\\\])(\\\\Q)", "$1") + .replaceAll("^\\\\E", "") + .replaceAll("^\\\\Q", "") + .replaceAll("([^'])'", "$1''") + .replaceAll("^'([^'])", "''$1"); + + return '^' + literalizedString + ".*"; + } + + /** + * Check if given constraint is a regex to match strings that starts with another string. + */ + private static boolean isStartsWithRegex(Object constraint) { + return constraint != null && constraint instanceof String && + ((String)constraint).startsWith("^"); + } + /** * Matches $regex constraints. */ diff --git a/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java b/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java index 3d7a79e05..e8426e8ce 100644 --- a/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java +++ b/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java @@ -8,6 +8,8 @@ */ package com.parse; +import android.support.annotation.NonNull; + import org.json.JSONArray; import org.json.JSONObject; import org.junit.After; @@ -23,6 +25,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import bolts.Task; @@ -381,6 +384,88 @@ public void testMatchesAll() throws Exception { assertFalse(matches(logic, query, object)); } + @Test + public void testMatchesAllStartingWith() throws Exception { + ParseObject object = new ParseObject("TestObject"); + object.put("foo", Arrays.asList("foo", "bar")); + + ParseQuery.State query; + OfflineQueryLogic logic = new OfflineQueryLogic(null); + + query = new ParseQuery.State.Builder<>("TestObject") + .addCondition("foo", "$all", + Arrays.asList( + buildStartsWithRegex("foo"), + buildStartsWithRegex("bar"))) + .build(); + assertTrue(matches(logic, query, object)); + + query = new ParseQuery.State.Builder<>("TestObject") + .addCondition("foo", "$all", + Arrays.asList( + buildStartsWithRegex("fo"), + buildStartsWithRegex("b"))) + .build(); + assertTrue(matches(logic, query, object)); + + query = new ParseQuery.State.Builder<>("TestObject") + .addCondition("foo", "$all", + Arrays.asList( + buildStartsWithRegex("foo"), + buildStartsWithRegex("bar"), + buildStartsWithRegex("qux"))) + .build(); + assertFalse(matches(logic, query, object)); + + // Non-existant key + object = new ParseObject("TestObject"); + assertFalse(matches(logic, query, object)); + object.put("foo", JSONObject.NULL); + assertFalse(matches(logic, query, object)); + + thrown.expect(IllegalArgumentException.class); + object.put("foo", "bar"); + assertFalse(matches(logic, query, object)); + } + + @Test + public void testMatchesAllStartingWithParameters() throws Exception { + ParseObject object = new ParseObject("TestObject"); + object.put("foo", Arrays.asList("foo", "bar")); + + ParseQuery.State query; + OfflineQueryLogic logic = new OfflineQueryLogic(null); + + query = new ParseQuery.State.Builder<>("TestObject") + .addCondition("foo", "$all", + Arrays.asList( + buildStartsWithRegex("foo"), + buildStartsWithRegex("bar"))) + .build(); + assertTrue(matches(logic, query, object)); + + query = new ParseQuery.State.Builder<>("TestObject") + .addCondition("foo", "$all", + Arrays.asList( + buildStartsWithRegex("fo"), + "b")) + .build(); + thrown.expect(IllegalArgumentException.class); + assertFalse(matches(logic, query, object)); + } + + /** + * Helper method to convert a string to regex for start word matching. + * + * @param prefix String to use as prefix in regex. + * @return The string converted as regex for start word matching. + */ + @NonNull + private String buildStartsWithRegex(String prefix) { + return "^" + Pattern.quote(prefix); + } + + @Test public void testMatchesNearSphere() throws Exception { ParseGeoPoint fb = new ParseGeoPoint(37.481689f, -122.154949f); From 7c8a423f36730a4191baab3a7dc98ae8b90d6be9 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Tue, 11 Jul 2017 18:52:14 +0200 Subject: [PATCH 7/8] fix: Constraints for $all $regex are KeyConstraints and not strings --- .../java/com/parse/OfflineQueryLogic.java | 27 ++++++++---- .../java/com/parse/OfflineQueryLogicTest.java | 44 ++++++++++++++----- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/Parse/src/main/java/com/parse/OfflineQueryLogic.java b/Parse/src/main/java/com/parse/OfflineQueryLogic.java index 11c631660..47771ec8b 100644 --- a/Parse/src/main/java/com/parse/OfflineQueryLogic.java +++ b/Parse/src/main/java/com/parse/OfflineQueryLogic.java @@ -236,7 +236,7 @@ private static boolean matchesEqualConstraint(Object constraint, Object value) { decider = new Decider() { @Override public boolean decide(Object constraint, Object value) { - return ((String) value).matches(constraint.toString()); + return ((String) value).matches(((KeyConstraints)constraint).get("$regex").toString()); } }; } else { @@ -390,9 +390,13 @@ private static boolean isAnyValueRegexStartsWith(Collection constraints) { * All values in a $all constraint must be a starting with another string regex. */ private static Collection cleanRegexStartsWith(Collection constraints) { - ArrayList cleanedValues = new ArrayList<>(); + ArrayList cleanedValues = new ArrayList<>(); for (Object constraint : constraints) { - String cleanedRegex = cleanRegexStartsWith((String) constraint); + if (!(constraint instanceof KeyConstraints)) { + return null; + } + + KeyConstraints cleanedRegex = cleanRegexStartsWith((KeyConstraints) constraint); if (cleanedRegex == null) { return null; } @@ -409,28 +413,35 @@ private static Collection cleanRegexStartsWith(Collection constraints) { * If given string is not a regex to match a string at the beginning of another string, then null * is returned. */ - private static String cleanRegexStartsWith(String regex) { + private static KeyConstraints cleanRegexStartsWith(KeyConstraints regex) { if (!isStartsWithRegex(regex)) { return null; } // remove all instances of \Q and \E from the remaining text & escape single quotes - String literalizedString = regex.replaceAll("([^\\\\])(\\\\E)", "$1") + String literalizedString = ((String)regex.get("$regex")) + .replaceAll("([^\\\\])(\\\\E)", "$1") .replaceAll("([^\\\\])(\\\\Q)", "$1") .replaceAll("^\\\\E", "") .replaceAll("^\\\\Q", "") .replaceAll("([^'])'", "$1''") .replaceAll("^'([^'])", "''$1"); - return '^' + literalizedString + ".*"; + regex.put("$regex", literalizedString + ".*"); + return regex; } /** * Check if given constraint is a regex to match strings that starts with another string. */ private static boolean isStartsWithRegex(Object constraint) { - return constraint != null && constraint instanceof String && - ((String)constraint).startsWith("^"); + if (constraint == null || !(constraint instanceof KeyConstraints)) { + return false; + } + + KeyConstraints keyConstraints = (KeyConstraints) constraint; + return keyConstraints.size() == 1 && keyConstraints.containsKey("$regex") && + ((String)keyConstraints.get("$regex")).startsWith("^"); } /** diff --git a/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java b/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java index e8426e8ce..9806499e6 100644 --- a/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java +++ b/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java @@ -395,25 +395,25 @@ public void testMatchesAllStartingWith() throws Exception { query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$all", Arrays.asList( - buildStartsWithRegex("foo"), - buildStartsWithRegex("bar"))) + buildStartsWithRegexKeyConstraint("foo"), + buildStartsWithRegexKeyConstraint("bar"))) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$all", Arrays.asList( - buildStartsWithRegex("fo"), - buildStartsWithRegex("b"))) + buildStartsWithRegexKeyConstraint("fo"), + buildStartsWithRegexKeyConstraint("b"))) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$all", Arrays.asList( - buildStartsWithRegex("foo"), - buildStartsWithRegex("bar"), - buildStartsWithRegex("qux"))) + buildStartsWithRegexKeyConstraint("foo"), + buildStartsWithRegexKeyConstraint("bar"), + buildStartsWithRegexKeyConstraint("qux"))) .build(); assertFalse(matches(logic, query, object)); @@ -439,19 +439,43 @@ public void testMatchesAllStartingWithParameters() throws Exception { query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$all", Arrays.asList( - buildStartsWithRegex("foo"), - buildStartsWithRegex("bar"))) + buildStartsWithRegexKeyConstraint("foo"), + buildStartsWithRegexKeyConstraint("bar"))) .build(); assertTrue(matches(logic, query, object)); query = new ParseQuery.State.Builder<>("TestObject") .addCondition("foo", "$all", Arrays.asList( - buildStartsWithRegex("fo"), + buildStartsWithRegexKeyConstraint("fo"), + buildStartsWithRegex("ba"), "b")) .build(); thrown.expect(IllegalArgumentException.class); assertFalse(matches(logic, query, object)); + + query = new ParseQuery.State.Builder<>("TestObject") + .addCondition("foo", "$all", + Arrays.asList( + buildStartsWithRegexKeyConstraint("fo"), + "b")) + .build(); + thrown.expect(IllegalArgumentException.class); + assertFalse(matches(logic, query, object)); + } + + /** + * Helper method to convert a string to a key constraint to match strings that starts with given + * string. + * + * @param prefix String to use as prefix in regex. + * @return The key constraint for word matching at the beginning of a string. + */ + @NonNull + private ParseQuery.KeyConstraints buildStartsWithRegexKeyConstraint(String prefix) { + ParseQuery.KeyConstraints constraint = new ParseQuery.KeyConstraints(); + constraint.put("$regex", buildStartsWithRegex(prefix)); + return constraint; } /** From f5c650c2ea414eb4c248e5fc6def40ba223bd49d Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Wed, 13 Sep 2017 11:40:41 +0200 Subject: [PATCH 8/8] fix: Remove unexistent call --- Parse/src/main/java/com/parse/ParseQuery.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/Parse/src/main/java/com/parse/ParseQuery.java b/Parse/src/main/java/com/parse/ParseQuery.java index 097182a6d..48ea1e4f2 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -1692,8 +1692,6 @@ public ParseQuery whereContainsAll(String key, Collection values) { * @return this, so you can chain this call. */ public ParseQuery whereContainsAllStartsWith(String key, Collection values) { - checkIfRunning(); - ArrayList startsWithConstraints = new ArrayList<>(); for (String value : values) { KeyConstraints keyConstraints = new KeyConstraints();