diff --git a/src/main/java/org/springframework/data/repository/query/parser/PartTree.java b/src/main/java/org/springframework/data/repository/query/parser/PartTree.java index 2126a832a..f51eb289a 100644 --- a/src/main/java/org/springframework/data/repository/query/parser/PartTree.java +++ b/src/main/java/org/springframework/data/repository/query/parser/PartTree.java @@ -34,11 +34,23 @@ import org.springframework.util.StringUtils; * each query execution. * * @author Oliver Gierke + * @author Thomas Darimont */ public class PartTree implements Iterable { private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^(find|read|get|count)(\\p{Lu}.*?)??By"); - private static final String KEYWORD_TEMPLATE = "(%s)(?=\\p{Lu})"; + + /* + * We look for a pattern of: keyword followed by + * + * an upper-case letter that has a lower-case variant \p{Lu} + * OR + * any other letter NOT in the BASIC_LATIN Uni-code Block \\P{InBASIC_LATIN} (like Chinese, Korean, Japanese, etc.). + * + * @see http://www.regular-expressions.info/unicode.html + * @see http://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html#ubc + */ + private static final String KEYWORD_TEMPLATE = "(%s)(?=(\\p{Lu}|\\P{InBASIC_LATIN}))"; /** * The subject, for example "findDistinctUserByNameOrderByAge" would have the subject "DistinctUser". diff --git a/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java b/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java index 9d96e469a..7d2a0fe93 100644 --- a/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java +++ b/src/test/java/org/springframework/data/repository/query/parser/PartTreeUnitTests.java @@ -319,6 +319,94 @@ public class PartTreeUnitTests { assertTrue(tree.getSort().getOrderFor("år").isAscending()); } + /** + * @see DATACMNS-363 + */ + @Test + public void parsesSpecialCharactersOnlyCorrectly_Korean() { + + PartTree tree = new PartTree("findBy이름And생일OrderBy생일Asc", DomainObjectWithSpecialChars.class); + + assertPart(tree, new Part[] { new Part("이름", DomainObjectWithSpecialChars.class), + new Part("생일", DomainObjectWithSpecialChars.class) }); + assertTrue(tree.getSort().getOrderFor("생일").isAscending()); + } + + /** + * @see DATACMNS-363 + */ + @Test + public void parsesSpecialUnicodeCharactersMixedWithRegularCharactersCorrectly_Korean() { + + PartTree tree = new PartTree("findBy이름AndOrderIdOrderBy생일Asc", DomainObjectWithSpecialChars.class); + + assertPart(tree, new Part[] { new Part("이름", DomainObjectWithSpecialChars.class), + new Part("order.id", DomainObjectWithSpecialChars.class) }); + assertTrue(tree.getSort().getOrderFor("생일").isAscending()); + } + + /** + * @see DATACMNS-363 + */ + @Test + public void parsesNestedSpecialUnicodeCharactersMixedWithRegularCharactersCorrectly_Korean() { + + PartTree tree = new PartTree( // + "findBy" + "이름" // + + "And" + "OrderId" // + + "And" + "Nested_이름" // we use _ here to mark the beginning of a new property reference "이름" + + "Or" + "NestedOrderId" // + + "OrderBy" + "생일" + "Asc", DomainObjectWithSpecialChars.class); + + Iterator parts = tree.iterator(); + assertPartsIn(parts.next(), new Part[] { // + new Part("이름", DomainObjectWithSpecialChars.class), // + new Part("order.id", DomainObjectWithSpecialChars.class), // + new Part("nested.이름", DomainObjectWithSpecialChars.class) // + }); + assertPartsIn(parts.next(), new Part[] { // + new Part("nested.order.id", DomainObjectWithSpecialChars.class) // + }); + + assertTrue(tree.getSort().getOrderFor("생일").isAscending()); + } + + /** + * @see DATACMNS-363 + */ + @Test + public void parsesNestedSpecialUnicodeCharactersMixedWithRegularCharactersCorrectly_KoreanNumbersSymbols() { + + PartTree tree = new PartTree( // + "findBy" + "이름" // + + "And" + "OrderId" // + + "And" + "Anders" // + + "And" + "Property1" // + + "And" + "Øre" // + + "And" + "År" // + + "Or" + "NestedOrderId" // + + "And" + "Nested_property1" // we use _ here to mark the beginning of a new property reference "이름" + + "And" + "Property1" // + + "OrderBy" + "생일" + "Asc", DomainObjectWithSpecialChars.class); + + Iterator parts = tree.iterator(); + assertPartsIn(parts.next(), new Part[] { // + new Part("이름", DomainObjectWithSpecialChars.class), // + new Part("order.id", DomainObjectWithSpecialChars.class), // + new Part("anders", DomainObjectWithSpecialChars.class), // + new Part("property1", DomainObjectWithSpecialChars.class), // + new Part("øre", DomainObjectWithSpecialChars.class), // + new Part("år", DomainObjectWithSpecialChars.class) // + }); + assertPartsIn(parts.next(), new Part[] { // + new Part("nested.order.id", DomainObjectWithSpecialChars.class), // + new Part("nested.property1", DomainObjectWithSpecialChars.class), // + new Part("property1", DomainObjectWithSpecialChars.class) // + }); + + assertTrue(tree.getSort().getOrderFor("생일").isAscending()); + } + /** * @see DATACMNS-303 */ @@ -374,6 +462,14 @@ public class PartTreeUnitTests { assertThat(new PartTree("findByAnders", Product.class), is(notNullValue())); } + /** + * @see DATACMNS-368 + */ + @Test + public void detectPropertyPathWithOrKeywordPart() { + assertThat(new PartTree("findByOrderId", Product.class), is(notNullValue())); + } + private static void assertType(Iterable sources, Type type, String property) { assertType(sources, type, property, 1, true); } @@ -411,18 +507,23 @@ public class PartTreeUnitTests { } private void assertPart(PartTree tree, Part[]... parts) { - Iterator iterator = tree.iterator(); + + Iterator orParts = tree.iterator(); for (Part[] part : parts) { - assertThat(iterator.hasNext(), is(true)); - Iterator partIterator = iterator.next().iterator(); - for (int k = 0; k < part.length; k++) { - assertThat(String.format("Expected %d parts but have %d", part.length, k), partIterator.hasNext(), is(true)); - Part next = partIterator.next(); - assertThat(String.format("Expected %s but got %s!", part[k], next), part[k], is(next)); - } - assertThat("Too many parts!", partIterator.hasNext(), is(false)); + assertThat(orParts.hasNext(), is(true)); + assertPartsIn(orParts.next(), part); } - assertThat("Too many or parts!", iterator.hasNext(), is(false)); + assertThat("Too many or parts!", orParts.hasNext(), is(false)); + } + + private void assertPartsIn(OrPart orPart, Part[] part) { + Iterator partIterator = orPart.iterator(); + for (int k = 0; k < part.length; k++) { + assertThat(String.format("Expected %d parts but have %d", part.length, k), partIterator.hasNext(), is(true)); + Part next = partIterator.next(); + assertThat(String.format("Expected %s but got %s!", part[k], next), part[k], is(next)); + } + assertThat("Too many parts!", partIterator.hasNext(), is(false)); } private static Collection toCollection(Iterable iterable) { @@ -452,9 +553,16 @@ public class PartTreeUnitTests { String øre; String år; - public Order getOrder() { - return null; - } + String 생일; // Birthday + String 이름; // Name + + int property1; + + Order order; + + Anders anders; + + DomainObjectWithSpecialChars nested; } interface Product {