This commit is contained in:
Francesco 2023-09-20 11:21:48 +02:00
parent 3bd17e6e84
commit 154bb1fcb8
13 changed files with 193 additions and 78 deletions

View File

@ -2,7 +2,6 @@ package tech.ailef.dbadmin.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@ -25,16 +24,15 @@ import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import ch.qos.logback.core.joran.action.ParamAction;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import tech.ailef.dbadmin.DbAdmin;
import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
import tech.ailef.dbadmin.dbmapping.DbObject;
import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.dto.PaginatedResult;
import tech.ailef.dbadmin.dto.QueryFilter;
import tech.ailef.dbadmin.exceptions.DbAdminException;
import tech.ailef.dbadmin.exceptions.InvalidPageException;
import tech.ailef.dbadmin.misc.Utils;
@ -113,7 +111,11 @@ public class DefaultDbAdminController {
for (int i = 0; i < fields.size(); i++) {
QueryFilter toRemove =
new QueryFilter(fields.get(i), otherParams.get("remove_op").get(i), otherParams.get("remove_value").get(i));
new QueryFilter(
fields.get(i),
CompareOperator.valueOf(otherParams.get("remove_op").get(i).toUpperCase()),
otherParams.get("remove_value").get(i)
);
queryFilters.removeIf(f -> f.equals(toRemove));
}

View File

@ -1,6 +1,9 @@
package tech.ailef.dbadmin.dbmapping;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@ -13,6 +16,7 @@ import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.dto.QueryFilter;
@SuppressWarnings("rawtypes")
@ -89,29 +93,54 @@ public class AdvancedJpaRepository extends SimpleJpaRepository {
finalPredicates.add(queryPredicate);
}
for (QueryFilter filter : queryFilters) {
String op = filter.getOp();
String field = filter.getField();
String value = filter.getValue();
if (op.equalsIgnoreCase("equals")) {
finalPredicates.add(cb.equal(cb.lower(cb.toString(root.get(field))), value.toLowerCase()));
} else if (op.equalsIgnoreCase("contains")) {
if (queryFilters == null) queryFilters = new HashSet<>();
for (QueryFilter filter : queryFilters) {
CompareOperator op = filter.getOp();
String field = filter.getField();
String v = filter.getValue();
DbField dbField = schema.getFieldByJavaName(field);
Object value = dbField.getType().parseValue(v);
if (op == CompareOperator.STRING_EQ) {
finalPredicates.add(cb.equal(cb.lower(cb.toString(root.get(field))), value.toString().toLowerCase()));
} else if (op == CompareOperator.CONTAINS) {
finalPredicates.add(
cb.like(cb.lower(cb.toString(root.get(field))), "%" + value.toLowerCase() + "%")
cb.like(cb.lower(cb.toString(root.get(field))), "%" + value.toString().toLowerCase() + "%")
);
} else if (op.equalsIgnoreCase("eq")) {
} else if (op == CompareOperator.EQ) {
finalPredicates.add(
cb.equal(root.get(field), value)
);
} else if (op.equalsIgnoreCase("gt")) {
} else if (op == CompareOperator.GT) {
finalPredicates.add(
cb.greaterThan(root.get(field), value)
cb.greaterThan(root.get(field), value.toString())
);
} else if (op.equalsIgnoreCase("lt")) {
} else if (op == CompareOperator.LT) {
finalPredicates.add(
cb.lessThan(root.get(field), value)
cb.lessThan(root.get(field), value.toString())
);
} else if (op == CompareOperator.AFTER) {
if (value instanceof LocalDate)
finalPredicates.add(
cb.greaterThan(root.get(field), (LocalDate)value)
);
else if (value instanceof LocalDateTime)
finalPredicates.add(
cb.greaterThan(root.get(field), (LocalDateTime)value)
);
} else if (op == CompareOperator.BEFORE) {
if (value instanceof LocalDate)
finalPredicates.add(
cb.lessThan(root.get(field), (LocalDate)value)
);
else if (value instanceof LocalDateTime)
finalPredicates.add(
cb.lessThan(root.get(field), (LocalDateTime)value)
);
}
}
return finalPredicates;

View File

@ -119,7 +119,7 @@ public class DbAdminRepository {
return new PaginatedResult(
new PaginationInfo(page, maxPage, pageSize, maxElement, null, new HashSet<>()),
new PaginationInfo(page, maxPage, pageSize, maxElement, null, sortKey, sortOrder, new HashSet<>()),
results
);
}
@ -239,7 +239,7 @@ public class DbAdminRepository {
}
return new PaginatedResult(
new PaginationInfo(page, maxPage, pageSize, maxElement, query, queryFilters),
new PaginationInfo(page, maxPage, pageSize, maxElement, query, sortKey, sortOrder, queryFilters),
jpaRepository.search(query, page, pageSize, sortKey, sortOrder, queryFilters).stream()
.map(o -> new DbObject(o, schema))
.toList()

View File

@ -11,6 +11,7 @@ import org.springframework.web.multipart.MultipartFile;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.exceptions.DbAdminException;
public enum DbFieldType {
@ -32,8 +33,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("gt", "eq", "lt");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
DOUBLE {
@ -53,8 +54,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("gt", "eq", "lt");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
LONG {
@ -74,8 +75,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("gt", "eq", "lt");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
FLOAT {
@ -95,8 +96,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("gt", "eq", "lt");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
LOCAL_DATE {
@ -116,8 +117,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("After", "Equals", "Before");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
LOCAL_DATE_TIME {
@ -137,8 +138,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("After", "Equals", "Before");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.AFTER, CompareOperator.STRING_EQ, CompareOperator.BEFORE);
}
},
STRING {
@ -158,8 +159,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("Equals", "Contains");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.CONTAINS, CompareOperator.STRING_EQ);
}
},
BOOLEAN {
@ -179,8 +180,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("Equals");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.EQ);
}
},
BIG_DECIMAL {
@ -200,8 +201,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("gt", "eq", "lt");
public List<CompareOperator> getCompareOperators() {
return List.of(CompareOperator.GT, CompareOperator.EQ, CompareOperator.LT);
}
},
BYTE_ARRAY {
@ -225,8 +226,8 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
return List.of("Equals");
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException("Binary fields are not comparable");
}
},
ONE_TO_MANY {
@ -256,7 +257,7 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
@ -287,7 +288,7 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
@ -318,7 +319,7 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
},
@ -339,7 +340,7 @@ public enum DbFieldType {
}
@Override
public List<String> getCompareOperators() {
public List<CompareOperator> getCompareOperators() {
throw new DbAdminException();
}
};
@ -350,7 +351,7 @@ public enum DbFieldType {
public abstract Class<?> getJavaClass();
public abstract List<String> getCompareOperators();
public abstract List<CompareOperator> getCompareOperators();
public boolean isRelationship() {
return false;

View File

@ -1,7 +1,6 @@
package tech.ailef.dbadmin.dbmapping;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

View File

@ -0,0 +1,52 @@
package tech.ailef.dbadmin.dto;
public enum CompareOperator {
GT {
@Override
public String getDisplayName() {
return "Greater than";
}
},
LT {
@Override
public String getDisplayName() {
return "Less than";
}
},
EQ {
@Override
public String getDisplayName() {
return "Equals";
}
},
STRING_EQ {
@Override
public String getDisplayName() {
return "Equals";
}
},
BEFORE {
@Override
public String getDisplayName() {
return "Before";
}
},
AFTER {
@Override
public String getDisplayName() {
return "After";
}
},
CONTAINS {
@Override
public String getDisplayName() {
return "Contains";
}
};
public abstract String getDisplayName();
public String toString() {
return this.name().toLowerCase();
}
}

View File

@ -41,13 +41,20 @@ public class PaginationInfo {
private String query;
public PaginationInfo(int currentPage, int maxPage, int pageSize, long maxElement, String query, Set<QueryFilter> queryFilters) {
private String sortKey;
private String sortOrder;
public PaginationInfo(int currentPage, int maxPage, int pageSize, long maxElement, String query,
String sortKey, String sortOrder, Set<QueryFilter> queryFilters) {
this.currentPage = currentPage;
this.maxPage = maxPage;
this.pageSize = pageSize;
this.query = query;
this.maxElement = maxElement;
this.queryFilters = queryFilters;
this.sortKey = sortKey;
this.sortOrder = sortOrder;
}
public int getCurrentPage() {
@ -78,6 +85,22 @@ public class PaginationInfo {
return maxElement;
}
public String getSortedPageLink(String sortKey, String sortOrder) {
MultiValueMap<String, String> params = Utils.computeParams(queryFilters);
if (query != null) {
params.put("query", new ArrayList<>());
params.get("query").add(query);
}
params.add("pageSize", "" + pageSize);
params.add("page", "" + currentPage);
params.add("sortKey", sortKey);
params.add("sortOrder", sortOrder);
return Utils.getQueryString(params);
}
public String getLink(int page) {
MultiValueMap<String, String> params = Utils.computeParams(queryFilters);

View File

@ -5,11 +5,11 @@ import java.util.Objects;
public class QueryFilter {
private String field;
private String op;
private CompareOperator op;
private String value;
public QueryFilter(String field, String op, String value) {
public QueryFilter(String field, CompareOperator op, String value) {
this.field = field;
this.op = op;
this.value = value;
@ -19,7 +19,7 @@ public class QueryFilter {
return field;
}
public String getOp() {
public CompareOperator getOp() {
return op;
}
@ -34,7 +34,11 @@ public class QueryFilter {
@Override
public String toString() {
return field + " " + op + " '" + value + "'";
String displayValue = value;
if (value.length() > 10) {
displayValue = value.substring(0, 4) + "..." + value.substring(value.length() - 4);
}
return "'" + field + "' " + op.getDisplayName() + " '" + displayValue + "'";
}
@Override

View File

@ -8,6 +8,7 @@ import java.util.Set;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.dto.QueryFilter;
import tech.ailef.dbadmin.exceptions.DbAdminException;
@ -30,7 +31,7 @@ public interface Utils {
for (QueryFilter filter : filters) {
r.get("filter_field").add(filter.getField());
r.get("filter_op").add(filter.getOp());
r.get("filter_op").add(filter.getOp().toString());
r.get("filter_value").add(filter.getValue());
}
@ -59,7 +60,7 @@ public interface Utils {
String field = fields.get(i);
String value = values.get(i);
QueryFilter queryFilter = new QueryFilter(field, op, value);
QueryFilter queryFilter = new QueryFilter(field, CompareOperator.valueOf(op.toUpperCase()), value);
filters.add(queryFilter);
}

View File

@ -1,9 +1,7 @@
spring.datasource.url=jdbc:h2:file:./dbadmin
spring.datasource.username=sa
spring.datasource.password=password
#spring.datasource.url=jdbc:h2:file:./database
#spring.datasource.username=sa
#spring.datasource.password=password
#spring.h2.console.enabled=true
spring.jpa.show-sql=true
server.tomcat.relaxed-path-chars=[,]
#spring.jpa.show-sql=true

View File

@ -50,31 +50,40 @@
<form action="" method="GET">
<!-- Reset page when applying filter to start back at page 1 -->
<input type="hidden" name="page" value="1">
<input type="hidden" name="pageSize" th:value="${queryParams.getOrDefault('pageSize', ['50'])[0]}">
<input type="hidden" name="pageSize" th:value="${page.getPagination().getPageSize()}">
<input type="hidden" name="query" th:value="${query}">
<input type="hidden" name="filter_field" th:value="${field.getJavaName()}">
<div class="input-group pe-2">
<th:block th:if="${field.isForeignKey()}">
<div th:replace="~{fragments/forms :: input_autocomplete(field=${field}, value='')}">
<span class="input-group-text w-25">
<input type="hidden" name="filter_op" value="string_eq">
Equals
</span>
<div class="autocomplete-input position-relative w-50">
<input class="autocomplete form-control" type="text" name="filter_value"
th:data-classname="${field.getConnectedType().getName()}"
autocomplete="off"
placeholder="NULL">
</input>
<div class="suggestions d-none">
</div>
</div>
<!-- <input type="hidden" th:value="${field.getType()}" th:name="|__dbadmin_${field.getName()}_type|"> -->
</th:block>
<th:block th:unless="${field.isForeignKey()}">
<div class="container w-25">
<select class="form-select w-auto" name="filter_op">
<option th:value="${op}" th:each="op : ${field.getType().getCompareOperators()}" th:text="${op}">
</select>
</div>
<input type="hidden" name="filter_field" th:value="${field.getJavaName()}">
<select class="form-select w-25" name="filter_op">
<option th:value="${op}" th:each="op : ${field.getType().getCompareOperators()}"
th:text="${op.getDisplayName()}">
</select>
<input placeholder="NULL" th:type="${field.getType().getHTMLName()}"
name="filter_value"
class="form-control" th:id="|__id_${field.getName()}|"
class="form-control w-50" th:id="|__id_${field.getName()}|"
th:classAppend="${field.isPrimaryKey() && object != null ? 'disable' : ''}"
th:required="${!field.isNullable() && !field.isPrimaryKey()}"
step="any"
oninvalid="this.setCustomValidity('This field is not nullable.')"
oninput="this.setCustomValidity('')">
<!-- <input type="hidden" th:value="${field.getType()}" th:name="|__dbadmin_${field.getName()}_type|"> -->
</th:block>
<th:block th:each="p : ${queryParams.keySet()}">
@ -84,7 +93,7 @@
</th:block>
<button class="ui-btn btn btn-primary">Filter</button>
<button class="ui-btn btn btn-primary"><i class="bi bi-search text-white"></i></button>
</div>
</form>

View File

@ -27,21 +27,18 @@
</div>
<div class="align-items-center">
<h4 class="m-0" th:if="${page}">
<th:block th:if="${sortKey != field.getName()}" >
<a th:href="@{|/dbadmin/model/${schema.getClassName()}|(page=${page.getPagination().getCurrentPage()},
pageSize=${page.getPagination().getPageSize()},sortKey=${field.getName()},sortOrder=DESC)}">
<th:block th:if="${sortKey != field.getJavaName()}" >
<a th:href="@{|/dbadmin/model/${schema.getClassName()}${page.getPagination().getSortedPageLink(field.getJavaName(), 'DESC')}|}">
<i title="Sort" class="bi bi-caret-up"></i>
</a>
</th:block>
<th:block th:unless="${sortKey != field.getName()}">
<th:block th:unless="${sortKey != field.getJavaName()}">
<a th:if="${sortOrder == 'DESC'}"
th:href="@{|/dbadmin/model/${schema.getClassName()}|(page=${page.getPagination().getCurrentPage()},
pageSize=${page.getPagination().getPageSize()},sortKey=${field.getName()},sortOrder=ASC)}">
th:href="@{|/dbadmin/model/${schema.getClassName()}${page.getPagination().getSortedPageLink(field.getJavaName(), 'ASC')}|}">
<i title="Sort" class="bi bi-caret-down-fill"></i>
</a>
<a th:if="${sortOrder == 'ASC'}"
th:href="@{|/dbadmin/model/${schema.getClassName()}|(page=${page.getPagination().getCurrentPage()},
pageSize=${page.getPagination().getPageSize()},sortKey=${field.getName()},sortOrder=DESC)}">
th:href="@{|/dbadmin/model/${schema.getClassName()}${page.getPagination().getSortedPageLink(field.getJavaName(), 'DESC')}|}">
<i title="Sort" class="bi bi-caret-up-fill"></i>
</a>
</th:block>

View File

@ -47,7 +47,7 @@
<input type="hidden" name="page" value="1">
<input type="hidden" name="pageSize"
th:value="${queryParams.getOrDefault('pageSize', ['50'])[0]}">
th:value="${page.getPagination().getPageSize()}">
</form>
<div class="separator mb-4 mt-4"></div>