All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.sql.SqlExecMojo Maven / Gradle / Ivy

package org.codehaus.mojo.sql;

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Reader;
import java.io.StringReader;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import org.apache.commons.text.StringEscapeUtils;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.project.MavenProject;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.Settings;
import org.apache.maven.shared.filtering.MavenFileFilter;
import org.apache.maven.shared.filtering.MavenFileFilterRequest;
import org.apache.maven.shared.filtering.MavenFilteringException;
import org.apache.maven.shared.scriptinterpreter.ScriptException;
import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
import org.codehaus.plexus.util.FileUtils;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.StringUtils;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcher;
import org.sonatype.plexus.components.sec.dispatcher.SecDispatcherException;

/**
 * Executes SQL against a database.
 */
@Mojo(name = "execute", requiresProject = true, threadSafe = true)
public class SqlExecMojo extends AbstractMojo {

    private static final String DELIMITER_STATEMENT = "DELIMITER ";
    private static final int DELIMITER_STATEMENT_LENGTH = DELIMITER_STATEMENT.length();

    /**
     * Call {@link #setOnError(String)} with this value to abort SQL command execution if an error is found.
     */
    public static final String ON_ERROR_ABORT = "abort";

    /**
     * Call {@link #setOnError(String)} with this value to continue SQL command execution until all commands have been
     * attempted, then abort the build if an SQL error occurred in any of the commands.
     */
    public static final String ON_ERROR_ABORT_AFTER = "abortAfter";

    /**
     * Call {@link #setOnError(String)} with this value to continue SQL command execution if an error is found.
     */
    public static final String ON_ERROR_CONTINUE = "continue";

    /**
     * Call {@link #setOrderFile(String)} with this value to sort in ascendant order the sql files.
     */
    public static final String FILE_SORTING_ASC = "ascending";

    /**
     * Call {@link #setOrderFile(String)} with this value to sort in descendant order the sql files.
     */
    public static final String FILE_SORTING_DSC = "descending";

    /**
     * The default SQL delimiter which is used to separate statements.
     */
    public static final String DEFAULT_DELIMITER = ";";

    //////////////////////////// User Info ///////////////////////////////////

    /**
     * Database username. If not given, it will be looked up through settings.xml's server with
     * ${settingsKey} as key.
     *
     * @since 1.0
     */
    @Parameter(property = "username")
    private String username;

    /**
     * Database password. If not given, it will be looked up through settings.xml's server with
     * ${settingsKey} as key.
     *
     * @since 1.0
     */
    @Parameter(property = "password")
    private String password;

    /**
     * Ignore the password and use anonymous access. This may be useful for databases like MySQL which do not allow
     * empty password parameters in the connection initialization.
     *
     * @since 1.4
     */
    @Parameter(defaultValue = "false")
    private boolean enableAnonymousPassword;

    /**
     * Additional key=value pairs separated by comma to be passed into JDBC driver.
     *
     * @since 1.0
     */
    @Parameter(defaultValue = "", property = "driverProperties")
    private String driverProperties;

    /**
     * @since 1.0
     */
    @Parameter(defaultValue = "${settings}", readonly = true, required = true)
    private Settings settings;

    /**
     * Server's id in settings.xml to look up username and password. Defaults to
     * ${url} if not given.
     *
     * @since 1.0
     */
    @Parameter(property = "settingsKey")
    private String settingsKey;

    /**
     * MNG-4384
     *
     * @since 1.5
     */
    @Component(role = SecDispatcher.class, hint = "default")
    private SecDispatcher securityDispatcher;

    /**
     * Skip execution when there is an error obtaining a connection. This is a special case to support databases, such
     * as embedded Derby, that can shutdown the database via the URL (i.e. shutdown=true).
     *
     * @since 1.1
     */
    @Parameter(defaultValue = "false", property = "skipOnConnectionError")
    private boolean skipOnConnectionError;

    /**
     * Skip missing files defined by {@link #setSrcFiles(File[])}. This behavior allows to define a full fledged list
     * of all sql files in a single {@code pom.xml} without failing if used by modules for which some sql files are not
     * available on the classpath.
     */
    @Parameter(defaultValue = "false", property = "skipMissingFiles")
    private boolean skipMissingFiles;

