Added EVERYTHING

This commit is contained in:
Andrew Lalis 2019-02-21 19:43:02 +01:00 committed by andrewlalis
parent 6670c245c2
commit d6e84f47e7
11 changed files with 870 additions and 0 deletions

View File

@ -0,0 +1 @@
select * from primes;

View File

@ -0,0 +1,7 @@
CREATE TABLE primes (
id SERIAL NOT NULL,
value INT NOT NULL
);
INSERT INTO primes(value) VALUES
(2), (3), (5), (7), (11);

1
resources/template.sql Normal file
View File

@ -0,0 +1 @@
select * from primes;

View File

@ -0,0 +1,123 @@
package nl.andrewlalis;
import nl.andrewlalis.log.ExecutionLog;
import nl.andrewlalis.log.QueryAction;
import nl.andrewlalis.log.UpdateAction;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class DatabaseHelper {
private String host;
private int port;
private String user;
private String password;
private Window window;
private ExecutionLog executionLog;
public DatabaseHelper(String host, int port, String user, String password, Window window) {
this.host = host;
this.port = port;
this.user = user;
this.password = password;
this.window = window;
this.executionLog = new ExecutionLog();
}
/**
* Executes possibly many queries which are contained in one string.
* @param database The database name to connect to, or an empty string to connect to the user's database.
* @param queriesString The string of queries.
*/
public void executeQueries(String database, String queriesString) {
String url = String.format(
"jdbc:postgresql://%s:%4d/%s?user=%s&password=%s",
host,
port,
database,
user,
password);
try {
Connection conn = DriverManager.getConnection(url);
if (!conn.isValid(1000)) {
throw new SQLException("Invalid connection.");
}
List<String> queries = splitQueries(queriesString);
Statement st = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
for (String query : queries) {
try {
executeQuery(query, st);
} catch (SQLException e) {
window.appendOutput("Exception while executing statement: " + e.getMessage());
}
}
conn.close();
} catch (SQLException e) {
int previousChannel = window.getOutputChannel();
window.setOutputChannel(Window.OUTPUT_GENERAL);
window.appendOutput("Unexpected SQL Exception occurred. URL:\n" + url + "\n\tException: " + e.getMessage() + "\n\tSQL State: " + e.getSQLState());
window.setOutputChannel(previousChannel);
}
}
/**
* Executes a single query and outputs the results.
* @param query The query to execute. Must be only one query in the string.
* @param statement The statement used to execute the query.
*/
private void executeQuery(String query, Statement statement) throws SQLException {
if (isSQLStatementQuery(query)) {
// A result set is expected.
window.appendOutput("Executing query:\n" + query);
QueryAction action = new QueryAction(statement.executeQuery(query));
window.appendOutput(action.toString());
this.executionLog.recordAction(action);
} else {
// A result set is not expected.
window.appendOutput("Executing update:\n" + query);
UpdateAction action = new UpdateAction(statement.executeUpdate(query), query);
window.appendOutput(action.toString());
this.executionLog.recordAction(action);
}
}
/**
* Splits and cleans each query so that it will run properly.
* @param queriesString A string containing one or more queries to execute.
* @return A list of individual queries.
*/
private static List<String> splitQueries(String queriesString) {
String[] sections = queriesString.split(";");
List<String> strings = new ArrayList<>();
for (String section : sections) {
String s = section.trim();
if (!s.isEmpty()) {
strings.add(s);
}
}
return strings;
}
/**
* Determines if an SQL string is a query (it should return a result set)
* @param str The string to check.
* @return True if this is a query, or false if it is an update.
*/
private static boolean isSQLStatementQuery(String str) {
String upper = str.toUpperCase();
return upper.startsWith("SELECT");
}
}

View File

@ -0,0 +1,18 @@
package nl.andrewlalis;
import javax.swing.*;
public class Main {
public static final String APPLICATION_NAME = "SQL-Assesser";
public static void main(String[] args) {
Window window = new Window(APPLICATION_NAME);
window.pack();
window.setSize(1000, 800);
window.setVisible(true);
window.setLocationRelativeTo(null);
window.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
}
}

View File

