WIP SQL console: pagination

This commit is contained in:
Francesco 2023-10-23 10:26:31 +02:00
parent d3f516edaf
commit 10af1b9020
4 changed files with 199 additions and 11 deletions

View File

@ -66,6 +66,7 @@ import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.dto.FacetedSearchRequest;
import tech.ailef.dbadmin.external.dto.LogsSearchRequest;
import tech.ailef.dbadmin.external.dto.PaginatedResult;
import tech.ailef.dbadmin.external.dto.PaginationInfo;
import tech.ailef.dbadmin.external.dto.QueryFilter;
import tech.ailef.dbadmin.external.dto.ValidationErrorsContainer;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
@ -101,7 +102,7 @@ public class DefaultDbAdminController {
private ConsoleQueryRepository consoleQueryRepository;
@Autowired
private JdbcTemplate jdbTemplate;
private JdbcTemplate jdbcTemplate;
@Autowired
private UserSettingsRepository userSettingsRepo;
@ -596,7 +597,13 @@ public class DefaultDbAdminController {
@GetMapping("/console/run/{queryId}")
public String consoleRun(Model model, @RequestParam(required = false) String query,
@RequestParam(required = false) String queryTitle,
@RequestParam(required = false) Integer page,
@RequestParam(required = false) Integer pageSize,
@PathVariable String queryId) {
if (page == null || page <= 0) page = 1;
if (pageSize == null) pageSize = 50;
long startTime = System.currentTimeMillis();
if (!properties.isSqlConsoleEnabled()) {
@ -623,9 +630,10 @@ public class DefaultDbAdminController {
List<ConsoleQuery> tabs = consoleQueryRepository.findAll();
model.addAttribute("tabs", tabs);
List<DbQueryResultRow> results = new ArrayList<>();
if (activeQuery.getSql() != null && !activeQuery.getSql().isBlank()) {
try {
List<DbQueryResultRow> results = jdbTemplate.query(activeQuery.getSql(), (rs, rowNum) -> {
results = jdbcTemplate.query(activeQuery.getSql(), (rs, rowNum) -> {
Map<DbQueryOutputField, Object> result = new HashMap<>();
ResultSetMetaData metaData = rs.getMetaData();
@ -640,14 +648,32 @@ public class DefaultDbAdminController {
result.put(field, o);
}
return new DbQueryResultRow(result, query);
DbQueryResultRow row = new DbQueryResultRow(result, query);
result.keySet().forEach(f -> {
f.setResult(row);
});
return row;
});
model.addAttribute("results", new DbQueryResult(results));
} catch (DataAccessException e) {
model.addAttribute("error", e.getMessage());
}
}
if (!results.isEmpty()) {
int maxPage = (int)(Math.ceil ((double)results.size() / pageSize));
PaginationInfo pagination = new PaginationInfo(page, maxPage, pageSize, results.size(), null, null);
int startOffset = (page - 1) * pageSize;
int endOffset = (page) * pageSize;
endOffset = Math.min(results.size(), endOffset);
results = results.subList(startOffset, endOffset);
model.addAttribute("pagination", pagination);
model.addAttribute("results", new DbQueryResult(results));
}
double elapsedTime = (System.currentTimeMillis() - startTime) / 1000.0;
model.addAttribute("elapsedTime", new DecimalFormat("0.0#").format(elapsedTime));
return "console";

View File

@ -4,8 +4,10 @@ import java.util.Objects;
import tech.ailef.dbadmin.external.DbAdmin;
import tech.ailef.dbadmin.external.dbmapping.DbField;
import tech.ailef.dbadmin.external.dbmapping.DbFieldType;
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
import tech.ailef.dbadmin.external.exceptions.UnsupportedFieldTypeException;
public class DbQueryOutputField {
private String name;
@ -14,6 +16,8 @@ public class DbQueryOutputField {
private DbField dbField;
private DbQueryResultRow result;
public DbQueryOutputField(String name, String table, DbAdmin dbAdmin) {
this.name = name;
this.table = table;
@ -29,14 +33,26 @@ public class DbQueryOutputField {
}
}
/**
* Returns the column name of the field
* @return
*/
public String getName() {
return name;
}
/**
* Returns the table name of the field
* @return
*/
public String getTable() {
return table;
}
/**
* Returns true if this field is a primary key
* @return
*/
public boolean isPrimaryKey() {
return dbField != null && dbField.isPrimaryKey();
}
@ -67,26 +83,56 @@ public class DbQueryOutputField {
}
/**
* Returns the type of the field, only in the case the field
* has been mapped to a table
* Returns the type of the field.
* If the field has been mapped to a table column returns the
* type of the column, otherwise tries to parse the field
* field type from the raw value returned by the database.
* @return
*/
public String getType() {
// If the field has been mapped to the database
if (dbField != null)
return dbField.getType().toString();
// If the row this fields belongs to is defined
if (result != null) {
try {
DbFieldType type = DbFieldType.fromClass(result.get(this).getClass());
return type.toString();
} catch (UnsupportedFieldTypeException e) {
return "-";
}
}
return "-";
}
/**
* Returns the Java name of the field, if mapped to a table column
* @return
*/
public String getJavaName() {
if (dbField == null) return null;
return dbField.getJavaName();
}
/**
* Returns the Java class of the field, if mapped to a table column
* @return
*/
public String getEntityClassName() {
if (dbField == null) return null;
return dbField.getSchema().getClassName();
}
/**
* Sets the row object this field belongs to
* @param result
*/
public void setResult(DbQueryResultRow result) {
this.result = result;
}
@Override
public int hashCode() {
return Objects.hash(name, table);

View File

@ -48,10 +48,124 @@
<div th:if="${error == null && activeQuery.getSql() != null}">
<div class="separator mt-3 mb-3"></div>
<span class="text-muted mb-1 d-inline-block">
[[ ${results.size()} ]] results in [[ ${elapsedTime} ]] seconds
</span>
<!-- Pagination -->
<nav aria-label="Results pagination">
<div class="d-flex justify-content-between">
<div th:if="${pagination != null && pagination.getMaxPage() != 1}" class="d-flex">
<ul class="pagination me-3">
<li class="page-item" th:if="${pagination.getCurrentPage() != 1}">
<a class="page-link"
th:href="@{|${dbadmin_requestUrl}${pagination.getLink(pagination.getCurrentPage() - 1)}|}"
aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only">Previous</span>
</a>
</li>
<li class="page-item" th:each="p : ${pagination.getBeforePages()}">
<a class="page-link"
th:href="@{|${dbadmin_requestUrl}${pagination.getLink(p)}|}" th:text="${p}"></a>
</li>
<li class="page-item active">
<a class="page-link" href="#" th:text="${pagination.getCurrentPage()}"></a>
</li>
<li class="page-item" th:each="p : ${pagination.getAfterPages()}">
<a class="page-link"
th:href="@{|${dbadmin_requestUrl}${pagination.getLink(p)}|}"
th:text="${p}"></a>
</li>
<li class="page-item">
<a class="page-link"
th:if="${!pagination.isLastPage()}"
th:href="@{|${dbadmin_requestUrl}${pagination.getLink(pagination.getCurrentPage() + 1)}|}"
aria-label="Next">
<span class="sr-only">Next</span>
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
<div class="d-flex align-items-center" th:if="${pagination.getMaxPage() > 1}">
<p class="m-0 p-0">
<i>Showing [[ ${results.size()} ]] of [[ ${pagination.getMaxElement()} ]]
results in [[ ${elapsedTime} ]] seconds</i>
</p>
</div>
</div>
<div class="d-flex align-items-center" th:if="${pagination.getMaxPage() == 1}">
<p class="m-0 p-0">
<i>Showing [[ ${results.size()} ]] of [[ ${pagination.getMaxElement()} ]]
results in [[ ${elapsedTime} ]] seconds</i>
</p>
</div>
</div>
</nav>
<div th:replace="~{fragments/generic_table :: table(results=${results})}"></div>
<!-- Pagination -->
<nav aria-label="Results pagination">
<div class="d-flex justify-content-between">
<div th:if="${pagination != null && pagination.getMaxPage() != 1}" class="d-flex">
<ul class="pagination me-3">
<li class="page-item" th:if="${pagination.getCurrentPage() != 1}">
<a class="page-link"
th:href="@{|${dbadmin_requestUrl}${pagination.getLink(pagination.getCurrentPage() - 1)}|}"
aria-label="Previous">
<span aria-hidden="true">&laquo;</span>
<span class="sr-only">Previous</span>
</a>
</li>
<li class="page-item" th:each="p : ${pagination.getBeforePages()}">
<a class="page-link"
th:href="@{|${dbadmin_requestUrl}${pagination.getLink(p)}|}" th:text="${p}"></a>
</li>
<li class="page-item active">
<a class="page-link" href="#" th:text="${pagination.getCurrentPage()}"></a>
</li>
<li class="page-item" th:each="p : ${pagination.getAfterPages()}">
<a class="page-link"
th:href="@{|${dbadmin_requestUrl}${pagination.getLink(p)}|}"
th:text="${p}"></a>
</li>
<li class="page-item">
<a class="page-link"
th:if="${!pagination.isLastPage()}"
th:href="@{|${dbadmin_requestUrl}${pagination.getLink(pagination.getCurrentPage() + 1)}|}"
aria-label="Next">
<span class="sr-only">Next</span>
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</ul>
<div class="d-flex align-items-center" th:if="${pagination.getMaxPage() > 1}">
<p class="m-0 p-0">
<i>Showing [[ ${results.size()} ]] of [[ ${pagination.getMaxElement()} ]]
results in [[ ${elapsedTime} ]] seconds</i>
</p>
</div>
</div>
<div class="d-flex align-items-center" th:if="${pagination.getMaxPage() == 1}">
<p class="m-0 p-0">
<i>Showing [[ ${results.size()} ]] of [[ ${pagination.getMaxElement()} ]]
results in [[ ${elapsedTime} ]] seconds</i>
</p>
</div>
</div>
</nav>
</div>
<div th:if="${error != null}">
<div class="separator mt-3 mb-3"></div>

View File

@ -18,7 +18,9 @@
</th:block>
</th:block>
<th:block th:if="${!field.isBinary()}">
<span th:text="${object.get(field)}"></span>
<span th:if="${object.get(field) != null}" th:text="${object.get(field)}"></span>
<span th:if="${object.get(field) == null}" class="null-label font-monospace">NULL</span>
</th:block>
</th:block>
<!-- end data-row-field fragment -->