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