@ -0,0 +1,381 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="nl.andrewlalis.Window">
<grid id="27dc6" binding="mainPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<xy x="20" y="20" width="1195" height="814"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="3de6c" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<horizontalAlignment value="0"/>
<text value="SQL-Assesser"/>
</properties>
</component>
<grid id="e44a4" binding="inputPanel" layout-manager="GridBagLayout">
<constraints border-constraint="Center"/>
<properties/>
<border type="none"/>
<children>
<grid id="bfff3" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<gridbag weightx="1.0" weighty="1.0"/>
</constraints>
<properties/>
<border type="bevel-lowered"/>
<children>
<component id="5a36e" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<horizontalAlignment value="0"/>
<text value="Template"/>
</properties>
</component>
<scrollpane id="4a4c7">
<constraints border-constraint="Center"/>
<properties>
<verticalScrollBarPolicy value="22"/>
</properties>
<border type="none"/>
<children>
<component id="c2e9b" class="javax.swing.JTextArea" binding="templateTextArea">
<constraints/>
<properties>
<text value="select * from primes;"/>
</properties>
</component>
</children>
</scrollpane>
<grid id="2493e" layout-manager="FlowLayout" hgap="5" vgap="5" flow-align="1">
<constraints border-constraint="South"/>
<properties/>
<border type="none"/>
<children>
<component id="15c71" class="javax.swing.JButton" binding="loadTemplateFromFileButton">
<constraints/>
<properties>
<text value="Load From File"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<grid id="ce988" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<gridbag weightx="1.0" weighty="1.0"/>
</constraints>
<properties/>
<border type="bevel-lowered"/>
<children>
<component id="e3b9a" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<horizontalAlignment value="0"/>
<text value="Testing"/>
</properties>
</component>
<scrollpane id="28e0d">
<constraints border-constraint="Center"/>
<properties>
<verticalScrollBarPolicy value="22"/>
</properties>
<border type="none"/>
<children>
<component id="17867" class="javax.swing.JTextArea" binding="testingTextArea">
<constraints/>
<properties>
<text value="select * from primes;"/>
</properties>
</component>
</children>
</scrollpane>
<grid id="7bdad" layout-manager="FlowLayout" hgap="5" vgap="5" flow-align="1">
<constraints border-constraint="South"/>
<properties/>
<border type="none"/>
<children>
<component id="781f9" class="javax.swing.JButton" binding="loadTestingFromFileButton">
<constraints/>
<properties>
<text value="Load From File"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<grid id="a5211" layout-manager="FlowLayout" hgap="5" vgap="5" flow-align="1">
<constraints>
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<gridbag weightx="1.0" weighty="0.0"/>
</constraints>
<properties/>
<border type="bevel-lowered"/>
<children>
<grid id="a0bad" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints/>
<properties/>
<border type="none"/>
<children>
<component id="1ff5c" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<text value="Host"/>
</properties>
</component>
<component id="99396" class="javax.swing.JTextField" binding="hostTextField">
<constraints border-constraint="Center"/>
<properties>
<text value="localhost"/>
</properties>
</component>
</children>
</grid>
<grid id="87583" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints/>
<properties/>
<border type="none"/>
<children>
<component id="274c3" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<text value="Port"/>
</properties>
</component>
<component id="44d44" class="javax.swing.JTextField" binding="portTextField">
<constraints border-constraint="Center"/>
<properties>
<text value="5432"/>
</properties>
</component>
</children>
</grid>
<grid id="881cf" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints/>
<properties/>
<border type="none"/>
<children>
<component id="a2ebb" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<text value="User"/>
</properties>
</component>
<component id="c33e3" class="javax.swing.JTextField" binding="userTextField">
<constraints border-constraint="Center"/>
<properties>
<text value="andrew"/>
</properties>
</component>
</children>
</grid>
<grid id="2eb2f" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints/>
<properties/>
<border type="none"/>
<children>
<component id="bf4a1" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<text value="Password"/>
</properties>
</component>
<component id="e2839" class="javax.swing.JTextField" binding="passwordTextField">
<constraints border-constraint="Center"/>
<properties>
<text value="root"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
</children>
</grid>
<grid id="5241a" binding="outputPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints border-constraint="South"/>
<properties/>
<border type="none"/>
<children>
<grid id="4eed0" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints border-constraint="South"/>
<properties/>
<border type="none"/>
<children>
<scrollpane id="adb12" binding="outputScrollPane">
<constraints border-constraint="Center"/>
<properties>
<preferredSize width="2" height="300"/>
<verticalScrollBarPolicy value="22"/>
</properties>
<border type="none"/>
<children>
<component id="aca43" class="javax.swing.JTextArea" binding="outputTextArea">
<constraints/>
<properties>
<editable value="false"/>
<lineWrap value="true"/>
<wrapStyleWord value="true"/>
</properties>
</component>
</children>
</scrollpane>
<component id="730ad" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<font style="1"/>
<horizontalAlignment value="0"/>
<text value="General Output"/>
</properties>
</component>
<grid id="6c3b1" layout-manager="FlowLayout" hgap="5" vgap="5" flow-align="1">
<constraints border-constraint="South"/>
<properties/>
<border type="none"/>
<children>
<component id="cfab2" class="javax.swing.JButton" binding="executeButton" default-binding="true">
<constraints/>
<properties>
<text value="Execute"/>
</properties>
</component>
<component id="167c9" class="javax.swing.JButton" binding="clearOutputButton" default-binding="true">
<constraints/>
<properties>
<text value="Clear Output"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
<grid id="4fb1a" layout-manager="GridBagLayout">
<constraints border-constraint="Center"/>
<properties>
<preferredSize width="70" height="200"/>
</properties>
<border type="none"/>
<children>
<grid id="60b96" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<gridbag weightx="1.0" weighty="1.0"/>
</constraints>
<properties>
<preferredSize width="105" height="300"/>
</properties>
<border type="none"/>
<children>
<component id="a32af" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<horizontalAlignment value="0"/>
<text value="Template Output"/>
</properties>
</component>
<scrollpane id="ae321">
<constraints border-constraint="Center"/>
<properties>
<verticalScrollBarPolicy value="22"/>
</properties>
<border type="none"/>
<children>
<component id="1e89d" class="javax.swing.JTextArea" binding="templateOutputTextArea">
<constraints/>
<properties>
<editable value="false"/>
</properties>
</component>
</children>
</scrollpane>
</children>
</grid>
<grid id="f3ff1" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
<gridbag weightx="1.0" weighty="1.0"/>
</constraints>
<properties>
<preferredSize width="93" height="300"/>
</properties>
<border type="none"/>
<children>
<component id="67bc9" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<horizontalAlignment value="0"/>
<text value="Testing Output"/>
</properties>
</component>
<scrollpane id="c31b2">
<constraints border-constraint="Center"/>
<properties>
<verticalScrollBarPolicy value="22"/>
</properties>
<border type="none"/>
<children>
<component id="f0d22" class="javax.swing.JTextArea" binding="testingOutputTextArea">
<constraints/>
<properties>
<editable value="false"/>
</properties>
</component>
</children>
</scrollpane>
</children>
</grid>
</children>
</grid>
</children>
</grid>
<grid id="1e3e8" binding="initializationPanel" layout-manager="BorderLayout" hgap="0" vgap="0">
<constraints border-constraint="West"/>
<properties/>
<border type="bevel-lowered"/>
<children>
<component id="3f0fa" class="javax.swing.JLabel">
<constraints border-constraint="North"/>
<properties>
<horizontalAlignment value="0"/>
<text value="Initialization"/>
</properties>
</component>
<scrollpane id="27433">
<constraints border-constraint="Center"/>
<properties>
<preferredSize width="400" height="18"/>
<verticalScrollBarPolicy value="22"/>
</properties>
<border type="none"/>
<children>
<component id="cc50b" class="javax.swing.JTextArea" binding="initializationTextArea">
<constraints/>
<properties>
<text value="CREATE TABLE primes (&#10;&#9;id SERIAL NOT NULL,&#10;&#9;value INT NOT NULL&#10;);&#10;&#10;INSERT INTO primes(value) VALUES&#10;(2), (3), (5), (7), (11);" noi18n="true"/>
</properties>
</component>
</children>
</scrollpane>
<grid id="2ca80" layout-manager="FlowLayout" hgap="5" vgap="5" flow-align="1">
<constraints border-constraint="South"/>
<properties/>
<border type="none"/>
<children>
<component id="ffb59" class="javax.swing.JButton" binding="loadInitializationFromFileButton">
<constraints/>
<properties>
<text value="Load From File"/>
</properties>
</component>
</children>
</grid>
</children>
</grid>
</children>
</grid>
</form>

