Added SQL Console View.
This commit is contained in:
parent
411f384775
commit
ea94f09702
|
@ -97,6 +97,7 @@ public class PerfinApp extends Application {
|
||||||
router.map("categories", PerfinApp.class.getResource("/categories-view.fxml"));
|
router.map("categories", PerfinApp.class.getResource("/categories-view.fxml"));
|
||||||
router.map("edit-category", PerfinApp.class.getResource("/edit-category.fxml"));
|
router.map("edit-category", PerfinApp.class.getResource("/edit-category.fxml"));
|
||||||
router.map("tags", PerfinApp.class.getResource("/tags-view.fxml"));
|
router.map("tags", PerfinApp.class.getResource("/tags-view.fxml"));
|
||||||
|
router.map("sql-console", PerfinApp.class.getResource("/sql-console-view.fxml"));
|
||||||
|
|
||||||
// Help pages.
|
// Help pages.
|
||||||
helpRouter.map("home", PerfinApp.class.getResource("/help-pages/home.fxml"));
|
helpRouter.map("home", PerfinApp.class.getResource("/help-pages/home.fxml"));
|
||||||
|
|
|
@ -4,6 +4,7 @@ import com.andrewlalis.javafx_scene_router.AnchorPaneRouterView;
|
||||||
import com.andrewlalis.perfin.view.BindingUtil;
|
import com.andrewlalis.perfin.view.BindingUtil;
|
||||||
import com.andrewlalis.perfin.view.ProfilesStage;
|
import com.andrewlalis.perfin.view.ProfilesStage;
|
||||||
import com.andrewlalis.perfin.view.component.ScrollPaneRouterView;
|
import com.andrewlalis.perfin.view.component.ScrollPaneRouterView;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
|
@ -102,4 +103,8 @@ public class MainViewController {
|
||||||
@FXML public void goToDashboard() {
|
@FXML public void goToDashboard() {
|
||||||
router.replace("dashboard");
|
router.replace("dashboard");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FXML public void goToSqlConsole() {
|
||||||
|
router.replace("sql-console");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
package com.andrewlalis.perfin.control;
|
||||||
|
|
||||||
|
import com.andrewlalis.javafx_scene_router.RouteSelectionListener;
|
||||||
|
import com.andrewlalis.perfin.data.impl.JdbcDataSource;
|
||||||
|
import com.andrewlalis.perfin.model.Profile;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.ButtonType;
|
||||||
|
import javafx.scene.control.Dialog;
|
||||||
|
import javafx.scene.control.DialogPane;
|
||||||
|
import javafx.scene.control.TextArea;
|
||||||
|
import javafx.stage.Modality;
|
||||||
|
import javafx.stage.StageStyle;
|
||||||
|
import javafx.stage.Window;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the SQL Console View, in which the user can write and execute
|
||||||
|
* arbitrary SQL queries on the database. This allows power users to create
|
||||||
|
* custom analytics queries and get exactly the data they want, without fiddling
|
||||||
|
* with user-friendly search fields.
|
||||||
|
*/
|
||||||
|
public class SqlConsoleViewController implements RouteSelectionListener {
|
||||||
|
|
||||||
|
@FXML public TextArea sqlEditorTextArea;
|
||||||
|
@FXML public TextArea outputTextArea;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRouteSelected(Object context) {
|
||||||
|
sqlEditorTextArea.clear();
|
||||||
|
outputTextArea.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void executeQuery() {
|
||||||
|
String queryText = sqlEditorTextArea.getText().strip();
|
||||||
|
String[] rawQueries = queryText.split("\\s*;\\s*");
|
||||||
|
List<String> queries = Arrays.stream(rawQueries)
|
||||||
|
.filter(s -> !s.isBlank())
|
||||||
|
.filter(s -> !s.startsWith("#") && !s.startsWith("//"))
|
||||||
|
.toList();
|
||||||
|
outputTextArea.clear();
|
||||||
|
JdbcDataSource dataSource = (JdbcDataSource) Profile.getCurrent().dataSource();
|
||||||
|
try (
|
||||||
|
var conn = dataSource.getConnection();
|
||||||
|
var stmt = conn.createStatement()
|
||||||
|
) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (int queryIdx = 0; queryIdx < queries.size(); queryIdx++) {
|
||||||
|
sb.append("Query ").append(queryIdx + 1).append(" of ").append(queries.size()).append(":\n");
|
||||||
|
String query = queries.get(queryIdx);
|
||||||
|
ResultSet rs = stmt.executeQuery(query);
|
||||||
|
int columnCount = rs.getMetaData().getColumnCount();
|
||||||
|
|
||||||
|
for (int i = 1; i <= columnCount; i++) {
|
||||||
|
sb.append(rs.getMetaData().getColumnLabel(i));
|
||||||
|
if (i < columnCount) sb.append(", ");
|
||||||
|
}
|
||||||
|
sb.append('\n');
|
||||||
|
while (rs.next()) {
|
||||||
|
for (int i = 1; i <= columnCount; i++) {
|
||||||
|
sb.append(rs.getString(i));
|
||||||
|
if (i < columnCount) sb.append(", ");
|
||||||
|
}
|
||||||
|
sb.append('\n');
|
||||||
|
}
|
||||||
|
if (queryIdx < queries.size() - 1) {
|
||||||
|
sb.append("-----\n\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputTextArea.setText(sb.toString());
|
||||||
|
} catch (SQLException e) {
|
||||||
|
outputTextArea.setText("Error: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FXML public void showSchema() {
|
||||||
|
SchemaDialog dialog = new SchemaDialog(sqlEditorTextArea.getScene().getWindow());
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class SchemaDialog extends Dialog<Void> {
|
||||||
|
public SchemaDialog(Window owner) {
|
||||||
|
DialogPane pane = new DialogPane();
|
||||||
|
TextArea schemaTextArea = new TextArea();
|
||||||
|
schemaTextArea.setEditable(false);
|
||||||
|
schemaTextArea.getStyleClass().addAll("mono-font", "small-font");
|
||||||
|
try (var in = SqlConsoleViewController.class.getResourceAsStream("/sql/schema.sql")) {
|
||||||
|
if (in == null) throw new IOException("Could not load database schema from resource location.");
|
||||||
|
String schemaStr = new String(in.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
schemaTextArea.setText(schemaStr);
|
||||||
|
} catch (IOException e) {
|
||||||
|
schemaTextArea.setText("Failed to load schema file!");
|
||||||
|
}
|
||||||
|
pane.setContent(schemaTextArea);
|
||||||
|
pane.getButtonTypes().add(ButtonType.OK);
|
||||||
|
|
||||||
|
initOwner(owner);
|
||||||
|
initModality(Modality.NONE);
|
||||||
|
initStyle(StageStyle.DECORATED);
|
||||||
|
setResizable(true);
|
||||||
|
setTitle("Perfin Database Schema");
|
||||||
|
setDialogPane(pane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,6 +17,7 @@
|
||||||
<Button text="Forward" onAction="#goForward"/>
|
<Button text="Forward" onAction="#goForward"/>
|
||||||
<Button text="Dashboard" onAction="#goToDashboard"/>
|
<Button text="Dashboard" onAction="#goToDashboard"/>
|
||||||
<Button text="Profiles" onAction="#viewProfiles"/>
|
<Button text="Profiles" onAction="#viewProfiles"/>
|
||||||
|
<Button text="SQL Console" onAction="#goToSqlConsole"/>
|
||||||
|
|
||||||
<Button text="View Help" fx:id="showManualButton" onAction="#showManual"/>
|
<Button text="View Help" fx:id="showManualButton" onAction="#showManual"/>
|
||||||
<Button text="Hide Help" fx:id="hideManualButton" onAction="#hideManual"/>
|
<Button text="Hide Help" fx:id="hideManualButton" onAction="#hideManual"/>
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.HBox?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<VBox
|
||||||
|
xmlns="http://javafx.com/javafx"
|
||||||
|
xmlns:fx="http://javafx.com/fxml"
|
||||||
|
fx:controller="com.andrewlalis.perfin.control.SqlConsoleViewController"
|
||||||
|
>
|
||||||
|
<ScrollPane fitToWidth="true" fitToHeight="true" VBox.vgrow="ALWAYS">
|
||||||
|
<TextArea
|
||||||
|
fx:id="sqlEditorTextArea"
|
||||||
|
promptText="Write your SQL query here..."
|
||||||
|
styleClass="mono-font,small-font"
|
||||||
|
/>
|
||||||
|
</ScrollPane>
|
||||||
|
<HBox styleClass="std-padding,std-spacing">
|
||||||
|
<Button text="Execute Query" onAction="#executeQuery"/>
|
||||||
|
<Button text="View Schema" onAction="#showSchema"/>
|
||||||
|
</HBox>
|
||||||
|
<ScrollPane fitToHeight="true" fitToWidth="true">
|
||||||
|
<TextArea
|
||||||
|
fx:id="outputTextArea"
|
||||||
|
styleClass="mono-font,small-font"
|
||||||
|
maxHeight="800"
|
||||||
|
/>
|
||||||
|
</ScrollPane>
|
||||||
|
</VBox>
|
|
@ -1,3 +1,11 @@
|
||||||
|
/*
|
||||||
|
+--------------------------+
|
||||||
|
| PERFIN Database Schema |
|
||||||
|
+--------------------------+
|
||||||
|
This file defines the relational database schema for Perfin. Various sections
|
||||||
|
are labeled below.
|
||||||
|
*/
|
||||||
|
|
||||||
CREATE TABLE account (
|
CREATE TABLE account (
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
||||||
created_at TIMESTAMP NOT NULL,
|
created_at TIMESTAMP NOT NULL,
|
||||||
|
@ -139,7 +147,8 @@ CREATE TABLE balance_record_attachment (
|
||||||
ON UPDATE CASCADE ON DELETE CASCADE
|
ON UPDATE CASCADE ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
/* HISTORY */
|
/* HISTORY ENTITIES */
|
||||||
|
|
||||||
CREATE TABLE history (
|
CREATE TABLE history (
|
||||||
id BIGINT PRIMARY KEY AUTO_INCREMENT
|
id BIGINT PRIMARY KEY AUTO_INCREMENT
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue