001package edu.pdx.cs.joy.jdbc;
002
003import java.io.File;
004import java.sql.*;
005
006/**
007 * A command-line program that executes SQL statements against an H2 database.
008 * Supports all CRUD operations (INSERT, SELECT, UPDATE, DELETE) and displays
009 * results in a human-readable format.
010 */
011public class ExecuteH2DatabaseStatement {
012
013  /**
014   * Main method that takes a database file path and SQL statement as arguments.
015   *
016   * @param args command line arguments: args[0] = database file path, args[1] = SQL statement
017   * @throws SQLException if a database error occurs
018   */
019  public static void main(String[] args) throws SQLException {
020    if (args.length < 2) {
021      System.err.println("Missing required arguments");
022      System.err.println("Usage: java ExecuteH2DatabaseStatement <database-file-path> <sql-statement>");
023      System.err.println();
024      System.err.println("Examples:");
025      System.err.println("  SELECT: java ExecuteH2DatabaseStatement mydb.db \"SELECT * FROM users\"");
026      System.err.println("  INSERT: java ExecuteH2DatabaseStatement mydb.db \"INSERT INTO users (name) VALUES ('John')\"");
027      System.err.println("  UPDATE: java ExecuteH2DatabaseStatement mydb.db \"UPDATE users SET name='Jane' WHERE id=1\"");
028      System.err.println("  DELETE: java ExecuteH2DatabaseStatement mydb.db \"DELETE FROM users WHERE id=1\"");
029      return;
030    }
031
032    String dbFilePath = args[0];
033    String sqlStatement = args[1];
034
035    File dbFile = new File(dbFilePath);
036    System.out.println("Connecting to H2 database: " + dbFile.getAbsolutePath());
037    System.out.println("Executing SQL: " + sqlStatement);
038    System.out.println();
039
040    try (Connection connection = H2DatabaseHelper.createFileBasedConnection(dbFile)) {
041      executeStatement(connection, sqlStatement);
042    }
043  }
044
045  /**
046   * Executes a SQL statement and displays the results.
047   *
048   * @param connection the database connection
049   * @param sql the SQL statement to execute
050   * @throws SQLException if a database error occurs
051   */
052  private static void executeStatement(Connection connection, String sql) throws SQLException {
053    try (Statement statement = connection.createStatement()) {
054      // Determine if this is a query (SELECT) or update (INSERT/UPDATE/DELETE)
055      boolean isQuery = statement.execute(sql);
056
057      if (isQuery) {
058        // Handle SELECT queries
059        try (ResultSet resultSet = statement.getResultSet()) {
060          displayResultSet(resultSet);
061        }
062      } else {
063        // Handle INSERT, UPDATE, DELETE
064        int rowsAffected = statement.getUpdateCount();
065        System.out.println("Statement executed successfully");
066        System.out.println("Rows affected: " + rowsAffected);
067      }
068    }
069  }
070
071  /**
072   * Displays a ResultSet in a formatted table.
073   *
074   * @param resultSet the result set to display
075   * @throws SQLException if a database error occurs
076   */
077  private static void displayResultSet(ResultSet resultSet) throws SQLException {
078    ResultSetMetaData metaData = resultSet.getMetaData();
079    int columnCount = metaData.getColumnCount();
080
081    // Calculate column widths
082    int[] columnWidths = new int[columnCount];
083    String[] columnNames = new String[columnCount];
084
085    for (int i = 1; i <= columnCount; i++) {
086      columnNames[i - 1] = metaData.getColumnLabel(i);
087      columnWidths[i - 1] = Math.max(columnNames[i - 1].length(), 10);
088    }
089
090    // Collect all rows to calculate proper column widths
091    java.util.List<String[]> rows = new java.util.ArrayList<>();
092    while (resultSet.next()) {
093      String[] row = new String[columnCount];
094      for (int i = 1; i <= columnCount; i++) {
095        Object value = resultSet.getObject(i);
096        row[i - 1] = value != null ? value.toString() : "NULL";
097        columnWidths[i - 1] = Math.max(columnWidths[i - 1], row[i - 1].length());
098      }
099      rows.add(row);
100    }
101
102    // Print header
103    printSeparator(columnWidths);
104    printRow(columnNames, columnWidths);
105    printSeparator(columnWidths);
106
107    // Print data rows
108    if (rows.isEmpty()) {
109      System.out.println("No rows returned");
110    } else {
111      for (String[] row : rows) {
112        printRow(row, columnWidths);
113      }
114      printSeparator(columnWidths);
115      System.out.println(rows.size() + " row(s) returned");
116    }
117  }
118
119  /**
120   * Prints a separator line.
121   *
122   * @param columnWidths array of column widths
123   */
124  private static void printSeparator(int[] columnWidths) {
125    System.out.print("+");
126    for (int width : columnWidths) {
127      for (int i = 0; i < width + 2; i++) {
128        System.out.print("-");
129      }
130      System.out.print("+");
131    }
132    System.out.println();
133  }
134
135  /**
136   * Prints a data row.
137   *
138   * @param values array of values to print
139   * @param columnWidths array of column widths
140   */
141  private static void printRow(String[] values, int[] columnWidths) {
142    System.out.print("|");
143    for (int i = 0; i < values.length; i++) {
144      System.out.print(" ");
145      System.out.print(padRight(values[i], columnWidths[i]));
146      System.out.print(" |");
147    }
148    System.out.println();
149  }
150
151  /**
152   * Pads a string to the right with spaces.
153   *
154   * @param str the string to pad
155   * @param length the desired length
156   * @return the padded string
157   */
158  private static String padRight(String str, int length) {
159    if (str.length() >= length) {
160      return str;
161    }
162    StringBuilder sb = new StringBuilder(str);
163    while (sb.length() < length) {
164      sb.append(' ');
165    }
166    return sb.toString();
167  }
168}