WIP SQL console: basic prototype working

This commit is contained in:
Francesco 2023-10-21 19:39:21 +02:00
parent 2352bdcdd7
commit 0691d17867
8 changed files with 221 additions and 20 deletions

View File

@ -19,7 +19,6 @@
package tech.ailef.dbadmin.external.controller; package tech.ailef.dbadmin.external.controller;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
@ -57,6 +56,9 @@ import tech.ailef.dbadmin.external.DbAdminProperties;
import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository; import tech.ailef.dbadmin.external.dbmapping.DbAdminRepository;
import tech.ailef.dbadmin.external.dbmapping.DbObject; import tech.ailef.dbadmin.external.dbmapping.DbObject;
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema; import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.external.dbmapping.query.DbQueryOutputField;
import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResult;
import tech.ailef.dbadmin.external.dbmapping.query.DbQueryResultRow;
import tech.ailef.dbadmin.external.dto.CompareOperator; import tech.ailef.dbadmin.external.dto.CompareOperator;
import tech.ailef.dbadmin.external.dto.FacetedSearchRequest; import tech.ailef.dbadmin.external.dto.FacetedSearchRequest;
import tech.ailef.dbadmin.external.dto.LogsSearchRequest; import tech.ailef.dbadmin.external.dto.LogsSearchRequest;
@ -543,10 +545,10 @@ public class DefaultDbAdminController {
@GetMapping("/console") @GetMapping("/console")
public String console(Model model, @RequestParam(required=false) String query) { public String console(Model model, @RequestParam(required=false) String query) {
model.addAttribute("activePage", "console"); model.addAttribute("activePage", "console");
model.addAttribute("query", query == null ? "" : query);
if (query != null) { if (query != null) {
List<Map<String, Object>> results = jdbTemplate.query(query, (rs, rowNum) -> { List<DbQueryResultRow> results = jdbTemplate.query(query, (rs, rowNum) -> {
Map<String, Object> result = new HashMap<>(); Map<DbQueryOutputField, Object> result = new HashMap<>();
ResultSetMetaData metaData = rs.getMetaData(); ResultSetMetaData metaData = rs.getMetaData();
int cols = metaData.getColumnCount(); int cols = metaData.getColumnCount();
@ -554,23 +556,15 @@ public class DefaultDbAdminController {
for (int i = 0; i < cols; i++) { for (int i = 0; i < cols; i++) {
Object o = rs.getObject(i + 1); Object o = rs.getObject(i + 1);
String columnName = metaData.getColumnName(i + 1); String columnName = metaData.getColumnName(i + 1);
result.put(columnName, o); String tableName = metaData.getTableName(i + 1);
DbQueryOutputField field = new DbQueryOutputField(columnName, tableName, dbAdmin);
result.put(field, o);
} }
return result; return new DbQueryResultRow(result, query);
}); });
model.addAttribute("results", new DbQueryResult(results));
/*
* Print each map in a tabular format
*/
for (Map<String, Object> obj : results) {
System.out.println("-----------------------------------------------------------");
for (String key : obj.keySet()) {
System.out.printf("%-20s | %s\n", key, obj.get(key));
}
}
} }

View File

@ -0,0 +1,74 @@
package tech.ailef.dbadmin.external.dbmapping.query;
import java.util.Objects;
import tech.ailef.dbadmin.external.DbAdmin;
import tech.ailef.dbadmin.external.dbmapping.DbField;
import tech.ailef.dbadmin.external.dbmapping.DbObjectSchema;
import tech.ailef.dbadmin.external.exceptions.DbAdminException;
public class DbQueryOutputField {
private String name;
private String table;
private DbField dbField;
public DbQueryOutputField(String name, String table, DbAdmin dbAdmin) {
this.name = name;
this.table = table;
try {
DbObjectSchema schema = dbAdmin.findSchemaByTableName(table);
DbField dbField = schema.getFieldByName(name);
this.dbField = dbField;
} catch (DbAdminException e) {
// We were unable to map this result column to a table, this happens
// for example with COUNT(*) results and similar. We ignore this
// as the dbField will be null and handled as such in the rest of the code
}
}
public String getName() {
return name;
}
public String getTable() {
return table;
}
public boolean isPrimaryKey() {
return dbField != null && dbField.isPrimaryKey();
}
public boolean isForeignKey() {
return dbField != null && dbField.isForeignKey();
}
public boolean isBinary() {
return dbField != null && dbField.isBinary();
}
public String getType() {
return "TODO TYPE";
}
@Override
public int hashCode() {
return Objects.hash(name, table);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
DbQueryOutputField other = (DbQueryOutputField) obj;
return Objects.equals(name, other.name) && Objects.equals(table, other.table);
}
}

View File

@ -0,0 +1,32 @@
package tech.ailef.dbadmin.external.dbmapping.query;
import java.util.ArrayList;
import java.util.List;
public class DbQueryResult {
private List<DbQueryResultRow> rows;
public DbQueryResult(List<DbQueryResultRow> rows) {
this.rows = rows;
}
public List<DbQueryResultRow> getRows() {
return rows;
}
public boolean isEmpty() {
return rows.isEmpty();
}
public List<DbQueryOutputField> getSortedFields() {
if (isEmpty()) {
return new ArrayList<>();
} else {
return rows.get(0).getSortedFields();
}
}
public int size() {
return rows.size();
}
}

View File

@ -0,0 +1,31 @@
package tech.ailef.dbadmin.external.dbmapping.query;
import java.util.List;
import java.util.Map;
public class DbQueryResultRow {
private Map<DbQueryOutputField, Object> values;
private String query;
public DbQueryResultRow(Map<DbQueryOutputField, Object> values, String query) {
this.values = values;
this.query = query;
}
public List<DbQueryOutputField> getSortedFields() {
return values.keySet().stream().sorted((f1, f2) -> f1.getName().compareTo(f2.getName())).toList();
}
public String getQuery() {
return query;
}
public Object get(DbQueryOutputField field) {
return values.get(field);
}
}

View File

@ -6,3 +6,4 @@
#spring.h2.console.enabled=true #spring.h2.console.enabled=true
#spring.jpa.show-sql=true #spring.jpa.show-sql=true

View File

@ -13,9 +13,14 @@
<div class="col"> <div class="col">
<div class="box"> <div class="box">
<form th:action="|/${dbadmin_baseUrl}/console|" method="GET"> <form th:action="|/${dbadmin_baseUrl}/console|" method="GET">
<textarea class="form-control" rows="6" name="query"></textarea> <textarea class="form-control" rows="6" name="query"
<input class="ui-btn btn btn-primary" type="submit" value="Run"> th:text="${query}"></textarea>
<input class="ui-btn btn btn-primary mt-3" type="submit" value="Run">
</form> </form>
<div class="separator mt-3 mb-3"></div>
<div th:replace="~{fragments/generic_table :: table(results=${results})}"></div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head></head>
<body>
<tr th:fragment="data_row(row)" class="table-data-row">
<td th:each="field : ${row.getSortedFields()}"
th:classAppend="${field.isBinary() ? 'text-center' : ''}">
<th:block th:replace="~{fragments/generic_data_row :: data_row_field(field=${field}, object=${row})}"></th:block>
</td>
</tr>
<!-- data-row-field fragment -->
<th:block th:fragment="data_row_field(field, object)">
<span th:text="${object.get(field)}"></span>
</th:block>
<!-- end data-row-field fragment -->
</body>
</html>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org">
<head></head>
<body>
<div th:fragment="table(results)">
<div th:if="${results == null || results.isEmpty()}">
<p class="alert alert-warning">No results.</p>
</div>
<div th:if="${results != null && results.size() > 0}">
<table class="table table-striped align-middle mt-3">
<tr class="table-data-row">
<th th:each="field : ${results.getSortedFields()}">
<div class="m-0 p-0 d-flex justify-content-between">
<div class="column-title">
<span th:if="${field.isPrimaryKey()}">
<i title="Primary Key" class="bi bi-key"></i>
</span>
<span th:if="${field.isForeignKey()}">
<i title="Foreign Key" class="bi bi-link"></i>
</span>
<span class="m-0 p-0" th:text="${field.getName()}"></span>
</div>
</div>
<p class="m-0 p-0 dbfieldtype"><small th:text="${field.getType()}"></small></p>
</th>
</tr>
<th:block th:each="r : ${results.getRows()}">
<tr th:replace="~{fragments/generic_data_row :: data_row(row=${r})}">
</tr>
</th:block>
</table>
</div>
</div>
</body>
</html>