Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add explicit join #1364

Merged
merged 1 commit into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,37 @@ protected String getJoinClauseFromFilters(FilterExpression filterExpression, boo
Collection<FilterPredicate> predicates = filterExpression.accept(visitor);

return predicates.stream()
.map(predicate -> extractJoinClause(predicate, skipFetches))
.map(predicate -> extractJoinClause(predicate.getPath(), skipFetches))
.collect(Collectors.joining(SPACE));
}


/**
* Extracts all the HQL JOIN clauses from given filter expression.
* @param sorting the sort expression to extract a join clause from
* @return an HQL join clause
*/
protected String getJoinClauseFromSort(Optional<Sorting> sorting) {
return getJoinClauseFromSort(sorting, false);
}

/**
* Extracts all the HQL JOIN clauses from given filter expression.
* @param sorting the sort expression to extract a join clause from
* @param skipFetches JOIN but don't FETCH JOIN a relationship.
* @return an HQL join clause
*/
protected String getJoinClauseFromSort(Optional<Sorting> sorting, boolean skipFetches) {
if (sorting.isPresent() && !sorting.get().isDefaultInstance()) {
Map<Path, Sorting.SortOrder> validSortingRules = sorting.get().getSortingPaths();
return validSortingRules.keySet().stream()
.map(path -> extractJoinClause(path, skipFetches))
.collect(Collectors.joining(SPACE));
}
return "";
}


