001package io.ebean.dbmigration.ddl;
002
003import org.slf4j.Logger;
004import org.slf4j.LoggerFactory;
005
006import java.io.StringReader;
007import java.sql.Connection;
008import java.sql.PreparedStatement;
009import java.sql.SQLException;
010import java.util.ArrayList;
011import java.util.List;
012
013/**
014 * Runs DDL scripts.
015 */
016public class DdlRunner {
017
018  protected static final Logger logger = LoggerFactory.getLogger("io.ebean.DDL");
019
020  private DdlParser ddlParser = new DdlParser();
021
022  private final String scriptName;
023
024  private final boolean expectErrors;
025
026  /**
027   * Construct with a script name (for logging) and flag indicating if errors are expected.
028   */
029  public DdlRunner(boolean expectErrors, String scriptName) {
030    this.expectErrors = expectErrors;
031    this.scriptName = scriptName;
032  }
033
034  /**
035   * Parse the content into sql statements and execute them in a transaction.
036   */
037  public int runAll(String content, Connection connection) throws SQLException {
038
039    List<String> statements = ddlParser.parse(new StringReader(content));
040    return runStatements(statements, connection);
041  }
042
043  /**
044   * Execute all the statements in a single transaction.
045   */
046  private int runStatements(List<String> statements, Connection connection) throws SQLException {
047
048    List<String> noDuplicates = new ArrayList<>();
049
050    for (String statement : statements) {
051      if (!noDuplicates.contains(statement)) {
052        noDuplicates.add(statement);
053      }
054    }
055
056    logger.info("Executing {} - {} statements", scriptName, noDuplicates.size());
057
058    for (int i = 0; i < noDuplicates.size(); i++) {
059      String xOfy = (i + 1) + " of " + noDuplicates.size();
060      runStatement(expectErrors, xOfy, noDuplicates.get(i), connection);
061    }
062
063    return noDuplicates.size();
064  }
065
066  /**
067   * Execute the statement.
068   */
069  private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) throws SQLException {
070
071    PreparedStatement pstmt = null;
072    try {
073
074      // trim and remove trailing ; or /
075      stmt = stmt.trim();
076      if (stmt.endsWith(";")) {
077        stmt = stmt.substring(0, stmt.length() - 1);
078      } else if (stmt.endsWith("/")) {
079        stmt = stmt.substring(0, stmt.length() - 1);
080      }
081
082      if (logger.isDebugEnabled()) {
083        logger.debug("executing " + oneOf + " " + getSummary(stmt));
084      }
085
086      pstmt = c.prepareStatement(stmt);
087      pstmt.execute();
088
089    } catch (SQLException e) {
090      if (expectErrors) {
091        logger.debug(" ... ignoring error executing " + getSummary(stmt) + "  error: " + e.getMessage());
092      } else {
093        String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]";
094        throw new SQLException(msg, e);
095      }
096
097    } finally {
098      if (pstmt != null) {
099        try {
100          pstmt.close();
101        } catch (SQLException e) {
102          logger.error("Error closing pstmt", e);
103        }
104      }
105    }
106  }
107
108  private String getSummary(String s) {
109    if (s.length() > 80) {
110      return s.substring(0, 80).trim() + "...";
111    }
112    return s;
113  }
114
115}