diff --git a/src/nl/andrewlalis/DatabaseHelper.java b/src/nl/andrewlalis/DatabaseHelper.java index d1b759a..a2fdb99 100644 --- a/src/nl/andrewlalis/DatabaseHelper.java +++ b/src/nl/andrewlalis/DatabaseHelper.java @@ -1,5 +1,6 @@ package nl.andrewlalis; +import nl.andrewlalis.log.ExecutionAction; import nl.andrewlalis.log.ExecutionLog; import nl.andrewlalis.log.QueryAction; import nl.andrewlalis.log.UpdateAction; @@ -8,6 +9,8 @@ import java.sql.*; import java.util.ArrayList; import java.util.List; +import static nl.andrewlalis.Window.*; + public class DatabaseHelper { private String host; @@ -16,24 +19,60 @@ public class DatabaseHelper { 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(); + public void executeSQLComparison(String initializationSQL, String templateSQL, String testingSQL) { + // Run the database code in a separate thread to update the UI quickly. + Thread t = new Thread(() -> { + // Setup both databases. + this.window.appendOutput("Dropping old databases and re-creating them..."); + this.window.indentOutput(); + String dropDatabases = "DROP DATABASE " + DB_TEMPLATE + "; " + + "DROP DATABASE " + DB_TESTING + ";"; + String createDatabases = "CREATE DATABASE " + DB_TEMPLATE + "; " + + "CREATE DATABASE " + DB_TESTING + ";"; + this.executeQueries("", dropDatabases); + this.executeQueries("", createDatabases); + this.window.unindentOutput(); + + // Run initialization script on each database. + this.window.appendOutput("Running initialization SQL on databases..."); + this.window.indentOutput(); + this.executeQueries(DB_TEMPLATE, initializationSQL); + this.executeQueries(DB_TESTING, initializationSQL); + this.window.unindentOutput(); + + // TESTING SQL HERE + + // Template-specific output. + this.window.setOutputChannel(OUTPUT_TEMPLATE); + ExecutionLog templateLog = this.executeQueries(DB_TEMPLATE, templateSQL); + + // Testing-specific output. + this.window.setOutputChannel(OUTPUT_TESTING); + ExecutionLog testingLog = this.executeQueries(DB_TESTING, testingSQL); + + // Output results. + this.window.setOutputChannel(OUTPUT_GENERAL); + this.window.appendOutput("Execution test result: " + templateLog.equals(testingLog)); + }); + t.start(); } /** * 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. + * @return The execution log from this series of queries. */ - public void executeQueries(String database, String queriesString) { + public ExecutionLog executeQueries(String database, String queriesString) { + ExecutionLog executionLog = new ExecutionLog(); String url = String.format( "jdbc:postgresql://%s:%4d/%s?user=%s&password=%s", host, @@ -54,7 +93,7 @@ public class DatabaseHelper { for (String query : queries) { try { - executeQuery(query, st); + executionLog.recordAction(executeQuery(query, st)); } catch (SQLException e) { window.appendOutput("Exception while executing statement: " + e.getMessage()); } @@ -67,27 +106,30 @@ public class DatabaseHelper { window.appendOutput("Unexpected SQL Exception occurred. URL:\n" + url + "\n\tException: " + e.getMessage() + "\n\tSQL State: " + e.getSQLState()); window.setOutputChannel(previousChannel); } + + return executionLog; } /** * 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. + * @return The execution action which was done by executing this query. */ - private void executeQuery(String query, Statement statement) throws SQLException { + private ExecutionAction 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); + return 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); + return action; } } diff --git a/src/nl/andrewlalis/Window.java b/src/nl/andrewlalis/Window.java index bf44b95..51e882b 100644 --- a/src/nl/andrewlalis/Window.java +++ b/src/nl/andrewlalis/Window.java @@ -1,6 +1,7 @@ package nl.andrewlalis; import javax.swing.*; +import javax.xml.crypto.Data; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -69,37 +70,8 @@ public class Window extends JFrame { 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(); + DatabaseHelper helper = new DatabaseHelper(host, port, user, password, this); + helper.executeSQLComparison(initialization, this.templateTextArea.getText(), this.testingTextArea.getText()); } int getOutputChannel() { diff --git a/src/nl/andrewlalis/log/ExecutionLog.java b/src/nl/andrewlalis/log/ExecutionLog.java index db835f0..e4e8d95 100644 --- a/src/nl/andrewlalis/log/ExecutionLog.java +++ b/src/nl/andrewlalis/log/ExecutionLog.java @@ -31,15 +31,17 @@ public class ExecutionLog { ExecutionLog otherLog = (ExecutionLog) other; if (otherLog.getActions().size() != this.getActions().size()) { + System.out.println("Size difference in logs."); return false; } List otherLogActions = otherLog.getActions(); for (int i = 0; i < this.getActions().size(); i++) { - if (!this.getActions().get(i).equals(otherLogActions.get(i))) { - return false; - } + ExecutionAction myAction = this.getActions().get(i); + ExecutionAction theirAction = otherLogActions.get(i); + System.out.println("My action: " + myAction + "\nTheir action: " + theirAction); + System.out.println("\tEqual? " + myAction.equals(theirAction)); } return true; diff --git a/src/nl/andrewlalis/log/QueryAction.java b/src/nl/andrewlalis/log/QueryAction.java index 970cbe1..d30d7e9 100644 --- a/src/nl/andrewlalis/log/QueryAction.java +++ b/src/nl/andrewlalis/log/QueryAction.java @@ -3,104 +3,70 @@ 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. + * An action in which a query result set is returned. Note that SCROLL_INSENSITIVE statements must be used, otherwise + * an SQL exception will be thrown at each attempt to go through the result set. */ public class QueryAction extends ExecutionAction { - private String[] columns; - private String[][] values; + private ResultSet resultSet; - 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 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; + public QueryAction(ResultSet resultSet) { + this.resultSet = resultSet; } + /** + * The algorithm to determine if two query sets are equivalent is as follows: + * If all of the values of one column contain all of the values of another column, then these two columns must + * almost certainly represent the same value, even if in the wrong order. + * @param other The other object to check equality with. + * @return True if the two query sets are equivalent, or false otherwise. + */ @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 thisColumnValues = new HashMap<>(); - Map 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; - } - } - } + QueryAction otherAction = (QueryAction) other; 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"); + try { + this.resultSet.absolute(1); + ResultSetMetaData metaData = this.resultSet.getMetaData(); + int columnCount = metaData.getColumnCount(); - // 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) { + StringBuilder sb = new StringBuilder("Query Result:\n\tColumns: ("); + for (int i = 0; i < columnCount; i++) { + sb.append(metaData.getColumnName(i + 1)); + if (i < columnCount - 1) { sb.append(", "); } } - sb.append(")\n"); - } + sb.append(")\n\tValues:\n"); - return sb.toString(); + while (this.resultSet.next()) { + sb.append("\t("); + for (int i = 0; i < columnCount; i++) { + sb.append(this.resultSet.getString(i + 1)); + if (i < columnCount - 1) { + sb.append(", "); + } + } + sb.append(")\n"); + } + + return sb.toString(); + + } catch (SQLException e) { + e.printStackTrace(); + return "SQLException; Please use a SCROLL_INSENSITIVE statement when executing the query."; + } } }