/**
* Modifies the HQL query to add OFFSET and LIMIT.
* @param query The HQL query object
Expand All @@ -153,17 +180,17 @@ protected void addPaginationToQuery(Query query) {
}

/**
* Extracts a join clause from a filter predicate (if it exists).
* @param predicate The predicate to examine
* Extracts a join clause from a path (if it exists).
* @param path The path to examine
* @param skipFetches Don't fetch join
* @return A HQL string representing the join
*/
private String extractJoinClause(FilterPredicate predicate, boolean skipFetches) {
private String extractJoinClause(Path path, boolean skipFetches) {
StringBuilder joinClause = new StringBuilder();

String previousAlias = null;

for (Path.PathElement pathElement : predicate.getPath().getPathElements()) {
for (Path.PathElement pathElement : path.getPathElements()) {
String fieldName = pathElement.getFieldName();
Class<?> typeClass = dictionary.lookupEntityClass(pathElement.getType());
String typeAlias = getTypeAlias(typeClass);
Expand Down Expand Up @@ -236,6 +263,7 @@ protected String extractToOneMergeJoins(Class<?> entityClass, String alias,
joinString.append(" LEFT JOIN FETCH ");
joinString.append(joinKey);
joinString.append(SPACE);
alreadyJoined.add(joinKey);
}
}
return joinString.toString();
Expand All @@ -244,21 +272,34 @@ protected String extractToOneMergeJoins(Class<?> entityClass, String alias,
/**
* Returns a sorting object into a HQL ORDER BY string.
* @param sorting The sorting object passed from the client
* @param sortClass The class to sort.
* @param prefixWithAlias Whether the sorting fields should be prefixed by an alias.
* @return The sorting clause
*/
protected String getSortClause(final Optional<Sorting> sorting, Class<?> sortClass, boolean prefixWithAlias) {
protected String getSortClause(final Optional<Sorting> sorting) {
String sortingRules = "";
if (sorting.isPresent() && !sorting.get().isDefaultInstance()) {
final Map<Path, Sorting.SortOrder> validSortingRules = sorting.get().getSortingPaths();
if (!validSortingRules.isEmpty()) {
final List<String> ordering = new ArrayList<>();
// pass over the sorting rules
validSortingRules.forEach((path, order) -> {
String prefix = (prefixWithAlias) ? getTypeAlias(sortClass) + PERIOD : "";

ordering.add(prefix + path.getFieldPath() + SPACE
String previousAlias = null;
String aliasedFieldName = null;

for (Path.PathElement pathElement : path.getPathElements()) {
String fieldName = pathElement.getFieldName();
Class<?> typeClass = dictionary.lookupEntityClass(pathElement.getType());
previousAlias = previousAlias == null
? getTypeAlias(typeClass)
: previousAlias;
aliasedFieldName = previousAlias + PERIOD + fieldName;

if (!dictionary.isRelation(pathElement.getType(), fieldName)) {
break;
}

previousAlias = appendAlias(previousAlias, fieldName);
}
ordering.add(aliasedFieldName + SPACE
+ (order.equals(Sorting.SortOrder.desc) ? "desc" : "asc"));
});
sortingRules = " order by " + StringUtils.join(ordering, COMMA);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public Query build() {

//Build the JOIN clause
String joinClause = getJoinClauseFromFilters(filterExpression.get())
+ getJoinClauseFromSort(sorting)
+ extractToOneMergeJoins(entityClass, entityAlias);

boolean requiresDistinct = pagination.isPresent() && containsOneToMany(filterExpression.get());
Expand All @@ -75,7 +76,7 @@ public Query build() {
+ SPACE
+ filterClause
+ SPACE
+ getSortClause(sorting, entityClass, USE_ALIAS)
+ getSortClause(sorting)
);

//Fill in the query parameters
Expand All @@ -88,9 +89,10 @@ public Query build() {
+ AS
+ entityAlias
+ SPACE
+ getJoinClauseFromSort(sorting)
+ extractToOneMergeJoins(entityClass, entityAlias)
+ SPACE
+ getSortClause(sorting, entityClass, USE_ALIAS));
+ getSortClause(sorting));
}

addPaginationToQuery(query);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public Query build() {
String filterClause = new FilterTranslator().apply(fe, USE_ALIAS);

String joinClause = getJoinClauseFromFilters(filterExpression.get())
+ getJoinClauseFromSort(sorting)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic looks correct - but you are missing two files that need to have their sorting changed. SubCollectionPageTotalsQueryBuilder and RootCollectionPageTotalsQueryBuilder

+ extractToOneMergeJoins(relationship.getChildType(), childAlias);

//SELECT parent_children from Parent parent JOIN parent.children parent_children
Expand All @@ -88,7 +89,7 @@ public Query build() {
+ filterClause
+ " AND " + parentAlias + "=:" + parentAlias
+ SPACE
+ getSortClause(sorting, relationship.getChildType(), USE_ALIAS)
+ getSortClause(sorting)
);

supplyFilterQueryParameters(q, predicates);
Expand All @@ -99,9 +100,10 @@ public Query build() {
+ parentName + SPACE + parentAlias
+ JOIN
+ parentAlias + PERIOD + relationshipName + SPACE + childAlias
+ getJoinClauseFromSort(sorting)
+ extractToOneMergeJoins(relationship.getChildType(), childAlias)
+ " WHERE " + parentAlias + "=:" + parentAlias
+ getSortClause(sorting, relationship.getChildType(), USE_ALIAS)
+ getSortClause(sorting)
));

query.setParameter(parentAlias, relationship.getParent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ public void testSortClauseWithoutPrefix() {
sorting.put(TITLE, Sorting.SortOrder.asc);
sorting.put(GENRE, Sorting.SortOrder.desc);

String actual = getSortClause(Optional.of(new SortingImpl(sorting, Book.class, dictionary)), Book.class, NO_ALIAS);
String actual = getSortClause(Optional.of(new SortingImpl(sorting, Book.class, dictionary)));

String expected = " order by title asc,genre desc";
String expected = " order by example_Book.title asc,example_Book.genre desc";
assertEquals(expected, actual);
}

Expand All @@ -133,7 +133,7 @@ public void testSortClauseWithPrefix() {
sorting.put(TITLE, Sorting.SortOrder.asc);
sorting.put(GENRE, Sorting.SortOrder.desc);

String actual = getSortClause(Optional.of(new SortingImpl(sorting, Book.class, dictionary)), Book.class, USE_ALIAS);
String actual = getSortClause(Optional.of(new SortingImpl(sorting, Book.class, dictionary)));

String expected = " order by example_Book.title asc,example_Book.genre desc";
assertEquals(expected, actual);
Expand All @@ -144,9 +144,9 @@ public void testSortClauseWithJoin() {
Map<String, Sorting.SortOrder> sorting = new LinkedHashMap<>();
sorting.put(PUBLISHER + PERIOD + NAME, Sorting.SortOrder.asc);

String actual = getSortClause(Optional.of(new SortingImpl(sorting, Book.class, dictionary)), Book.class, NO_ALIAS);
String actual = getSortClause(Optional.of(new SortingImpl(sorting, Book.class, dictionary)));

String expected = " order by publisher.name asc";
String expected = " order by example_Book_publisher.name asc";
assertEquals(expected, actual);
}

Expand All @@ -155,7 +155,7 @@ public void testSortClauseWithInvalidJoin() {
Map<String, Sorting.SortOrder> sorting = new LinkedHashMap<>();
sorting.put(AUTHORS + PERIOD + NAME, Sorting.SortOrder.asc);

assertThrows(InvalidValueException.class, () -> getSortClause(Optional.of(new SortingImpl(sorting, Book.class, dictionary)), Book.class, NO_ALIAS));
assertThrows(InvalidValueException.class, () -> getSortClause(Optional.of(new SortingImpl(sorting, Book.class, dictionary))));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import example.Author;
import example.Book;
import example.Chapter;
import example.Editor;
import example.Publisher;

import org.junit.jupiter.api.BeforeAll;
Expand All @@ -40,6 +41,11 @@ public class RootCollectionFetchQueryBuilderTest {
private EntityDictionary dictionary;

private static final String TITLE = "title";
private static final String PUBLISHER = "publisher";
private static final String EDITOR = "editor";
private static final String PERIOD = ".";
private static final String NAME = "name";
private static final String FIRSTNAME = "firstName";
private RSQLFilterDialect filterParser;

@BeforeAll
Expand All @@ -49,6 +55,7 @@ public void initialize() {
dictionary.bindEntity(Author.class);
dictionary.bindEntity(Publisher.class);
dictionary.bindEntity(Chapter.class);
dictionary.bindEntity(Editor.class);
filterParser = new RSQLFilterDialect(dictionary, new CaseSensitivityStrategy.UseColumnCollation());
}

Expand Down Expand Up @@ -183,4 +190,61 @@ public void testRootFetchWithSortingAndFilters() {

assertEquals(expected, actual);
}

@Test
public void testSortingWithJoin() {
RootCollectionFetchQueryBuilder builder = new RootCollectionFetchQueryBuilder(
Book.class, dictionary, new TestSessionWrapper());

Map<String, Sorting.SortOrder> sorting = new HashMap<>();
sorting.put(PUBLISHER + PERIOD + NAME, Sorting.SortOrder.asc);


TestQueryWrapper query = (TestQueryWrapper) builder
.withPossibleSorting(Optional.of(new SortingImpl(sorting, Book.class, dictionary)))
.build();

String expected =
"SELECT example_Book FROM example.Book AS example_Book "
+ "LEFT JOIN FETCH example_Book.publisher example_Book_publisher "
+ "order by example_Book_publisher.name asc";

String actual = query.getQueryText();
actual = actual.trim().replaceAll(" +", " ");
actual = actual.replaceFirst(":id_\\w+", ":id_XXX");

assertEquals(expected, actual);
}

@Test
public void testRootFetchWithRelationshipSortingAndFilters() {
RootCollectionFetchQueryBuilder builder = new RootCollectionFetchQueryBuilder(
Book.class, dictionary, new TestSessionWrapper());

Map<String, Sorting.SortOrder> sorting = new HashMap<>();
sorting.put(PUBLISHER + PERIOD + EDITOR + PERIOD + FIRSTNAME, Sorting.SortOrder.desc);

Path.PathElement idPath = new Path.PathElement(Book.class, Chapter.class, "id");

FilterPredicate idPredicate = new InPredicate(idPath, 1);


TestQueryWrapper query = (TestQueryWrapper) builder
.withPossibleSorting(Optional.of(new SortingImpl(sorting, Book.class, dictionary)))
.withPossibleFilterExpression(Optional.of(idPredicate))
.build();

String expected =
"SELECT example_Book FROM example.Book AS example_Book"
+ " LEFT JOIN FETCH example_Book.publisher example_Book_publisher"
+ " LEFT JOIN example_Book_publisher.editor example_Book_publisher_editor"
+ " WHERE example_Book.id IN (:id_XXX)"
+ " order by example_Book_publisher_editor.firstName desc";

String actual = query.getQueryText();
actual = actual.trim().replaceAll(" +", " ");
actual = actual.replaceFirst(":id_\\w+", ":id_XXX");

assertEquals(expected, actual);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public class SubCollectionFetchQueryBuilderTest {
private static final String BOOKS = "books";
private static final String NAME = "name";
private static final String PUBLISHER = "publisher";
private static final String PERIOD = ".";
private static final String PUB1 = "Pub1";

@BeforeAll
Expand Down Expand Up @@ -236,4 +237,39 @@ public void testFetchJoinExcludesParent() {

assertEquals(expected, actual);
}

@Test
public void testSubCollectionFetchWithRelationshipSorting() {
Author author = new Author();
author.setId(1L);

Book book = new Book();
book.setId(2);

RelationshipImpl relationship = new RelationshipImpl(
Author.class,
Book.class,
BOOKS,
author,
Arrays.asList(book));

SubCollectionFetchQueryBuilder builder = new SubCollectionFetchQueryBuilder(relationship,
dictionary, new TestSessionWrapper());

Map<String, Sorting.SortOrder> sorting = new HashMap<>();
sorting.put(PUBLISHER + PERIOD + NAME, Sorting.SortOrder.asc);

TestQueryWrapper query = (TestQueryWrapper) builder
.withPossibleSorting(Optional.of(new SortingImpl(sorting, Book.class, dictionary)))
.build();

String expected = "SELECT example_Book FROM example.Author example_Author__fetch "
+ "JOIN example_Author__fetch.books example_Book "
+ "LEFT JOIN FETCH example_Book.publisher example_Book_publisher "
+ "WHERE example_Author__fetch=:example_Author__fetch order by example_Book_publisher.name asc";
String actual = query.getQueryText();
actual = actual.trim().replaceAll(" +", " ");

assertEquals(expected, actual);
}
}
Loading