001package io.ebean.migration.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 private boolean commitOnCreateIndex; 027 028 /** 029 * Construct with a script name (for logging) and flag indicating if errors are expected. 030 */ 031 public DdlRunner(boolean expectErrors, String scriptName) { 032 this.expectErrors = expectErrors; 033 this.scriptName = scriptName; 034 } 035 036 /** 037 * Needed for Cockroach DB. Needs commit after create index and before alter table add FK. 038 */ 039 public void setCommitOnCreateIndex() { 040 commitOnCreateIndex = true; 041 } 042 043 /** 044 * Parse the content into sql statements and execute them in a transaction. 045 */ 046 public int runAll(String content, Connection connection) throws SQLException { 047 048 List<String> statements = ddlParser.parse(new StringReader(content)); 049 return runStatements(statements, connection); 050 } 051 052 /** 053 * Execute all the statements in a single transaction. 054 */ 055 private int runStatements(List<String> statements, Connection connection) throws SQLException { 056 057 List<String> noDuplicates = new ArrayList<>(); 058 059 for (String statement : statements) { 060 if (!noDuplicates.contains(statement)) { 061 noDuplicates.add(statement); 062 } 063 } 064 065 logger.info("Executing {} - {} statements", scriptName, noDuplicates.size()); 066 067 for (int i = 0; i < noDuplicates.size(); i++) { 068 String xOfy = (i + 1) + " of " + noDuplicates.size(); 069 String ddl = noDuplicates.get(i); 070 runStatement(expectErrors, xOfy, ddl, connection); 071 if (commitOnCreateIndex && ddl.startsWith("create index ")) { 072 logger.debug("commit on create index ..."); 073 connection.commit(); 074 } 075 } 076 077 return noDuplicates.size(); 078 } 079 080 /** 081 * Execute the statement. 082 */ 083 private void runStatement(boolean expectErrors, String oneOf, String stmt, Connection c) throws SQLException { 084 085 PreparedStatement pstmt = null; 086 try { 087 088 // trim and remove trailing ; or / 089 stmt = stmt.trim(); 090 if (stmt.endsWith(";")) { 091 stmt = stmt.substring(0, stmt.length() - 1); 092 } else if (stmt.endsWith("/")) { 093 stmt = stmt.substring(0, stmt.length() - 1); 094 } 095 096 if (stmt.isEmpty()) { 097 logger.debug("skip empty statement at " + oneOf); 098 return; 099 } 100 101 if (logger.isDebugEnabled()) { 102 logger.debug("executing " + oneOf + " " + getSummary(stmt)); 103 } 104 105 pstmt = c.prepareStatement(stmt); 106 pstmt.execute(); 107 108 } catch (SQLException e) { 109 if (expectErrors) { 110 logger.debug(" ... ignoring error executing " + getSummary(stmt) + " error: " + e.getMessage()); 111 } else { 112 String msg = "Error executing stmt[" + stmt + "] error[" + e.getMessage() + "]"; 113 throw new SQLException(msg, e); 114 } 115 116 } finally { 117 if (pstmt != null) { 118 try { 119 pstmt.close(); 120 } catch (SQLException e) { 121 logger.error("Error closing pstmt", e); 122 } 123 } 124 } 125 } 126 127 private String getSummary(String s) { 128 if (s.length() > 80) { 129 return s.substring(0, 80).trim() + "..."; 130 } 131 return s; 132 } 133 134}