    /**
     * Setting this parameter to true will force the execution of this mojo, even if it would get skipped
     * usually.
     */
    @Parameter(defaultValue = "false", property = "forceOpenJpaExecution", required = true)
    private boolean forceMojoExecution;

    /**
     * The Maven Project Object
     */
    @Parameter(defaultValue = "${project}", readonly = true, required = true)
    private MavenProject project;

    /**
     */
    @Parameter(defaultValue = "${session}", readonly = true, required = true)
    private MavenSession mavenSession;

    //////////////////////////////// Source info /////////////////////////////

    /**
     * SQL input commands separated by ${delimiter}.
     *
     * @since 1.0
     */
    @Parameter(property = "sqlCommand")
    private String sqlCommand;

    /**
     * List of files containing SQL statements to load.
     *
     * @since 1.0
     * @see #fileset
     */
    @Parameter
    private File[] srcFiles;

    /**
     * File(s) containing SQL statements to load. Only use a Fileset if you want to use ant-like filepatterns, otherwise
     * use srcFiles. The order is based on a matching occurrence while scanning the directory (not the order of
     * includes!).
     *
     * @since 1.0
     * @see #srcFiles
     */
    @Parameter
    private Fileset fileset;

    /**
     * When true, skip the execution.
     *
     * @since 1.0
     */
    @Parameter(defaultValue = "false")
    private boolean skip;

    ////////////////////////////////// Database info /////////////////////////
    /**
     * Database URL.
     *
     * @since 1.0-beta-1
     */
    @Parameter(property = "url", required = true)
    private String url;

    /**
     * Database driver classname.
     *
     * @since 1.0
     */
    @Parameter(property = "driver", required = true)
    private String driver;

    ////////////////////////////// Operation Configuration ////////////////////
    /**
     * Set to true to execute none-transactional SQL.
     *
     * @since 1.0
     */
    @Parameter(defaultValue = "false", property = "autocommit")
    private boolean autocommit;

    /**
     * Set to true to execute immediately rollback transactional SQL.
     *
     * @since 3.0.0
     */
    @Parameter(defaultValue = "false", property = "rollbackTransactions")
    private boolean rollbackTransactions;

    /**
     * Action to perform if an error is found. Possible values are abort and continue.
     *
     * @since 1.0
     */
    @Parameter(defaultValue = ON_ERROR_ABORT, property = "onError")
    private String onError;

    /**
     * List of connection validation SQLs. If the value is given, the SQL files
     * are processed after all validation have been executed successfully.
     * The validation SQLs can be used to verify that required tables or data
     * exists before the SQLs are processed.
     *
     * @since 3.0.0
     */
    @Parameter(defaultValue = "", property = "connectionValidationSqls")
    private List connectionValidationSqls;

    /**
     * Connection retry count. How many times connection is tried to be
     * opened and validated after failure.
     *
     * @since 3.0.0
     */
    @Parameter(defaultValue = "0", property = "connectionRetryCount")
    private int connectionRetryCount;

    /**
     * How many seconds are waited before next connection attempt.
     *
     * @since 3.0.0
     */
    @Parameter(defaultValue = "1", property = "connectionRetryInterval")
    private int connectionRetryInterval = 1;

    ////////////////////////////// Parser Configuration ////////////////////

    /**
     * Set the delimiter that separates SQL statements.
     *
     * @since 1.0
     */
    @Parameter(defaultValue = DEFAULT_DELIMITER, property = "delimiter")
    private String delimiter;

