Skip to content

Commit

Permalink
[feat] Add the ability to filter on NULL values (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
ssando authored and darrachequesne committed Dec 15, 2016
1 parent 60b0141 commit 067d4e4
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 41 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.springframework.data.jpa.datatables.qrepository;

import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ESCAPED_NULL;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ESCAPED_OR_SEPARATOR;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.NULL;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.OR_SEPARATOR;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.isBoolean;

Expand Down Expand Up @@ -34,26 +36,52 @@ public static Predicate createPredicate(PathBuilder<?> entity, DataTablesInput i
}
if (filterValue.contains(OR_SEPARATOR)) {
// the filter contains multiple values, add a 'WHERE .. IN' clause
String[] values = filterValue.split(ESCAPED_OR_SEPARATOR);
if (values.length > 0 && isBoolean(values[0])) {
List<Boolean> booleanValues = new ArrayList<Boolean>();
for (int i = 0; i < values.length; i++) {
booleanValues.add(Boolean.valueOf(values[i]));
boolean nullable = false;
List<String> values = new ArrayList<String>();
for (String value : filterValue.split(ESCAPED_OR_SEPARATOR)) {
if (NULL.equals(value)) {
nullable = true;
} else {
values.add(ESCAPED_NULL.equals(value) ? NULL : value); // to match a 'NULL' string
}
predicate = predicate.and(entity.getBoolean(column.getData()).in(booleanValues));
} else {
predicate.and(getStringExpression(entity, column.getData()).in(values));
}
} else {
// the filter contains only one value, add a 'WHERE .. LIKE' clause
if (isBoolean(filterValue)) {
predicate =
predicate.and(entity.getBoolean(column.getData()).eq(Boolean.valueOf(filterValue)));
if (values.size() > 0 && isBoolean(values.get(0))) {
List<Boolean> booleanValues = new ArrayList<Boolean>();
for (int i = 0; i < values.size(); i++) {
booleanValues.add(Boolean.valueOf(values.get(i)));
}
Predicate in = entity.getBoolean(column.getData()).in(booleanValues);
if (nullable) {
predicate = predicate.and(entity.getBoolean(column.getData()).isNull().or(in));
} else {
predicate = predicate.and(in);
}
} else {
predicate = predicate.and(getStringExpression(entity, column.getData()).lower()
.like(getLikeFilterValue(filterValue), ESCAPE_CHAR));
Predicate in = getStringExpression(entity, column.getData()).in(values);
if (nullable) {
predicate = predicate.and(entity.get(column.getData()).isNull().or(in));
} else {
predicate = predicate.and(in);
}
}
continue;
}
// the filter contains only one value, add a 'WHERE .. LIKE' clause
if (isBoolean(filterValue)) {
predicate =
predicate.and(entity.getBoolean(column.getData()).eq(Boolean.valueOf(filterValue)));
continue;
}

StringExpression stringExpression = getStringExpression(entity, column.getData());
if (NULL.equals(filterValue)) {
predicate = predicate.and(stringExpression.isNull());
continue;
}

String likeFilterValue =
getLikeFilterValue(ESCAPED_NULL.equals(filterValue) ? NULL : filterValue);
predicate = predicate.and(stringExpression.lower().like(likeFilterValue, ESCAPE_CHAR));
}

// check whether a global filter value exists
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class DataTablesUtils {
public final static String ATTRIBUTE_SEPARATOR = ".";
public final static String ESCAPED_ATTRIBUTE_SEPARATOR = "\\.";
public final static char ESCAPE_CHAR = '\\';
public final static String NULL = "NULL";
public final static String ESCAPED_NULL = "\\NULL";

/**
* Creates a 'LIMIT .. OFFSET .. ORDER BY ..' clause for the given {@link DataTablesInput}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@

import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ATTRIBUTE_SEPARATOR;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ESCAPED_ATTRIBUTE_SEPARATOR;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ESCAPED_NULL;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ESCAPED_OR_SEPARATOR;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.ESCAPE_CHAR;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.NULL;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.OR_SEPARATOR;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.getLikeFilterValue;
import static org.springframework.data.jpa.datatables.repository.DataTablesUtils.isBoolean;

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
Expand Down Expand Up @@ -54,30 +57,55 @@ public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuild

if (filterValue.contains(OR_SEPARATOR)) {
// the filter contains multiple values, add a 'WHERE .. IN' clause
String[] values = filterValue.split(ESCAPED_OR_SEPARATOR);
if (values.length > 0 && isBoolean(values[0])) {
Object[] booleanValues = new Boolean[values.length];
for (int i = 0; i < values.length; i++) {
booleanValues[i] = Boolean.valueOf(values[i]);
boolean nullable = false;
List<String> values = new ArrayList<String>();
for (String value : filterValue.split(ESCAPED_OR_SEPARATOR)) {
if (NULL.equals(value)) {
nullable = true;
} else {
values.add(ESCAPED_NULL.equals(value) ? NULL : value); // to match a 'NULL' string
}
booleanExpression = getExpression(root, column.getData(), Boolean.class);
predicate = cb.and(predicate, booleanExpression.in(booleanValues));
} else {
stringExpression = getExpression(root, column.getData(), String.class);
predicate = cb.and(predicate, stringExpression.in(Arrays.asList(values)));
}
} else {
// the filter contains only one value, add a 'WHERE .. LIKE' clause
if (isBoolean(filterValue)) {
if (values.size() > 0 && isBoolean(values.get(0))) {
Object[] booleanValues = new Boolean[values.size()];
for (int i = 0; i < values.size(); i++) {
booleanValues[i] = Boolean.valueOf(values.get(i));
}
booleanExpression = getExpression(root, column.getData(), Boolean.class);
predicate =
cb.and(predicate, cb.equal(booleanExpression, Boolean.valueOf(filterValue)));
Predicate in = booleanExpression.in(booleanValues);
if (nullable) {
predicate = cb.and(predicate, cb.or(in, booleanExpression.isNull()));
} else {
predicate = cb.and(predicate, in);
}
} else {
stringExpression = getExpression(root, column.getData(), String.class);
predicate = cb.and(predicate,
cb.like(cb.lower(stringExpression), getLikeFilterValue(filterValue), ESCAPE_CHAR));
Predicate in = stringExpression.in(values);
if (nullable) {
predicate = cb.and(predicate, cb.or(in, stringExpression.isNull()));
} else {
predicate = cb.and(predicate, in);
}
}
continue;
}
// the filter contains only one value, add a 'WHERE .. LIKE' clause
if (isBoolean(filterValue)) {
booleanExpression = getExpression(root, column.getData(), Boolean.class);
predicate = cb.and(predicate, cb.equal(booleanExpression, Boolean.valueOf(filterValue)));
continue;
}

stringExpression = getExpression(root, column.getData(), String.class);
if (NULL.equals(filterValue)) {
predicate = cb.and(predicate, stringExpression.isNull());
continue;
}

String likeFilterValue =
getLikeFilterValue(ESCAPED_NULL.equals(filterValue) ? NULL : filterValue);
predicate =
cb.and(predicate, cb.like(cb.lower(stringExpression), likeFilterValue, ESCAPE_CHAR));
}

// check whether a global filter value exists
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ public void testWithoutFilter() {
DataTablesOutput<Bill> output = billRepository.findAll(input);
assertNotNull(output);
assertNull(output.getError());
assertEquals(12, output.getRecordsFiltered());
assertEquals(12, output.getRecordsTotal());
assertEquals(13, output.getRecordsFiltered());
assertEquals(13, output.getRecordsTotal());
}

@Test
Expand All @@ -44,6 +44,28 @@ public void testBooleanFilter() {
assertEquals(6, output.getRecordsFiltered());
}

@Test
public void testBooleanFilterAndNull() {
DataTablesInput input = getBasicInput();

input.getColumn("hasBeenPayed").setSearchValue("TRUE+NULL");
DataTablesOutput<Bill> output = billRepository.findAll(input);
assertNotNull(output);
assertNull(output.getError());
assertEquals(7, output.getRecordsFiltered());
}

@Test
public void testFilterIsNull() {
DataTablesInput input = getBasicInput();

input.getColumn("hasBeenPayed").setSearchValue("NULL");
DataTablesOutput<Bill> output = billRepository.findAll(input);
assertNotNull(output);
assertNull(output.getError());
assertEquals(1, output.getRecordsFiltered());
}

@Test
public void testBooleanFilter2() {
DataTablesInput input = getBasicInput();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,46 @@ public void testFilterOnSeveralColumns() {
assertEquals("ACTIVE", lastUser.getStatus().toString());
}

@Test
@Ignore
public void testNullColumnFilter() {
DataTablesInput input = getBasicInput();
input.getColumn("home.town").setSearchValue("town0+NULL");

DataTablesOutput<User> output = userRepository.findAll(input);
assertNotNull(output);
assertEquals(1, output.getDraw());
assertNull(output.getError());
assertEquals(10, output.getRecordsFiltered());
assertEquals(24, output.getRecordsTotal());
}

@Test
public void testEscapedOrNull() {
DataTablesInput input = getBasicInput();
input.getColumn("home.town").setSearchValue("town0+\\NULL");

DataTablesOutput<User> output = userRepository.findAll(input);
assertNotNull(output);
assertEquals(1, output.getDraw());
assertNull(output.getError());
assertEquals(6, output.getRecordsFiltered());
assertEquals(24, output.getRecordsTotal());
}

@Test
public void testEscapedNull() {
DataTablesInput input = getBasicInput();
input.getColumn("home.town").setSearchValue("\\NULL");

DataTablesOutput<User> output = userRepository.findAll(input);
assertNotNull(output);
assertEquals(1, output.getDraw());
assertNull(output.getError());
assertEquals(1, output.getRecordsFiltered());
assertEquals(24, output.getRecordsTotal());
}

@Test
public void testMultiFilterOnSameColumn() {
DataTablesInput input = getBasicInput();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public void testWithoutFilter() {
DataTablesOutput<Bill> output = billRepository.findAll(input);
assertNotNull(output);
assertNull(output.getError());
assertEquals(12, output.getRecordsFiltered());
assertEquals(12, output.getRecordsTotal());
assertEquals(13, output.getRecordsFiltered());
assertEquals(13, output.getRecordsTotal());
}

@Test
Expand All @@ -45,6 +45,28 @@ public void testBooleanFilter() {
assertEquals(6, output.getRecordsFiltered());
}

@Test
public void testBooleanFilterAndNull() {
DataTablesInput input = getBasicInput();

input.getColumn("hasBeenPayed").setSearchValue("TRUE+NULL");
DataTablesOutput<Bill> output = billRepository.findAll(input);
assertNotNull(output);
assertNull(output.getError());
assertEquals(7, output.getRecordsFiltered());
}

@Test
public void testFilterIsNull() {
DataTablesInput input = getBasicInput();

input.getColumn("hasBeenPayed").setSearchValue("NULL");
DataTablesOutput<Bill> output = billRepository.findAll(input);
assertNotNull(output);
assertNull(output.getError());
assertEquals(1, output.getRecordsFiltered());
}

@Test
public void testBooleanFilter2() {
DataTablesInput input = getBasicInput();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,45 @@ public void testFilterOnSeveralColumns() {
assertEquals("ACTIVE", lastUser.getStatus().toString());
}

@Test
public void testNullColumnFilter() {
DataTablesInput input = getBasicInput();
input.getColumn("home.town").setSearchValue("town0+NULL");

DataTablesOutput<User> output = userRepository.findAll(input);
assertNotNull(output);
assertEquals(1, output.getDraw());
assertNull(output.getError());
assertEquals(10, output.getRecordsFiltered());
assertEquals(24, output.getRecordsTotal());
}

@Test
public void testEscapedOrNull() {
DataTablesInput input = getBasicInput();
input.getColumn("home.town").setSearchValue("town0+\\NULL");

DataTablesOutput<User> output = userRepository.findAll(input);
assertNotNull(output);
assertEquals(1, output.getDraw());
assertNull(output.getError());
assertEquals(6, output.getRecordsFiltered());
assertEquals(24, output.getRecordsTotal());
}

@Test
public void testEscapedNull() {
DataTablesInput input = getBasicInput();
input.getColumn("home.town").setSearchValue("\\NULL");

DataTablesOutput<User> output = userRepository.findAll(input);
assertNotNull(output);
assertEquals(1, output.getDraw());
assertNull(output.getError());
assertEquals(1, output.getRecordsFiltered());
assertEquals(24, output.getRecordsTotal());
}

@Test
public void testMultiFilterOnSameColumn() {
DataTablesInput input = getBasicInput();
Expand Down
11 changes: 7 additions & 4 deletions src/test/resources/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ INSERT INTO bill (id, description, amount, hasBeenPayed) VALUES
(9, 'foo9', 900, false),
(10, 'foo10', 1000, true),
(11, 'foo11', 1100, false),
(12, 'foo12', 1200, true);
(12, 'foo12', 1200, true),
(13, 'foo13', 1300, NULL);

INSERT INTO game (id, prize_name) VALUES
(1, 'prize0'),
Expand All @@ -50,7 +51,9 @@ INSERT INTO home (id, town) VALUES
(1, 'town0'),
(2, 'town1'),
(3, 'town2'),
(4, 'town3');
(4, 'town3'),
(5, NULL),
(6, 'NULL');

INSERT INTO users (id, username, role, status, id_home, visible) VALUES
(1, 'john0', 'ADMIN', 'ACTIVE', null, true),
Expand All @@ -75,5 +78,5 @@ INSERT INTO users (id, username, role, status, id_home, visible) VALUES
(20, 'john19', 'AUTHOR', 'BLOCKED', 4, false),
(21, 'john20', 'USER', 'ACTIVE', 1, true),
(22, 'john21', 'ADMIN', 'BLOCKED', 2, false),
(23, 'john22', 'AUTHOR', 'ACTIVE', 3, true),
(24, 'john23', 'USER', 'BLOCKED', 4, false);
(23, 'john22', 'AUTHOR', 'ACTIVE', 6, true),
(24, 'john23', 'USER', 'BLOCKED', 5, false);

0 comments on commit 067d4e4

Please sign in to comment.