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.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -25,16 +24,15 @@ import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException; import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.servlet.mvc.support.RedirectAttributes; 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.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import tech.ailef.dbadmin.DbAdmin; import tech.ailef.dbadmin.DbAdmin;
import tech.ailef.dbadmin.dbmapping.DbAdminRepository; import tech.ailef.dbadmin.dbmapping.DbAdminRepository;
import tech.ailef.dbadmin.dbmapping.DbObject; import tech.ailef.dbadmin.dbmapping.DbObject;
import tech.ailef.dbadmin.dbmapping.DbObjectSchema; import tech.ailef.dbadmin.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.dto.PaginatedResult; import tech.ailef.dbadmin.dto.PaginatedResult;
import tech.ailef.dbadmin.dto.QueryFilter; import tech.ailef.dbadmin.dto.QueryFilter;
import tech.ailef.dbadmin.exceptions.DbAdminException;
import tech.ailef.dbadmin.exceptions.InvalidPageException; import tech.ailef.dbadmin.exceptions.InvalidPageException;
import tech.ailef.dbadmin.misc.Utils; import tech.ailef.dbadmin.misc.Utils;
@ -113,7 +111,11 @@ public class DefaultDbAdminController {
for (int i = 0; i < fields.size(); i++) { for (int i = 0; i < fields.size(); i++) {
QueryFilter toRemove = 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)); queryFilters.removeIf(f -> f.equals(toRemove));
} }

View File

@ -1,6 +1,9 @@
package tech.ailef.dbadmin.dbmapping; package tech.ailef.dbadmin.dbmapping;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -13,6 +16,7 @@ import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Path; import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate; import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root; import jakarta.persistence.criteria.Root;
import tech.ailef.dbadmin.dto.CompareOperator;
import tech.ailef.dbadmin.dto.QueryFilter; import tech.ailef.dbadmin.dto.QueryFilter;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@ -89,29 +93,54 @@ public class AdvancedJpaRepository extends SimpleJpaRepository {
finalPredicates.add(queryPredicate); finalPredicates.add(queryPredicate);
} }
for (QueryFilter filter : queryFilters) {
String op = filter.getOp();
String field = filter.getField();
String value = filter.getValue();
if (op.equalsIgnoreCase("equals")) { if (queryFilters == null) queryFilters = new HashSet<>();
finalPredicates.add(cb.equal(cb.lower(cb.toString(root.get(field))), value.toLowerCase())); for (QueryFilter filter : queryFilters) {
} else if (op.equalsIgnoreCase("contains")) { 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( 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( finalPredicates.add(
cb.equal(root.get(field), value) cb.equal(root.get(field), value)
); );
} else if (op.equalsIgnoreCase("gt")) { } else if (op == CompareOperator.GT) {
finalPredicates.add( 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( 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; return finalPredicates;

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package tech.ailef.dbadmin.dbmapping; package tech.ailef.dbadmin.dbmapping;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; 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; 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.currentPage = currentPage;
this.maxPage = maxPage; this.maxPage = maxPage;
this.pageSize = pageSize; this.pageSize = pageSize;
this.query = query; this.query = query;
this.maxElement = maxElement; this.maxElement = maxElement;
this.queryFilters = queryFilters; this.queryFilters = queryFilters;
this.sortKey = sortKey;
this.sortOrder = sortOrder;
} }
public int getCurrentPage() { public int getCurrentPage() {
@ -78,6 +85,22 @@ public class PaginationInfo {
return maxElement; 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) { public String getLink(int page) {
MultiValueMap<String, String> params = Utils.computeParams(queryFilters); MultiValueMap<String, String> params = Utils.computeParams(queryFilters);

View File

@ -5,11 +5,11 @@ import java.util.Objects;
public class QueryFilter { public class QueryFilter {
private String field; private String field;
private String op; private CompareOperator op;
private String value; private String value;
public QueryFilter(String field, String op, String value) { public QueryFilter(String field, CompareOperator op, String value) {
this.field = field; this.field = field;
this.op = op; this.op = op;
this.value = value; this.value = value;
@ -19,7 +19,7 @@ public class QueryFilter {
return field; return field;
} }
public String getOp() { public CompareOperator getOp() {
return op; return op;
} }
@ -34,7 +34,11 @@ public class QueryFilter {
@Override @Override
public String toString() { 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 @Override

View File

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

View File

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

View File

@ -50,31 +50,40 @@
<form action="" method="GET"> <form action="" method="GET">
<!-- Reset page when applying filter to start back at page 1 --> <!-- Reset page when applying filter to start back at page 1 -->
<input type="hidden" name="page" value="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="query" th:value="${query}">
<input type="hidden" name="filter_field" th:value="${field.getJavaName()}">
<div class="input-group pe-2"> <div class="input-group pe-2">
<th:block th:if="${field.isForeignKey()}"> <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|"> --> </div>
</th:block> </th:block>
<th:block th:unless="${field.isForeignKey()}"> <th:block th:unless="${field.isForeignKey()}">
<div class="container w-25"> <select class="form-select w-25" name="filter_op">
<select class="form-select w-auto" name="filter_op"> <option th:value="${op}" th:each="op : ${field.getType().getCompareOperators()}"
<option th:value="${op}" th:each="op : ${field.getType().getCompareOperators()}" th:text="${op}"> th:text="${op.getDisplayName()}">
</select> </select>
</div>
<input type="hidden" name="filter_field" th:value="${field.getJavaName()}">
<input placeholder="NULL" th:type="${field.getType().getHTMLName()}" <input placeholder="NULL" th:type="${field.getType().getHTMLName()}"
name="filter_value" 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:classAppend="${field.isPrimaryKey() && object != null ? 'disable' : ''}"
th:required="${!field.isNullable() && !field.isPrimaryKey()}" th:required="${!field.isNullable() && !field.isPrimaryKey()}"
step="any" step="any"
oninvalid="this.setCustomValidity('This field is not nullable.')" oninvalid="this.setCustomValidity('This field is not nullable.')"
oninput="this.setCustomValidity('')"> oninput="this.setCustomValidity('')">
<!-- <input type="hidden" th:value="${field.getType()}" th:name="|__dbadmin_${field.getName()}_type|"> -->
</th:block> </th:block>
<th:block th:each="p : ${queryParams.keySet()}"> <th:block th:each="p : ${queryParams.keySet()}">
@ -84,7 +93,7 @@
</th:block> </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> </div>
</form> </form>

View File

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

View File

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