    /**
     * 

* The delimiter type takes two values - "normal" and "row". Normal means that any occurrence of the delimiter * terminate the SQL command whereas with row, only a line containing just the delimiter is recognized as the end of * the command. *

*

* For example, set this to "go" and delimiterType to "row" for Sybase ASE or MS SQL Server. *

* * @since 1.2 */ @Parameter(defaultValue = DelimiterType.NORMAL, property = "delimiterType") private String delimiterType; /** * Set the order in which the SQL files will be executed. Possible values are ascending and * descending. Any other value means that no sorting will be performed. Refers to {@link #fileset} and * {@link #srcFiles} * * @since 1.1 */ @Parameter(property = "orderFile") private String orderFile; /** * Keep the format of an SQL block. * * @since 1.1 */ @Parameter(defaultValue = "false", property = "keepFormat") private boolean keepFormat; /////////////////////////////////////////////////////////////////////////////////////// /** * Print SQL results. * * @since 1.3 */ @Parameter(defaultValue = "false", property = "printResultSet") private boolean printResultSet; /** * Print header columns. */ @Parameter(property = "showheaders", defaultValue = "true") private boolean showHeaders; /** * Print footer value, informing on the number of rows */ @Parameter(property = "showFooter", defaultValue = "true") private boolean showFooter; /** * Dump the SQL execution's output to a file.
* Default value is: System.out. * * @since 1.3 */ @Parameter private File outputFile; /** * The delimiter used to separate fields in the output when using printResultSet. * * @since 1.4 */ @Parameter(defaultValue = ",") private String outputDelimiter; /** * Encoding to use while writing queried data to a file. * * @since 3.0.0 */ @Parameter(defaultValue = "${project.build.sourceEncoding}", property = "outputEncoding") private String outputEncoding; /** * Encoding to use when reading SQL statements from a file. * * @since 1.1 */ @Parameter(defaultValue = "${project.build.sourceEncoding}", property = "encoding") private String encoding; /** * Append to an existing file or overwrite it? */ private boolean append = false; /** * Argument to Statement.setEscapeProcessing If you want the driver to use regular SQL syntax then set this to * false. * * @since 1.4 */ @Parameter(defaultValue = "true", property = "escapeProcessing") private boolean escapeProcessing; ////////////////////////////////// Internal properties////////////////////// /** * number of successful executed statements */ private int successfulStatements = 0; /** * number of total executed statements */ private int totalStatements = 0; /** * Database connection */ private Connection conn = null; /** * SQL statement */ private Statement statement = null; /** * SQL transactions to perform */ private List transactions = new Vector<>(); /** * @since 1.4 */ @Component(role = MavenFileFilter.class) private MavenFileFilter fileFilter; /** * Set to true if you want to filter the srcFiles using system-, user- and project properties * * @since 1.4 */ @Parameter(defaultValue = "false", property = "enableFiltering") private boolean enableFiltering; private ScriptRunner scriptRunner; /** * @since 1.6 */ @Parameter private File preExecuteHookScript; /** * @since 1.6 */ @Parameter private File postExecuteHookScript; /** * number of done connection retry attempts * * @since 3.0.0 */ private int connectionRetryAttempts; /** * Add a SQL transaction to execute * * @return a new SqlExecMojo.Transaction */ public Transaction createTransaction() { Transaction t = new Transaction(); transactions.add(t); return t; } /** * Set an inline SQL command to execute. NB: Properties are not expanded in this text. * * @param sql the sql statement to add */ public void addText(String sql) { if (this.sqlCommand == null) { this.sqlCommand = sql; } else { this.sqlCommand += sql; } } /** * Set the file encoding to use on the SQL files read in * * @param encoding the encoding to use on the files */ public void setEncoding(String encoding) { this.encoding = encoding; } /** * Set the delimiter that separates SQL statements. Defaults to ";"; * * @param delimiter the new delimiter */ public void setDelimiter(String delimiter) { this.delimiter = delimiter; } /** * Set the delimiter type: "normal" or "row" (default "normal"). * * @param delimiterType the new delimiterType */ public void setDelimiterType(String delimiterType) { this.delimiterType = delimiterType; } /** * Print result sets from the statements; optional, default false * * @param print true to print the resultset, otherwise false */ public void setPrintResultSet(boolean print) { this.printResultSet = print; } /** * Print headers for result sets from the statements; optional, default true. * * @param showHeaders true to show the headers, otherwise false */ public void setShowHeaders(boolean showHeaders) { this.showHeaders = showHeaders; } /** * Set the output file; * * @param output the output file */ public void setOutputFile(File output) { this.outputFile = output; } /** * whether output should be appended to or overwrite an existing file. Defaults to false. * * @param append true to append, otherwise false to overwrite */ public void setAppend(boolean append) { this.append = append; } /** * whether or not format should be preserved. Defaults to false. * * @param keepformat {@code true} to keep the format {@code false} otherwise. */ public void setKeepFormat(boolean keepformat) { this.keepFormat = keepformat; } /** * Set escape processing for statements. * * @param enable true to escape, otherwise false */ public void setEscapeProcessing(boolean enable) { escapeProcessing = enable; } /** *

* Determine if the mojo execution should get skipped. *

* This is the case if: *
    *
  • {@link #skip} is true
  • *
  • if the mojo gets executed on a project with packaging type 'pom' and {@link #forceMojoExecution} is * false
  • *
* * @return true if the mojo execution should be skipped. */ protected boolean skipMojo() { if (skip) { getLog().info("User has requested to skip execution."); return true; } if (!forceMojoExecution && project != null && "pom".equals(project.getPackaging())) { getLog().info("Skipping sql execution for project with packaging type 'pom'"); return true; } return false; } /** * Load the sql file and then execute it * * @throws MojoExecutionException */ @Override public void execute() throws MojoExecutionException { if (skipMojo()) { return; } executeScriptPlusSql(); } private void executeScriptPlusSql() throws MojoExecutionException { // prepare scriptrunner scriptRunner = new ScriptRunner(); scriptRunner.setScriptEncoding(encoding); // scriptRunner.setGlobalVariable( "localRepositoryPath", localRepositoryPath ); // scriptRunner.setClassPath( scriptClassPath ); Map context = new HashMap<>(); try { if (preExecuteHookScript != null) { scriptRunner.run("pre-execute script", preExecuteHookScript, context, null); } executeSqlCore(); } catch (IOException | ScriptException e) { throw new MojoExecutionException(e.getMessage(), e); } finally { try { if (postExecuteHookScript != null) { scriptRunner.run("post-execute script", postExecuteHookScript, context, null); } } catch (IOException | ScriptException e) { throw new MojoExecutionException(e.getMessage(), e); } } } protected void executeSqlCore() throws MojoExecutionException { connectionRetryAttempts = 0; successfulStatements = 0; totalStatements = 0; // Connection is not valid until it is proved to be boolean connectionIsValid = false; loadUserInfoFromSettings(); addCommandToTransactions(); addFilesToTransactions(); addFileSetToTransactions(); sortTransactions(); // Loop until connection is valid (or exited otherwise) while (!connectionIsValid) { // Check should we retry, if there are some errors boolean retryOnConnectionError = this.connectionRetryCount > connectionRetryAttempts; try { // Get a new connection if is not already open if (conn == null || conn.isClosed()) { conn = getConnection(); } validateConnection(conn); // No SQLException thrown, the connection should be fine connectionIsValid = true; } catch (SQLException e) { if (retryOnConnectionError) { // User want to retry connection, increase the retry attempts connectionRetryAttempts++; getLog().info("Connection validation failed: retrying connection in " + connectionRetryInterval + " secs (" + connectionRetryAttempts + "/" + connectionRetryCount + ")..."); waitToRetryConnectionValidation(); } else if (!this.skipOnConnectionError) { // Make sure connection is closed, it can be open if only validation failed closeConnection(); throw new MojoExecutionException(e.getMessage(), e); } else { // Make sure connection is closed, it can be open if only validation failed closeConnection(); // Error on get connection and user asked to skip the rest return; } } } try { statement = conn.createStatement(); statement.setEscapeProcessing(escapeProcessing); PrintStream out = System.out; try { if (outputFile != null) { getLog().debug("Opening PrintStream to output file " + outputFile); outputFile.getParentFile().mkdirs(); if (StringUtils.isEmpty(this.outputEncoding)) { this.outputEncoding = System.getProperty("file.encoding"); } out = new PrintStream( new BufferedOutputStream(new FileOutputStream(outputFile.getAbsolutePath(), append)), true, this.outputEncoding); } // Process all transactions for (Transaction t : transactions) { t.runTransaction(out); if (!autocommit) { if (rollbackTransactions) { getLog().debug("Rollback transaction"); conn.rollback(); } else { getLog().debug("Committing transaction"); conn.commit(); } } } } finally { if (out != null && out != System.out) { out.close(); } } } catch (IOException e) { throw new MojoExecutionException(e.getMessage(), e); } catch (SQLException e) { if (!autocommit && conn != null && ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) { try { conn.rollback(); } catch (SQLException ex) { // ignore } } throw new MojoExecutionException(e.getMessage(), e); } finally { closeStatement(); closeConnection(); } getLog().info(getSuccessfulStatements() + " of " + getTotalStatements() + " SQL statements executed successfully"); if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(getOnError()) && totalStatements != successfulStatements) { throw new MojoExecutionException("Some SQL statements failed to execute"); } } /** * Add sql command to transactions list. */ private void addCommandToTransactions() { if (sqlCommand != null) { createTransaction().addText(sqlCommand.trim()); } } /** * Add user sql fileset to transation list */ private void addFileSetToTransactions() { String[] includedFiles; if (fileset != null) { fileset.scan(); includedFiles = fileset.getIncludedFiles(); } else { includedFiles = new String[0]; } for (String includedFile : includedFiles) { createTransaction().setSrc(new File(fileset.getBasedir(), includedFile)); } } /** * Add user input of srcFiles to transaction list. * * @throws MojoExecutionException */ private void addFilesToTransactions() throws MojoExecutionException { File[] files = getSrcFiles(); MavenFileFilterRequest request = new MavenFileFilterRequest(); request.setEncoding(encoding); request.setMavenSession(mavenSession); request.setMavenProject(project); request.setFiltering(enableFiltering); if (files != null) { for (File sourceFile : files) { if (sourceFile != null && !sourceFile.exists()) { if (skipMissingFiles) { getLog().debug(String.format("ignoring non existing sql file [%s].", sourceFile.getPath())); continue; } throw new MojoExecutionException(sourceFile.getPath() + " not found."); } String basename = FileUtils.basename(sourceFile.getName()); String extension = FileUtils.extension(sourceFile.getName()); File targetFile = FileUtils.createTempFile(basename, extension, null); if (!getLog().isDebugEnabled()) { targetFile.deleteOnExit(); } request.setFrom(sourceFile); request.setTo(targetFile); try { fileFilter.copyFile(request); } catch (MavenFilteringException e) { throw new MojoExecutionException(e.getMessage()); } createTransaction().setSrc(targetFile); } } } /** * Sort the transaction list. */ private void sortTransactions() { if (FILE_SORTING_ASC.equalsIgnoreCase(this.orderFile)) { Collections.sort(transactions); } else if (FILE_SORTING_DSC.equalsIgnoreCase(this.orderFile)) { transactions.sort(Collections.reverseOrder()); } } /** * Load username password from settings if user has not set them in JVM properties * * @throws MojoExecutionException */ private void loadUserInfoFromSettings() throws MojoExecutionException { if (this.settingsKey == null) { this.settingsKey = getUrl(); } if ((getUsername() == null || getPassword() == null) && (settings != null)) { Server server = this.settings.getServer(this.settingsKey); if (server != null) { if (getUsername() == null) { setUsername(server.getUsername()); } if (getPassword() == null && server.getPassword() != null) { try { setPassword(securityDispatcher.decrypt(server.getPassword())); } catch (SecDispatcherException e) { throw new MojoExecutionException(e.getMessage()); } } } } if (getUsername() == null) { // allow empty username setUsername(""); } if (getPassword() == null) { // allow empty password setPassword(""); } } /** * Creates a new Connection as using the driver, url, userid and password specified. The calling method is * responsible for closing the connection. * * @return Connection the newly created connection. * @throws MojoExecutionException if the UserId/Password/Url is not set or there is no suitable driver or the driver * fails to load. * @throws SQLException if there is problem getting connection with valid url */ Connection getConnection() throws MojoExecutionException, SQLException { getLog().debug("connecting to " + getUrl()); Properties info = new Properties(); info.put("user", getUsername()); if (!enableAnonymousPassword) { info.put("password", getPassword()); } info.putAll(this.getDriverProperties()); Driver driverInstance; try { Class dc = Class.forName(getDriver()); driverInstance = (Driver) dc.getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException e) { throw new MojoExecutionException("Driver class not found: " + getDriver(), e); } catch (Exception e) { throw new MojoExecutionException("Failure loading driver: " + getDriver(), e); } Connection connection = driverInstance.connect(getUrl(), info); if (connection == null) { // Driver doesn't understand the URL throw new SQLException("No suitable Driver for " + getUrl()); } connection.setAutoCommit(autocommit); return connection; } /** * parse driverProperties into Properties set * * @return the driver properties * @throws MojoExecutionException */ protected Properties getDriverProperties() throws MojoExecutionException { Properties properties = new Properties(); if (!StringUtils.isEmpty(this.driverProperties)) { String[] tokens = StringUtils.split(this.driverProperties, ","); for (String token : tokens) { String[] keyValueTokens = StringUtils.split(token.trim(), "="); if (keyValueTokens.length != 2) { throw new MojoExecutionException("Invalid JDBC Driver properties: " + this.driverProperties); } properties.setProperty(keyValueTokens[0], keyValueTokens[1]); } } return properties; } /** * read in lines and execute them * * @param reader the reader * @param out the outputstream * @throws SQLException * @throws IOException */ private void runStatements(Reader reader, PrintStream out) throws SQLException, IOException { String line; // The delimeter can be overwritten by mysql DELIMETER command. // If the delimeter is overwritten it is in one file, not a global change String fileDelimeter = this.delimiter; // TODO: Check if this equivalent with if (enableBlockMode) {.. if (delimiterType.equals(DelimiterType.ROW)) { // no need to parse the content, ship it directly to jdbc in one sql statement line = IOUtil.toString(reader); execSQL(line, out); return; } StringBuilder sql = new StringBuilder(); BufferedReader in = new BufferedReader(reader); int overflow = SqlSplitter.NO_END; while ((line = in.readLine()) != null) { if (!keepFormat) { line = line.trim(); } if (!keepFormat) { if (line.startsWith("//")) { continue; } if (line.startsWith("--")) { continue; } StringTokenizer st = new StringTokenizer(line); if (st.hasMoreTokens()) { String token = st.nextToken(); if ("REM".equalsIgnoreCase(token)) { continue; } } } // Check for mysql delimiter statements if (overflow >= SqlSplitter.NO_END) { String ucLine = line.toUpperCase(); if (ucLine.startsWith(DELIMITER_STATEMENT)) { fileDelimeter = line.substring(DELIMITER_STATEMENT_LENGTH).trim(); continue; } } if (!keepFormat) { sql.append(" ").append(line); } else { sql.append("\n").append(line); } overflow = SqlSplitter.containsSqlEnd(line, fileDelimeter, overflow); // SQL defines "--" as a comment to EOL // and in Oracle it may contain a hint // so we cannot just remove it, instead we must end it if (!keepFormat && overflow == SqlSplitter.NO_END) { sql.append("\n"); } if ((delimiterType.equals(DelimiterType.NORMAL) && overflow > 0) || (delimiterType.equals(DelimiterType.ROW) && line.trim().equals(fileDelimeter))) { execSQL(sql.substring(0, sql.length() - fileDelimeter.length()), out); sql.setLength(0); // clean buffer overflow = SqlSplitter.NO_END; } } // Catch any statements not followed by ; if (sql.length() > 0) { execSQL(sql.toString(), out); } } /** * Exec the sql statement. * * @param sql query to execute * @param out the outputstream */ private void execSQL(String sql, PrintStream out) throws SQLException { // Check and ignore empty statements if (sql.trim().isEmpty()) { return; } ResultSet resultSet = null; try { totalStatements++; getLog().debug("SQL: " + sql); boolean ret; int updateCountTotal = 0; ret = statement.execute(sql); do { if (!ret) { int updateCount = statement.getUpdateCount(); if (updateCount != -1) { updateCountTotal += updateCount; } } else { resultSet = statement.getResultSet(); if (printResultSet) { printResultSet(resultSet, out); } } ret = statement.getMoreResults(); } while (ret); getLog().debug(updateCountTotal + " rows affected"); if (printResultSet && showFooter) { printResultSetCount(updateCountTotal, out); } SQLWarning warning = conn.getWarnings(); while (warning != null) { getLog().debug(warning + " sql warning"); warning = warning.getNextWarning(); } conn.clearWarnings(); successfulStatements++; } catch (SQLException e) { getLog().error("Failed to execute: " + sql); if (ON_ERROR_ABORT.equalsIgnoreCase(getOnError())) { throw e; } getLog().error(e.toString()); } finally { if (resultSet != null) { resultSet.close(); } } } /** * print any results in the result set. * * @param rs the resultset to print information about * @param out the place to print results * @throws SQLException on SQL problems. */ protected void printResultSet(ResultSet rs, PrintStream out) throws SQLException { if (rs != null) { getLog().debug("Processing new result set."); ResultSetMetaData md = rs.getMetaData(); int columnCount = md.getColumnCount(); StringBuffer line = new StringBuffer(); if (showHeaders) { boolean first = true; for (int col = 1; col <= columnCount; col++) { String columnValue = md.getColumnName(col); if (columnValue != null) { columnValue = columnValue.trim(); if (",".equals(outputDelimiter)) { columnValue = StringEscapeUtils.escapeCsv(columnValue); } } if (first) { first = false; } else { line.append(outputDelimiter); } line.append(columnValue); } out.println(line); line = new StringBuffer(); } while (rs.next()) { boolean first = true; for (int col = 1; col <= columnCount; col++) { String columnValue = rs.getString(col); if (columnValue != null) { columnValue = columnValue.trim(); if (",".equals(outputDelimiter)) { columnValue = StringEscapeUtils.escapeCsv(columnValue); } } if (first) { first = false; } else { line.append(outputDelimiter); } line.append(columnValue); } out.println(line); line = new StringBuffer(); } } out.println(); } protected void printResultSetCount(int updateCountTotal, PrintStream out) { out.println(updateCountTotal + " rows affected"); } /** * Contains the definition of a new transaction element. Transactions allow several files or blocks of statements to * be executed using the same JDBC connection and commit operation in between. */ private class Transaction implements Comparable { private File tSrcFile = null; private String tSqlCommand = ""; /** * */ public void setSrc(File src) { this.tSrcFile = src; } /** * */ public void addText(String sql) { this.tSqlCommand += sql; } /** * */ private void runTransaction(PrintStream out) throws IOException, SQLException { if (!tSqlCommand.isEmpty()) { getLog().info("Executing commands"); runStatements(new StringReader(tSqlCommand), out); } if (tSrcFile != null) { getLog().info("Executing file: " + tSrcFile.getAbsolutePath()); Reader reader = null; if (StringUtils.isEmpty(encoding)) { reader = new FileReader(tSrcFile); } else { reader = new InputStreamReader(Files.newInputStream(tSrcFile.toPath()), encoding); } try { runStatements(reader, out); } finally { reader.close(); } } } public int compareTo(Transaction transaction) { if (transaction.tSrcFile == null) { if (this.tSrcFile == null) { return 0; } else { return Integer.MAX_VALUE; } } else { if (this.tSrcFile == null) { return Integer.MIN_VALUE; } else { return this.tSrcFile.compareTo(transaction.tSrcFile); } } } } // // helper accessors for unit test purposes // public String getUsername() { return this.username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return this.password; } public void setPassword(String password) { this.password = password; } public String getUrl() { return this.url; } public void setUrl(String url) { this.url = url; } public String getDriver() { return this.driver; } public void setDriver(String driver) { this.driver = driver; } void setAutocommit(boolean autocommit) { this.autocommit = autocommit; } public void setRollbackTransactions(boolean rollbackTransactions) { this.rollbackTransactions = rollbackTransactions; } void setFileset(Fileset fileset) { this.fileset = fileset; } public File[] getSrcFiles() { return this.srcFiles; } public void setSrcFiles(File[] files) { this.srcFiles = files; } public String getOrderFile() { return this.orderFile; } public void setOrderFile(String orderFile) { if (FILE_SORTING_ASC.equalsIgnoreCase(orderFile)) { this.orderFile = FILE_SORTING_ASC; } else if (FILE_SORTING_DSC.equalsIgnoreCase(orderFile)) { this.orderFile = FILE_SORTING_DSC; } else { throw new IllegalArgumentException(orderFile + " is not a valid value for orderFile, only '" + FILE_SORTING_ASC + "' or '" + FILE_SORTING_DSC + "'."); } } /** * Number of SQL statements executed so far that caused errors. * * @return the number */ public int getSuccessfulStatements() { return successfulStatements; } /** * Number of SQL statements executed so far, including the ones that caused errors. * * @return the number */ public int getTotalStatements() { return totalStatements; } public String getOnError() { return this.onError; } public void setOnError(String action) { if (ON_ERROR_ABORT.equalsIgnoreCase(action)) { this.onError = ON_ERROR_ABORT; } else if (ON_ERROR_CONTINUE.equalsIgnoreCase(action)) { this.onError = ON_ERROR_CONTINUE; } else if (ON_ERROR_ABORT_AFTER.equalsIgnoreCase(action)) { this.onError = ON_ERROR_ABORT_AFTER; } else { throw new IllegalArgumentException(action + " is not a valid value for onError, only '" + ON_ERROR_ABORT + "', '" + ON_ERROR_ABORT_AFTER + "', or '" + ON_ERROR_CONTINUE + "'."); } } /** * Validate the database connection by executing all validation SQLs. * * @param conn the open database connection * @throws java.sql.SQLException if SQL execution fails */ protected void validateConnection(Connection conn) throws SQLException { if (connectionValidationSqls != null && !connectionValidationSqls.isEmpty()) { for (String sql : connectionValidationSqls) { if (sql != null && sql.trim().length() > 0) { conn.createStatement().executeQuery(sql); } } } } /** * Waits the time specified in connectionRetryInterval. * * @throws MojoExecutionException */ private void waitToRetryConnectionValidation() throws MojoExecutionException { try { Thread.sleep(this.connectionRetryInterval * 1000); } catch (InterruptedException e) { throw new MojoExecutionException(e.getMessage(), e); } } /** * Closes the database connection. */ void closeConnection() { if (conn != null) { try { conn.close(); } catch (SQLException e) { getLog().debug("Failed to close connection: " + e.getMessage()); } } } /** * Closes the database statement. */ private void closeStatement() { if (statement != null) { try { statement.close(); } catch (SQLException e) { getLog().debug("Failed to close statement: " + e.getMessage()); } } } /** * Check is the database connection closed. * * @return true if the connection is closed, otherwise false */ protected boolean isConnectionClosed() { try { return conn == null || conn.isClosed(); } catch (SQLException e) { return false; } } protected void clear() { sqlCommand = null; if (transactions != null) { transactions.clear(); } } protected int getConnectionRetryAttempts() { return this.connectionRetryAttempts; } public void setConnectionValidationSqls(List connectionValidationSql) { this.connectionValidationSqls = connectionValidationSql; } public void setConnectionRetryCount(int connectionRetryCount) { this.connectionRetryCount = connectionRetryCount; } public void setConnectionRetryInterval(int connectionRetryInterval) { this.connectionRetryInterval = connectionRetryInterval; } void setSettings(Settings settings) { this.settings = settings; } void setSettingsKey(String key) { this.settingsKey = key; } void setSkip(boolean skip) { this.skip = skip; } public void setSkipMissingFiles(boolean skipMissingFiles) { this.skipMissingFiles = skipMissingFiles; } public void setDriverProperties(String driverProperties) { this.driverProperties = driverProperties; } public String getSqlCommand() { return sqlCommand; } public void setSqlCommand(String sqlCommand) { this.sqlCommand = sqlCommand; } public List getTransactions() { return transactions; } public void setTransactions(List transactions) { this.transactions = transactions; } public void setFileFilter(MavenFileFilter filter) { this.fileFilter = filter; } public void setSecurityDispatcher(SecDispatcher securityDispatcher) { this.securityDispatcher = securityDispatcher; } public void setOutputDelimiter(String outputDelimiter) { this.outputDelimiter = outputDelimiter; } public void setOutputEncoding(String outputEncoding) { this.outputEncoding = outputEncoding; } public void setShowFooter(boolean showFooter) { this.showFooter = showFooter; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy