Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b527ee2
Add ModelSetup to unify setup codes
TimothyGillespie Mar 21, 2021
480b883
Merge branch 'master' into TimothyGillespie/addOrderByTests
TimothyGillespie Mar 31, 2021
49e4fa0
Add assertOrderByContains method
TimothyGillespie Mar 31, 2021
8aaedb2
Add SectionIndexOutOfBoundException
TimothyGillespie Mar 31, 2021
1ae8ac8
Add retrieval methods for the sections
TimothyGillespie Mar 31, 2021
5d4e233
Add JavaDocs for getSectionList too
TimothyGillespie Mar 31, 2021
89c48dc
Add JavaDocs for getSectionList too
TimothyGillespie Mar 31, 2021
51907a4
Reorder methods more logically
TimothyGillespie Mar 31, 2021
ac7140e
Add assertSectionEquals method
TimothyGillespie Mar 31, 2021
5491a48
Remove OrderBy specific contains method
TimothyGillespie Mar 31, 2021
6f97433
Make assertion methods chainable
TimothyGillespie Mar 31, 2021
b73d7db
Add better fitting internal assert for the equal assertion
TimothyGillespie Mar 31, 2021
710b6c5
Add tests for OrderBy Clauses
TimothyGillespie Mar 31, 2021
9e857cf
Merge branch 'TimothyGillespie/addOrderByTests' of github.com:JavaWeb…
TimothyGillespie Mar 31, 2021
e4d3fc0
Remove explicit ASC as discussed
TimothyGillespie Apr 1, 2021
0037a3a
Merge branch 'TimothyGillespie/enableMultipleOrderBy' into TimothyGil…
TimothyGillespie Apr 2, 2021
fb00e4d
Adjust MySQL Query Building for the new OrderBy and fix OrderBy test
TimothyGillespie Apr 2, 2021
018ce02
Add test to assert the correct sort priority
TimothyGillespie Apr 2, 2021
c8e9a40
Add error case for calling order on same column twice
TimothyGillespie Apr 2, 2021
37ed312
Add note of purpose of OrderByClauseTest
TimothyGillespie Apr 2, 2021
5a74b53
Add JavaDocs to QueryOrderBy
TimothyGillespie Apr 2, 2021
5210447
Add JavaDocs to QueryOrderByElement
TimothyGillespie Apr 2, 2021
3bff425
Add JavaDocs to the order method on Query
TimothyGillespie Apr 2, 2021
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
45 changes: 34 additions & 11 deletions src/main/java/org/javawebstack/orm/query/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -296,20 +296,43 @@ public Query<T> search(String search) {
return this;
}

public Query<T> order(String orderBy) {
return order(orderBy, false);
}

public Query<T> order(String orderBy, boolean desc) {
return order(new QueryColumn(orderBy), desc);
}