View File

@ -0,0 +1,150 @@
package nl.andrewlalis;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Window extends JFrame {
private JPanel mainPanel;
private JPanel inputPanel;
private JPanel outputPanel;
private JTextArea outputTextArea;
private JButton executeButton;
private JTextArea templateTextArea;
private JTextArea testingTextArea;
private JTextField hostTextField;
private JTextField portTextField;
private JTextField userTextField;
private JTextField passwordTextField;
private JScrollPane outputScrollPane;
private JTextArea testingOutputTextArea;
private JTextArea templateOutputTextArea;
private JTextArea initializationTextArea;
private JPanel initializationPanel;
private JButton clearOutputButton;
private JButton loadTemplateFromFileButton;
private JButton loadTestingFromFileButton;
private JButton loadInitializationFromFileButton;
public static final int OUTPUT_GENERAL = 0;
public static final int OUTPUT_TEMPLATE = 1;
public static final int OUTPUT_TESTING = 2;
public static final String DB_TEMPLATE = "sql_assess_template";
public static final String DB_TESTING = "sql_assess_testing";
private int outputChannel;
private int outputIndent;
public Window(String applicationName) {
super(applicationName);
this.setOutputChannel(OUTPUT_GENERAL);
this.setContentPane(mainPanel);
executeButton.addActionListener(actionEvent -> {
this.executeSQL();
});
clearOutputButton.addActionListener(actionEvent -> {
this.templateOutputTextArea.setText(null);
this.testingOutputTextArea.setText(null);
this.outputTextArea.setText(null);
});
loadInitializationFromFileButton.addActionListener(actionEvent -> {
});
}
/**
* Executes the SQL in the two text areas, and provides output.
*/
private void executeSQL() {
this.setOutputChannel(OUTPUT_GENERAL);
String host = this.hostTextField.getText();
int port = Integer.parseInt(this.portTextField.getText());
String user = this.userTextField.getText();
String password = this.passwordTextField.getText();
String initialization = this.initializationTextArea.getText();
// Run the database code in a separate thread to update the UI quickly.
Thread t = new Thread(() -> {
DatabaseHelper dbHelper = new DatabaseHelper(host, port, user, password, this);
// Setup both databases.
this.appendOutput("Dropping old databases and re-creating them...");
this.indentOutput();
String dropDatabases = "DROP DATABASE " + DB_TEMPLATE + "; " +
"DROP DATABASE " + DB_TESTING + ";";
String createDatabases = "CREATE DATABASE " + DB_TEMPLATE + "; " +
"CREATE DATABASE " + DB_TESTING + ";";
dbHelper.executeQueries("", dropDatabases);
dbHelper.executeQueries("", createDatabases);
this.unindentOutput();
// Run initialization script on each database.
this.appendOutput("Running initialization SQL on databases...");
this.indentOutput();
dbHelper.executeQueries(DB_TEMPLATE, initialization);
dbHelper.executeQueries(DB_TESTING, initialization);
this.unindentOutput();
// Template-specific output.
this.setOutputChannel(OUTPUT_TEMPLATE);
dbHelper.executeQueries(DB_TEMPLATE, this.templateTextArea.getText());
// Testing-specific output.
this.setOutputChannel(OUTPUT_TESTING);
dbHelper.executeQueries(DB_TESTING, this.testingTextArea.getText());
});
t.start();
}
int getOutputChannel() {
return this.outputChannel;
}
void setOutputChannel(int channel) {
this.outputChannel = channel;
}
void indentOutput() {
this.outputIndent++;
}
void unindentOutput() {
this.outputIndent--;
}
/**
* Adds some text to the current output channel, followed by a new line.
* @param text The text to append.
*/
void appendOutput(String text) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < this.outputIndent; i++) {
sb.append('\t');
}
String tabs = sb.toString();
StringBuilder resultSb = new StringBuilder();
for (String line : text.split("\n")) {
resultSb.append(tabs).append(line).append('\n');
}
String result = resultSb.toString();
switch (this.outputChannel) {
case OUTPUT_GENERAL:
this.outputTextArea.append(result);
break;
case OUTPUT_TEMPLATE:
this.templateOutputTextArea.append(result);
break;
case OUTPUT_TESTING:
this.testingOutputTextArea.append(result);
break;
}
}
}

