Skip to content

Commit e88bd3f

Browse files
Merge pull request #21 from JavaWebStack/TimothyGillespie/addOrderByTests
Order By Query Clauses Test
2 parents 17a7060 + 3bff425 commit e88bd3f

File tree

8 files changed

+432
-47
lines changed

8 files changed

+432
-47
lines changed

src/main/java/org/javawebstack/orm/query/Query.java

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -296,20 +296,43 @@ public Query<T> search(String search) {
296296
return this;
297297
}
298298

299-
public Query<T> order(String orderBy) {
300-
return order(orderBy, false);
301-
}
302-
303-
public Query<T> order(String orderBy, boolean desc) {
304-
return order(new QueryColumn(orderBy), desc);
305-
}
306-
307-
public Query<T> order(QueryColumn orderBy, boolean desc) {
308-
boolean success = this.order.add(orderBy, desc);
299+
/**
300+
* Sorts the results by the given column name ascendingly.
301+
*
302+
* @param columnName The name of the column to sort ascendingly by.
303+
* @return The Query object with the given order by information added.
304+
* @throws ORMQueryException if the order operation is called twice on a column specification with the same name.
305+
*/
306+
public Query<T> order(String columnName) throws ORMQueryException {
307+
return order(columnName, false);
308+
}
309+
310+
/**
311+
* Sorts the results by the given column name with the given order direction.
312+
*
313+
* @param columnName The name of the column to sort ascendingly by.
314+
* @param desc If true it will order descendingly, if false it will order ascendingly.
315+
* @return The Query object with the given order by information added.
316+
* @throws ORMQueryException if the order operation is called twice on a column specification with the same name.
317+
*/
318+
public Query<T> order(String columnName, boolean desc) throws ORMQueryException {
319+
return order(new QueryColumn(columnName), desc);
320+
}
321+
322+
/**
323+
* Sorts the results by the given column with the given order direction.
324+
*
325+
* @param column The column encoded as QueryColumn object.
326+
* @param desc If true it will order descendingly, if false it will order ascendingly.
327+
* @return The Query object with the given order by information added.
328+
* @throws ORMQueryException if the order operation is called twice on a column specification with the same name.
329+
*/
330+
public Query<T> order(QueryColumn column, boolean desc) throws ORMQueryException{
331+
boolean success = this.order.add(column, desc);
309332
if(!success) {
310333
throw new ORMQueryException(String.format(
311334
"The column %s could not be ordered %s. This is probably caused by calling .order() on this column twice.",
312-
orderBy.toString(),
335+
column.toString(),
313336
desc ? "descendingly" : "ascendingly"
314337
));
315338
}

src/main/java/org/javawebstack/orm/query/QueryOrderBy.java

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,48 @@
11
package org.javawebstack.orm.query;
22

3+
import org.javawebstack.orm.TableInfo;
4+
35
import java.util.LinkedList;
4-
import java.util.List;
6+
import java.util.stream.Collectors;
57

8+
/**
9+
* The QueryOrderBy class serves as an aggregation of order by elements. It extends a list, because the order of the
10+
* order by statements is of relevance.
11+
*/
612
public class QueryOrderBy extends LinkedList<QueryOrderByElement>{
713

14+
/**
15+
* Add a new order by statement. If a statement with the same column name already exists it will not add the
16+
* statement.
17+
*
18+
* @param columnName The column name to order by.
19+
* @param desc If the column should be order descendingly.
20+
* @return True if adding the statement was successful. False otherwise.
21+
*/
822
public boolean add(String columnName, boolean desc) {
923
return this.add(new QueryColumn(columnName), desc);
1024
}
1125

26+
/**
27+
* Add a new order by statement. If a statement with the same column name already exists it will not add the
28+
* statement.
29+
*
30+
* @param column The column to be ordered by. It will retrieve the name from the QueryColumn.
31+
* @param desc If the column should be order descendingly.
32+
* @return True if adding the statement was successful. False otherwise.
33+
*/
1234
public boolean add(QueryColumn column, boolean desc) {
1335
return this.add(new QueryOrderByElement(column, desc));
1436
}
1537

1638
@Override
39+
/**
40+
* Add a new order by statement. If a statement with the same column name already exists it will not add the
41+
* statement.
42+
*
43+
* @param element The direct QueryOrderByElement which encodes the order by statement.
44+
* @return True if adding the statement was successful. False otherwise.
45+
*/
1746
public boolean add(QueryOrderByElement element) {
1847
boolean hasBeenAdded = false;
1948
if(!willOverwrite(element))
@@ -25,4 +54,17 @@ public boolean add(QueryOrderByElement element) {
2554
private boolean willOverwrite(QueryOrderByElement element) {
2655
return this.stream().anyMatch(element::hasEqualColumn);
2756
}
57+
58+
59+
// The toString methods are specific to MySQL so they might have to be replaced later on.
60+
@Override
61+
public String toString() {
62+
return toString(null);
63+
}
64+
65+
public String toString(TableInfo info) {
66+
return this.stream()
67+
.map(QueryOrderByElement::toString)
68+
.collect(Collectors.joining(","));
69+
}
2870
}
Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package org.javawebstack.orm.query;
22

3+
import org.javawebstack.orm.TableInfo;
4+
35
import java.util.Objects;
46

7+
/**
8+
* The QueryOrderByElement class encodes an Order By Statement.
9+
*/
510
public class QueryOrderByElement {
6-
private QueryColumn queryColumn;
7-
private boolean desc;
11+
private final QueryColumn queryColumn;
12+
private final boolean desc;
813

914
QueryOrderByElement(String columnName, boolean desc) {
1015
queryColumn = new QueryColumn(columnName);
@@ -16,28 +21,37 @@ public class QueryOrderByElement {
1621
this.desc = desc;
1722
}
1823

24+
/**
25+
* Retrieves the QueryColumn of the statement which encodes the column name.
26+
*
27+
* @return The encoding QueryColumn object.
28+
*/
1929
public QueryColumn getQueryColumn() {
2030
return queryColumn;
2131
}
2232

33+
/**
34+
* Retrieves the information if this column is ordered ascendingly or descendingly.
35+
*
36+
* @return false if ascending, true if descending.
37+
*/
2338
public boolean isDesc() {
2439
return desc;
2540
}
2641

42+
/**
43+
* Compares the encoded column name.
44+
*
45+
* @param o An object to compare to.
46+
* @return True if the object is a QueryOrderByElement with a QueryColumn with generates the same identifier.
47+
*/
2748
public boolean hasEqualColumn(Object o) {
2849
if (this == o) return true;
2950
if (o == null || getClass() != o.getClass()) return false;
3051
QueryOrderByElement that = (QueryOrderByElement) o;
3152
return getQueryColumn().equals(that.getQueryColumn());
3253
}
3354

34-
public boolean hasEqualOrderDirection(Object o) {
35-
if (this == o) return true;
36-
if (o == null || getClass() != o.getClass()) return false;
37-
QueryOrderByElement that = (QueryOrderByElement) o;
38-
return isDesc() == that.isDesc();
39-
}
40-
4155
@Override
4256
public boolean equals(Object o) {
4357
if (this == o) return true;
@@ -50,4 +64,17 @@ public boolean equals(Object o) {
5064
public int hashCode() {
5165
return Objects.hash(getQueryColumn(), isDesc());
5266
}
67+
68+
@Override
69+
public String toString() {
70+
return this.toString(null);
71+
}
72+
73+
public String toString(TableInfo info) {
74+
String stringifiedOrderBy = getQueryColumn().toString(info);
75+
if (isDesc())
76+
stringifiedOrderBy += " DESC";
77+
78+
return stringifiedOrderBy;
79+
}
5380
}

src/main/java/org/javawebstack/orm/wrapper/builder/MySQLQueryStringBuilder.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,13 @@ public SQLQueryString buildQuery(Query<?> query, boolean count) {
5050
sb.append(" WHERE ").append(qs.getQuery());
5151
parameters.addAll(qs.getParameters());
5252
}
53-
if (query.getOrder() != null) {
54-
sb.append(" ORDER BY ").append(query.getOrder().toString(repo.getInfo()));
55-
if (query.isDescOrder())
56-
sb.append(" DESC");
53+
54+
QueryOrderBy orderBy = query.getOrder();
55+
if (!orderBy.isEmpty()) {
56+
sb.append(" ORDER BY ")
57+
.append(orderBy.toString());
5758
}
59+
5860
Integer offset = query.getOffset();
5961
Integer limit = query.getLimit();
6062
if (offset != null && limit == null)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.javawebstack.orm.test.exception;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
6+
@Getter
7+
@Setter
8+
/**
9+
* Only to be used for tests.
10+
* This exception should be thrown when a SQL Query String is manually parsed and sections and section types are defined, and
11+
* a type of section is attempted to be retrieved which does not exist in this number.
12+
*/
13+
public class SectionIndexOutOfBoundException extends Exception {
14+
private int sectionCount;
15+
private int attemptedIndex;
16+
private String topLevelKeyword;
17+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package org.javawebstack.orm.test.querybuilding;
2+
3+
import org.javawebstack.orm.exception.ORMQueryException;
4+
import org.javawebstack.orm.query.Query;
5+
import org.javawebstack.orm.test.exception.SectionIndexOutOfBoundException;
6+
import org.javawebstack.orm.test.shared.models.Datatype;
7+
import org.javawebstack.orm.test.shared.verification.QueryVerification;
8+
import org.junit.jupiter.api.Test;
9+
10+
import javax.xml.crypto.Data;
11+
12+
import java.util.*;
13+
import java.util.stream.Collectors;
14+
15+
import static org.javawebstack.orm.test.shared.setup.ModelSetup.setUpModel;
16+
import static org.junit.jupiter.api.Assertions.*;
17+
18+
// This class tests the query generation for order by statements an MySQL
19+
public class OrderByClauseTest {
20+
21+
@Test
22+
void testOneExistingColumnDefaultOrderBy() {
23+
Query<Datatype> query = setUpModel(Datatype.class).query()
24+
.order("wrapper_integer");
25+
new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer`");
26+
}
27+
28+
@Test
29+
void testOneNonExistingColumnDefaultOrderBy() {
30+
Query<Datatype> query = setUpModel(Datatype.class).query()
31+
.order("does_not_exist");
32+
new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`");
33+
}
34+
35+
@Test
36+
void testOneExistingColumnASCOrderBy() {
37+
Query<Datatype> query = setUpModel(Datatype.class).query()
38+
.order("wrapper_integer", false);
39+
new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer`");
40+
}
41+
42+
@Test
43+
void testOneNonExistingColumnASCOrderBy() {
44+
Query<Datatype> query = setUpModel(Datatype.class).query()
45+
.order("does_not_exist", false);
46+
new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`");
47+
}
48+
49+
@Test
50+
void testOneExistingColumnDESCOrderBy() {
51+
Query<Datatype> query = setUpModel(Datatype.class).query()
52+
.order("wrapper_integer", true);
53+
new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer` DESC");
54+
}
55+
56+
@Test
57+
void testOneNonExistingColumnDESCOrderBy() {
58+
Query<Datatype> query = setUpModel(Datatype.class).query()
59+
.order("does_not_exist", true);
60+
new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` DESC");
61+
}
62+
63+
@Test
64+
void testMultipleOrderByClausesOfASCOrder() {
65+
Query<Datatype> query = setUpModel(Datatype.class).query()
66+
.order("wrapper_integer")
67+
.order("primitive_integer");
68+
69+
new QueryVerification(query)
70+
.assertSectionContains("ORDER BY", "`wrapper_integer`")
71+
.assertSectionContains("ORDER BY", "`primitive_integer`");
72+
}
73+
74+
@Test
75+
void testMultipleOrderByClausesOfDESCOrder() {
76+
Query<Datatype> query = setUpModel(Datatype.class).query()
77+
.order("wrapper_integer", true)
78+
.order("primitive_integer", true);
79+
80+
new QueryVerification(query)
81+
.assertSectionContains("ORDER BY", "`wrapper_integer` DESC")
82+
.assertSectionContains("ORDER BY", "`primitive_integer` DESC");
83+
}
84+
85+
@Test
86+
void testMultipleOrderByClausesOfMixedOrder() {
87+
Query<Datatype> query = setUpModel(Datatype.class).query()
88+
.order("wrapper_integer", false)
89+
.order("primitive_integer", true);
90+
91+
new QueryVerification(query)
92+
.assertSectionContains("ORDER BY", "`wrapper_integer`")
93+
.assertSectionContains("ORDER BY", "`primitive_integer` DESC");
94+
}
95+
96+
@Test
97+
void testMultipleOrderByClausesOfMixedOrderReversed() {
98+
Query<Datatype> query = setUpModel(Datatype.class).query()
99+
.order("primitive_integer", true)
100+
.order("wrapper_integer", false);
101+
102+
new QueryVerification(query)
103+
.assertSectionContains("ORDER BY", "`primitive_integer` DESC")
104+
.assertSectionContains("ORDER BY", "`wrapper_integer`");
105+
}
106+
107+
108+
@Test
109+
// This test is important because putting the order by statements in different order is relevant (they set priorities)
110+
void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionIndexOutOfBoundException {
111+
Query<Datatype> query = setUpModel(Datatype.class).query();
112+
ArrayList<String> columnNames = new ArrayList<>(Datatype.columnNames);
113+
114+
LinkedList<String> callOrder = new LinkedList<>();
115+
116+
Random r = new Random();
117+
columnNames.stream().unordered().forEach((singleColumn) -> {
118+
query.order(singleColumn, r.nextBoolean());
119+
callOrder.add(singleColumn);
120+
});
121+
122+
String queryString = new QueryVerification(query).getSection("ORDER BY");
123+
int lastIndex = 0;
124+
int foundIndex = -1;
125+
for (String nextInCallOrder : callOrder) {
126+
foundIndex = queryString.indexOf("`" + nextInCallOrder + "`");
127+
if(foundIndex < lastIndex) {
128+
if (foundIndex == -1)
129+
fail("Not all columns occurred in the query string.");
130+
else
131+
fail("The columns did not appear an the correct order.");
132+
133+
break;
134+
}
135+
136+
lastIndex = foundIndex;
137+
}
138+
139+
// If it came until here the test should count as passed.
140+
assertTrue(true);
141+
142+
}
143+
144+
/*
145+
* Error Cases
146+
*/
147+
148+
// This test might not be correct here as it does not purely look at the query
149+
@Test
150+
void testCannotCallOrderOnSameColumnTwice() {
151+
Query<Datatype> query = setUpModel(Datatype.class).query()
152+
.order("primitive_integer", true);
153+
154+
assertThrows(ORMQueryException.class, () -> query.order("primitive_integer"));
155+
}
156+
}

0 commit comments

Comments
 (0)