From 84413001ca6db113e1a5cbaa3c56658da322815e Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Wed, 24 May 2017 16:13:17 +0200 Subject: [PATCH 1/9] 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 523d1e05c..c0a325bba 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; @@ -1708,6 +1710,31 @@ public ParseQuery whereFullText(String key, String text) { 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}. @@ -1988,7 +2015,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; } @@ -2192,4 +2219,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 f58aa3531..33c2021c5 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; @@ -389,6 +391,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"); @@ -425,7 +441,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 @@ -904,4 +920,9 @@ public Task then(Task task) throws Exception { })).cast(); } } + + @NonNull + private String buildStartsWithPattern(String value) { + return "^" + Pattern.quote(value); + } } From bbe49d294d0b66a8891445edbf6f638cc045f818 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Wed, 24 May 2017 18:11:17 +0200 Subject: [PATCH 2/9] 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 ae557aadf..2f453f6f4 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 @@ -41,7 +42,8 @@ abstract class ParseEncoder { || value instanceof ParseFile || value instanceof ParseGeoPoint || value instanceof ParsePolygon - || value instanceof ParseRelation; + || value instanceof ParseRelation + || value instanceof Pattern; } public Object encode(Object object) { @@ -129,6 +131,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 c0a325bba..f082c0917 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -1724,12 +1724,12 @@ public ParseQuery whereFullText(String key, String text) { * 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); @@ -2015,7 +2015,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; } @@ -2227,7 +2227,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 dafee8af9..e8a5b823d 100644 --- a/Parse/src/test/java/com/parse/ParseEncoderTest.java +++ b/Parse/src/test/java/com/parse/ParseEncoderTest.java @@ -23,6 +23,7 @@ import java.util.List; 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; @@ -177,6 +178,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 33c2021c5..7256e8c7f 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -400,9 +400,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 @@ -835,6 +835,20 @@ 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 999e77063d9f0e80db91cf14893924b99afae27f Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Fri, 26 May 2017 18:20:05 +0200 Subject: [PATCH 3/9] 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 f082c0917..4289ec36f 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -1675,10 +1675,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. * @@ -1711,17 +1707,13 @@ public ParseQuery whereFullText(String key, String text) { } /** - * 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 366f3bd090df69d03f49f7183774a9e7605c9551 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Fri, 26 May 2017 18:30:04 +0200 Subject: [PATCH 4/9] refactor: Use KeyConstraints in containsAllStartsWith Reverts ParseEncoder changes --- .../src/main/java/com/parse/ParseEncoder.java | 9 ++---- 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, 18 insertions(+), 39 deletions(-) diff --git a/Parse/src/main/java/com/parse/ParseEncoder.java b/Parse/src/main/java/com/parse/ParseEncoder.java index 2f453f6f4..cf83bddbd 100644 --- a/Parse/src/main/java/com/parse/ParseEncoder.java +++ b/Parse/src/main/java/com/parse/ParseEncoder.java @@ -43,7 +43,8 @@ abstract class ParseEncoder { || value instanceof ParseGeoPoint || value instanceof ParsePolygon || value instanceof ParseRelation - || value instanceof Pattern; + || value instanceof Pattern + || value instanceof ParseRelation; } public Object encode(Object object) { @@ -131,12 +132,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 4289ec36f..12c45171a 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -1719,12 +1719,14 @@ public ParseQuery whereFullText(String key, String text) { 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 e8a5b823d..dafee8af9 100644 --- a/Parse/src/test/java/com/parse/ParseEncoderTest.java +++ b/Parse/src/test/java/com/parse/ParseEncoderTest.java @@ -23,7 +23,6 @@ import java.util.List; 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; @@ -178,14 +177,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 7256e8c7f..8c7aa4225 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -394,15 +394,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 @@ -835,20 +840,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 5ede9f24c7d688cc0cd43bac4ab7fd312e2b31f8 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Fri, 26 May 2017 19:34:28 +0200 Subject: [PATCH 5/9] 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 8c7aa4225..cf5ec7bf3 100644 --- a/Parse/src/test/java/com/parse/ParseQueryTest.java +++ b/Parse/src/test/java/com/parse/ParseQueryTest.java @@ -839,7 +839,7 @@ private static void verifyCondition( assertEquals(map.get(constraintKey), values.get(constraintKey)); } } - + //endregion /** From 78236565c7a51822878860eb28b3be10a02b41f6 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Tue, 11 Jul 2017 12:52:00 +0200 Subject: [PATCH 6/9] 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 78b511589..898469048 100644 --- a/Parse/src/main/java/com/parse/OfflineQueryLogic.java +++ b/Parse/src/main/java/com/parse/OfflineQueryLogic.java @@ -237,12 +237,24 @@ private static boolean matchesEqualConstraint(Object constraint, Object value) { return lhs.equals(rhs); } - 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); } /** @@ -348,6 +360,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; @@ -358,6 +377,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 f7d84c04f..9de169db4 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; @@ -419,6 +422,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 689b987dfb5edac9c26784a9a8d843ff39fef051 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Tue, 11 Jul 2017 18:52:14 +0200 Subject: [PATCH 7/9] 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 898469048..dbb1e50ef 100644 --- a/Parse/src/main/java/com/parse/OfflineQueryLogic.java +++ b/Parse/src/main/java/com/parse/OfflineQueryLogic.java @@ -242,7 +242,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 { @@ -396,9 +396,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; } @@ -415,28 +419,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 9de169db4..309182adf 100644 --- a/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java +++ b/Parse/src/test/java/com/parse/OfflineQueryLogicTest.java @@ -433,25 +433,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)); @@ -477,19 +477,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 b188e0a6ec967c3727da1d55d95eb08660da1390 Mon Sep 17 00:00:00 2001 From: Eduard Bosch Bertran Date: Wed, 13 Sep 2017 11:40:41 +0200 Subject: [PATCH 8/9] 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 12c45171a..aff5cc9fe 100644 --- a/Parse/src/main/java/com/parse/ParseQuery.java +++ b/Parse/src/main/java/com/parse/ParseQuery.java @@ -1717,8 +1717,6 @@ public ParseQuery whereFullText(String key, String text) { * @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(); From 7917f7b803bcebd377c6de053481d253df5edc86 Mon Sep 17 00:00:00 2001 From: Roger Hu Date: Thu, 31 May 2018 11:37:21 -0700 Subject: [PATCH 9/9] Revert stuff --- Parse/src/main/java/com/parse/ParseEncoder.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/Parse/src/main/java/com/parse/ParseEncoder.java b/Parse/src/main/java/com/parse/ParseEncoder.java index cf83bddbd..ae557aadf 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 @@ -42,8 +41,6 @@ abstract class ParseEncoder { || value instanceof ParseFile || value instanceof ParseGeoPoint || value instanceof ParsePolygon - || value instanceof ParseRelation - || value instanceof Pattern || value instanceof ParseRelation; }