View File

@ -0,0 +1,7 @@
package nl.andrewlalis.log;
/**
* Represents an action performed on a database.
*/
public abstract class ExecutionAction {
}

View File

@ -0,0 +1,47 @@
package nl.andrewlalis.log;
import java.util.ArrayList;
import java.util.List;
/**
* Contains a log of all actions performed to a database.
*/
public class ExecutionLog {
private List<ExecutionAction> actions;
public ExecutionLog() {
this.actions = new ArrayList<>();
}
public void recordAction(ExecutionAction action) {
this.actions.add(action);
}
public List<ExecutionAction> getActions() {
return this.actions;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof ExecutionLog)) {
return false;
}
ExecutionLog otherLog = (ExecutionLog) other;
if (otherLog.getActions().size() != this.getActions().size()) {
return false;
}
List<ExecutionAction> otherLogActions = otherLog.getActions();
for (int i = 0; i < this.getActions().size(); i++) {
if (!this.getActions().get(i).equals(otherLogActions.get(i))) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,106 @@
package nl.andrewlalis.log;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* An action in which a query result set is returned.
*/
public class QueryAction extends ExecutionAction {
private String[] columns;
private String[][] values;
public QueryAction(ResultSet resultSet) throws SQLException {
// Read the columns into this object's memory.
ResultSetMetaData metaData = resultSet.getMetaData();
this.columns = new String[metaData.getColumnCount()];
for (int i = 0; i < metaData.getColumnCount(); i++) {
columns[i] = metaData.getColumnName(i + 1);
}
resultSet.absolute(1);// Ensure that this result set cursor is at the beginning.
// Read the rows into this object's memory.
List<String[]> rows = new ArrayList<>();
while (resultSet.next()) {
String[] row = new String[columns.length];
for (int i = 0; i < columns.length; i++) {
row[i] = resultSet.getString(i + 1);
}
rows.add(row);
}
this.values = new String[rows.size()][];
rows.toArray(this.values);
}
public String[] getColumns() {
return this.columns;
}
public String[][] getValues() {
return this.values;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof QueryAction)) {
return false;
}
QueryAction action = (QueryAction) other;
if (action.getColumns().length != this.columns.length || action.getValues().length != this.values.length) {
return false;
}
for (int i = 0; i < this.values.length; i++) {
Map<String, String> thisColumnValues = new HashMap<>();
Map<String, String> otherColumnValues = new HashMap<>();
for (int k = 0; k < this.values[i].length; k++) {
thisColumnValues.put(this.columns[k], this.values[i][k]);
otherColumnValues.put(action.getColumns()[k], action.getValues()[i][k]);
}
for (String column : this.columns) {
if (thisColumnValues.get(column).equals(otherColumnValues.get(column))) {
return false;
}
}
}
return true;
}
@Override
public String toString() {
// First build a list of columns.
StringBuilder sb = new StringBuilder("Query Result:\n\tColumns: (");
for (int i = 0; i < this.columns.length; i++) {
sb.append(this.columns[i]);
if (i < this.columns.length - 1) {
sb.append(", ");
}
}
sb.append(")\n\tValues:\n");
// Then build a list of the rows.
for (int i = 0; i < this.values.length; i++) {
sb.append("\t(");
for (int k = 0; k < this.values[i].length; k++) {
sb.append(this.values[i][k]);
if (k < this.values[i].length - 1) {
sb.append(", ");
}
}
sb.append(")\n");
}
return sb.toString();
}
}

View File

@ -0,0 +1,29 @@
package nl.andrewlalis.log;
/**
* Represents an action in which the schema or data was updated and no result set was returned.
*/
public class UpdateAction extends ExecutionAction {
private int rowsAffected;
public UpdateAction(int rowsAffected, String statement) {
this.rowsAffected = rowsAffected;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof UpdateAction)) {
return false;
}
UpdateAction action = (UpdateAction) other;
return action.rowsAffected == this.rowsAffected;
}
@Override
public String toString() {
return "Update result:\n\tRows affected: " + this.rowsAffected + "\n";
}
}