public Query<T> order(QueryColumn orderBy, boolean desc) {
boolean success = this.order.add(orderBy, desc);
/**
* Sorts the results by the given column name ascendingly.
*
* @param columnName The name of the column to sort ascendingly by.
* @return The Query object with the given order by information added.
* @throws ORMQueryException if the order operation is called twice on a column specification with the same name.
*/
public Query<T> order(String columnName) throws ORMQueryException {
return order(columnName, false);
}

/**
* Sorts the results by the given column name with the given order direction.
*
* @param columnName The name of the column to sort ascendingly by.
* @param desc If true it will order descendingly, if false it will order ascendingly.
* @return The Query object with the given order by information added.
* @throws ORMQueryException if the order operation is called twice on a column specification with the same name.
*/
public Query<T> order(String columnName, boolean desc) throws ORMQueryException {
return order(new QueryColumn(columnName), desc);
}

/**
* Sorts the results by the given column with the given order direction.
*
* @param column The column encoded as QueryColumn object.
* @param desc If true it will order descendingly, if false it will order ascendingly.
* @return The Query object with the given order by information added.
* @throws ORMQueryException if the order operation is called twice on a column specification with the same name.
*/
public Query<T> order(QueryColumn column, boolean desc) throws ORMQueryException{
boolean success = this.order.add(column, desc);
if(!success) {
throw new ORMQueryException(String.format(
"The column %s could not be ordered %s. This is probably caused by calling .order() on this column twice.",
orderBy.toString(),
column.toString(),
desc ? "descendingly" : "ascendingly"
));
}
Expand Down
44 changes: 43 additions & 1 deletion src/main/java/org/javawebstack/orm/query/QueryOrderBy.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,48 @@
package org.javawebstack.orm.query;

import org.javawebstack.orm.TableInfo;

import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

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

/**
* Add a new order by statement. If a statement with the same column name already exists it will not add the
* statement.
*
* @param columnName The column name to order by.
* @param desc If the column should be order descendingly.
* @return True if adding the statement was successful. False otherwise.
*/
public boolean add(String columnName, boolean desc) {
return this.add(new QueryColumn(columnName), desc);
}

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

@Override
/**
* Add a new order by statement. If a statement with the same column name already exists it will not add the
* statement.
*
* @param element The direct QueryOrderByElement which encodes the order by statement.
* @return True if adding the statement was successful. False otherwise.
*/
public boolean add(QueryOrderByElement element) {
boolean hasBeenAdded = false;
if(!willOverwrite(element))
Expand All @@ -25,4 +54,17 @@ public boolean add(QueryOrderByElement element) {
private boolean willOverwrite(QueryOrderByElement element) {
return this.stream().anyMatch(element::hasEqualColumn);
}


// The toString methods are specific to MySQL so they might have to be replaced later on.
@Override
public String toString() {
return toString(null);
}

public String toString(TableInfo info) {
return this.stream()
.map(QueryOrderByElement::toString)
.collect(Collectors.joining(","));
}
}
45 changes: 36 additions & 9 deletions src/main/java/org/javawebstack/orm/query/QueryOrderByElement.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package org.javawebstack.orm.query;

import org.javawebstack.orm.TableInfo;

import java.util.Objects;

/**
* The QueryOrderByElement class encodes an Order By Statement.
*/
public class QueryOrderByElement {
private QueryColumn queryColumn;
private boolean desc;
private final QueryColumn queryColumn;
private final boolean desc;

QueryOrderByElement(String columnName, boolean desc) {
queryColumn = new QueryColumn(columnName);
Expand All @@ -16,28 +21,37 @@ public class QueryOrderByElement {
this.desc = desc;
}

/**
* Retrieves the QueryColumn of the statement which encodes the column name.
*
* @return The encoding QueryColumn object.
*/
public QueryColumn getQueryColumn() {
return queryColumn;
}

/**
* Retrieves the information if this column is ordered ascendingly or descendingly.
*
* @return false if ascending, true if descending.
*/
public boolean isDesc() {
return desc;
}

/**
* Compares the encoded column name.
*
* @param o An object to compare to.
* @return True if the object is a QueryOrderByElement with a QueryColumn with generates the same identifier.
*/
public boolean hasEqualColumn(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
QueryOrderByElement that = (QueryOrderByElement) o;
return getQueryColumn().equals(that.getQueryColumn());
}

public boolean hasEqualOrderDirection(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
QueryOrderByElement that = (QueryOrderByElement) o;
return isDesc() == that.isDesc();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -50,4 +64,17 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(getQueryColumn(), isDesc());
}

@Override
public String toString() {
return this.toString(null);
}

public String toString(TableInfo info) {
String stringifiedOrderBy = getQueryColumn().toString(info);
if (isDesc())
stringifiedOrderBy += " DESC";

return stringifiedOrderBy;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,13 @@ public SQLQueryString buildQuery(Query<?> query, boolean count) {
sb.append(" WHERE ").append(qs.getQuery());
parameters.addAll(qs.getParameters());
}
if (query.getOrder() != null) {
sb.append(" ORDER BY ").append(query.getOrder().toString(repo.getInfo()));
if (query.isDescOrder())
sb.append(" DESC");

QueryOrderBy orderBy = query.getOrder();
if (!orderBy.isEmpty()) {
sb.append(" ORDER BY ")
.append(orderBy.toString());
}

Integer offset = query.getOffset();
Integer limit = query.getLimit();
if (offset != null && limit == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.javawebstack.orm.test.exception;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
/**
* Only to be used for tests.
* This exception should be thrown when a SQL Query String is manually parsed and sections and section types are defined, and
* a type of section is attempted to be retrieved which does not exist in this number.
*/
public class SectionIndexOutOfBoundException extends Exception {
private int sectionCount;
private int attemptedIndex;
private String topLevelKeyword;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package org.javawebstack.orm.test.querybuilding;

import org.javawebstack.orm.exception.ORMQueryException;
import org.javawebstack.orm.query.Query;
import org.javawebstack.orm.test.exception.SectionIndexOutOfBoundException;
import org.javawebstack.orm.test.shared.models.Datatype;
import org.javawebstack.orm.test.shared.verification.QueryVerification;
import org.junit.jupiter.api.Test;

import javax.xml.crypto.Data;

import java.util.*;
import java.util.stream.Collectors;

import static org.javawebstack.orm.test.shared.setup.ModelSetup.setUpModel;
import static org.junit.jupiter.api.Assertions.*;

// This class tests the query generation for order by statements an MySQL
public class OrderByClauseTest {

@Test
void testOneExistingColumnDefaultOrderBy() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("wrapper_integer");
new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer`");
}

@Test
void testOneNonExistingColumnDefaultOrderBy() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("does_not_exist");
new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`");
}

@Test
void testOneExistingColumnASCOrderBy() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("wrapper_integer", false);
new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer`");
}

@Test
void testOneNonExistingColumnASCOrderBy() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("does_not_exist", false);
new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist`");
}

@Test
void testOneExistingColumnDESCOrderBy() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("wrapper_integer", true);
new QueryVerification(query).assertSectionEquals("ORDER BY", "`wrapper_integer` DESC");
}

@Test
void testOneNonExistingColumnDESCOrderBy() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("does_not_exist", true);
new QueryVerification(query).assertSectionEquals("ORDER BY", "`does_not_exist` DESC");
}

@Test
void testMultipleOrderByClausesOfASCOrder() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("wrapper_integer")
.order("primitive_integer");

new QueryVerification(query)
.assertSectionContains("ORDER BY", "`wrapper_integer`")
.assertSectionContains("ORDER BY", "`primitive_integer`");
}

@Test
void testMultipleOrderByClausesOfDESCOrder() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("wrapper_integer", true)
.order("primitive_integer", true);

new QueryVerification(query)
.assertSectionContains("ORDER BY", "`wrapper_integer` DESC")
.assertSectionContains("ORDER BY", "`primitive_integer` DESC");
}

@Test
void testMultipleOrderByClausesOfMixedOrder() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("wrapper_integer", false)
.order("primitive_integer", true);

new QueryVerification(query)
.assertSectionContains("ORDER BY", "`wrapper_integer`")
.assertSectionContains("ORDER BY", "`primitive_integer` DESC");
}

@Test
void testMultipleOrderByClausesOfMixedOrderReversed() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("primitive_integer", true)
.order("wrapper_integer", false);

new QueryVerification(query)
.assertSectionContains("ORDER BY", "`primitive_integer` DESC")
.assertSectionContains("ORDER BY", "`wrapper_integer`");
}


@Test
// This test is important because putting the order by statements in different order is relevant (they set priorities)
void testMultipleOrderByClausesOfRandomOrderForCorrectOrder() throws SectionIndexOutOfBoundException {
Query<Datatype> query = setUpModel(Datatype.class).query();
ArrayList<String> columnNames = new ArrayList<>(Datatype.columnNames);

LinkedList<String> callOrder = new LinkedList<>();

Random r = new Random();
columnNames.stream().unordered().forEach((singleColumn) -> {
query.order(singleColumn, r.nextBoolean());
callOrder.add(singleColumn);
});

String queryString = new QueryVerification(query).getSection("ORDER BY");
int lastIndex = 0;
int foundIndex = -1;
for (String nextInCallOrder : callOrder) {
foundIndex = queryString.indexOf("`" + nextInCallOrder + "`");
if(foundIndex < lastIndex) {
if (foundIndex == -1)
fail("Not all columns occurred in the query string.");
else
fail("The columns did not appear an the correct order.");

break;
}

lastIndex = foundIndex;
}

// If it came until here the test should count as passed.
assertTrue(true);

}

/*
* Error Cases
*/

// This test might not be correct here as it does not purely look at the query
@Test
void testCannotCallOrderOnSameColumnTwice() {
Query<Datatype> query = setUpModel(Datatype.class).query()
.order("primitive_integer", true);

assertThrows(ORMQueryException.class, () -> query.order("primitive_integer"));
}
}
Loading