123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647 |
- <?php
- /*
- * $Id: PDOSQLExecTask.php 905 2010-10-05 16:28:03Z mrook $
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * This software consists of voluntary contributions made by many individuals
- * and is licensed under the LGPL. For more information please see
- * <http://phing.info>.
- */
- require_once 'phing/tasks/ext/pdo/PDOTask.php';
- include_once 'phing/system/io/StringReader.php';
- include_once 'phing/tasks/ext/pdo/PDOSQLExecFormatterElement.php';
- /**
- * Executes a series of SQL statements on a database using PDO.
- *
- * <p>Statements can
- * either be read in from a text file using the <i>src</i> attribute or from
- * between the enclosing SQL tags.</p>
- *
- * <p>Multiple statements can be provided, separated by semicolons (or the
- * defined <i>delimiter</i>). Individual lines within the statements can be
- * commented using either --, // or REM at the start of the line.</p>
- *
- * <p>The <i>autocommit</i> attribute specifies whether auto-commit should be
- * turned on or off whilst executing the statements. If auto-commit is turned
- * on each statement will be executed and committed. If it is turned off the
- * statements will all be executed as one transaction.</p>
- *
- * <p>The <i>onerror</i> attribute specifies how to proceed when an error occurs
- * during the execution of one of the statements.
- * The possible values are: <b>continue</b> execution, only show the error;
- * <b>stop</b> execution and commit transaction;
- * and <b>abort</b> execution and transaction and fail task.</p>
- *
- * @author Hans Lellelid <hans@xmpl.org> (Phing)
- * @author Jeff Martin <jeff@custommonkey.org> (Ant)
- * @author Michael McCallum <gholam@xtra.co.nz> (Ant)
- * @author Tim Stephenson <tim.stephenson@sybase.com> (Ant)
- * @package phing.tasks.ext.pdo
- * @version $Revision: 905 $
- */
- class PDOSQLExecTask extends PDOTask {
- /**
- * Count of how many statements were executed successfully.
- * @var int
- */
- private $goodSql = 0;
- /**
- * Count of total number of SQL statements.
- * @var int
- */
- private $totalSql = 0;
- const DELIM_ROW = "row";
- const DELIM_NORMAL = "normal";
- /**
- * Database connection
- * @var PDO
- */
- private $conn = null;
- /**
- * Files to load
- * @var array FileSet[]
- */
- private $filesets = array();
- /**
- * Files to load
- * @var array FileList[]
- */
- private $filelists = array();
-
- /**
- * Formatter elements.
- * @var array PDOSQLExecFormatterElement[]
- */
- private $formatters = array();
- /**
- * SQL statement
- * @var PDOStatement
- */
- private $statement;
- /**
- * SQL input file
- * @var PhingFile
- */
- private $srcFile;
- /**
- * SQL input command
- * @var string
- */
- private $sqlCommand = "";
- /**
- * SQL transactions to perform
- */
- private $transactions = array();
- /**
- * SQL Statement delimiter (for parsing files)
- * @var string
- */
- private $delimiter = ";";
- /**
- * The delimiter type indicating whether the delimiter will
- * only be recognized on a line by itself
- */
- private $delimiterType = "normal"; // can't use constant just defined
- /**
- * Action to perform if an error is found
- **/
- private $onError = "abort";
- /**
- * Encoding to use when reading SQL statements from a file
- */
- private $encoding = null;
- /**
- * Fetch mode for PDO select queries.
- * @var int
- */
- private $fetchMode;
- /**
- * Set the name of the SQL file to be run.
- * Required unless statements are enclosed in the build file
- */
- public function setSrc(PhingFile $srcFile) {
- $this->srcFile = $srcFile;
- }
- /**
- * Set an inline SQL command to execute.
- * NB: Properties are not expanded in this text.
- */
- public function addText($sql) {
- $this->sqlCommand .= $sql;
- }
- /**
- * Adds a set of files (nested fileset attribute).
- */
- public function addFileset(FileSet $set) {
- $this->filesets[] = $set;
- }
- /**
- * Adds a set of files (nested filelist attribute).
- */
- public function addFilelist(FileList $list) {
- $this->filelists[] = $list;
- }
-
- /**
- * Creates a new PDOSQLExecFormatterElement for <formatter> element.
- * @return PDOSQLExecFormatterElement
- */
- public function createFormatter()
- {
- $fe = new PDOSQLExecFormatterElement($this);
- $this->formatters[] = $fe;
- return $fe;
- }
- /**
- * Add a SQL transaction to execute
- */
- public function createTransaction() {
- $t = new PDOSQLExecTransaction($this);
- $this->transactions[] = $t;
- return $t;
- }
- /**
- * Set the file encoding to use on the SQL files read in
- *
- * @param encoding the encoding to use on the files
- */
- public function setEncoding($encoding) {
- $this->encoding = $encoding;
- }
- /**
- * Set the statement delimiter.
- *
- * <p>For example, set this to "go" and delimitertype to "ROW" for
- * Sybase ASE or MS SQL Server.</p>
- *
- * @param delimiter
- */
- public function setDelimiter($delimiter)
- {
- $this->delimiter = $delimiter;
- }
- /**
- * Set the Delimiter type for this sql task. The delimiter type takes two
- * values - normal and row. Normal means that any occurence 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.
- *
- * @param string $delimiterType
- */
- public function setDelimiterType($delimiterType)
- {
- $this->delimiterType = $delimiterType;
- }
- /**
- * Action to perform when statement fails: continue, stop, or abort
- * optional; default "abort"
- */
- public function setOnerror($action) {
- $this->onError = $action;
- }
- /**
- * Sets the fetch mode to use for the PDO resultset.
- * @param mixed $mode The PDO fetchmode integer or constant name.
- */
- public function setFetchmode($mode) {
- if (is_numeric($mode)) {
- $this->fetchMode = (int) $mode;
- } else {
- if (defined($mode)) {
- $this->fetchMode = constant($mode);
- } else {
- throw new BuildException("Invalid PDO fetch mode specified: " . $mode, $this->getLocation());
- }
- }
- }
- /**
- * Gets a default output writer for this task.
- * @return Writer
- */
- private function getDefaultOutput()
- {
- return new LogWriter($this);
- }
- /**
- * Load the sql file and then execute it
- * @throws BuildException
- */
- public function main() {
- // Set a default fetchmode if none was specified
- // (We're doing that here to prevent errors loading the class is PDO is not available.)
- if ($this->fetchMode === null) {
- $this->fetchMode = PDO::FETCH_BOTH;
- }
- // Initialize the formatters here. This ensures that any parameters passed to the formatter
- // element get passed along to the actual formatter object
- foreach($this->formatters as $fe) {
- $fe->prepare();
- }
- $savedTransaction = array();
- for($i=0,$size=count($this->transactions); $i < $size; $i++) {
- $savedTransaction[] = clone $this->transactions[$i];
- }
- $savedSqlCommand = $this->sqlCommand;
- $this->sqlCommand = trim($this->sqlCommand);
- try {
- if ($this->srcFile === null && $this->sqlCommand === ""
- && empty($this->filesets) && empty($this->filelists)
- && count($this->transactions) === 0) {
- throw new BuildException("Source file or fileset/filelist, "
- . "transactions or sql statement "
- . "must be set!", $this->location);
- }
- if ($this->srcFile !== null && !$this->srcFile->exists()) {
- throw new BuildException("Source file does not exist!", $this->location);
- }
- // deal with the filesets
- foreach($this->filesets as $fs) {
- $ds = $fs->getDirectoryScanner($this->project);
- $srcDir = $fs->getDir($this->project);
- $srcFiles = $ds->getIncludedFiles();
- // Make a transaction for each file
- foreach($srcFiles as $srcFile) {
- $t = $this->createTransaction();
- $t->setSrc(new PhingFile($srcDir, $srcFile));
- }
- }
-
- // process filelists
- foreach($this->filelists as $fl) {
- $srcDir = $fl->getDir($this->project);
- $srcFiles = $fl->getFiles($this->project);
- // Make a transaction for each file
- foreach($srcFiles as $srcFile) {
- $t = $this->createTransaction();
- $t->setSrc(new PhingFile($srcDir, $srcFile));
- }
- }
- // Make a transaction group for the outer command
- $t = $this->createTransaction();
- if ($this->srcFile) $t->setSrc($this->srcFile);
- $t->addText($this->sqlCommand);
- $this->conn = $this->getConnection();
- try {
- $this->statement = null;
- // Initialize the formatters.
- $this->initFormatters();
- try {
- // Process all transactions
- for ($i=0,$size=count($this->transactions); $i < $size; $i++) {
- if (!$this->isAutocommit()) {
- $this->log("Beginning transaction", Project::MSG_VERBOSE);
- $this->conn->beginTransaction();
- }
- $this->transactions[$i]->runTransaction();
- if (!$this->isAutocommit()) {
- $this->log("Commiting transaction", Project::MSG_VERBOSE);
- $this->conn->commit();
- }
- }
- } catch (Exception $e) {
- throw $e;
- }
- } catch (IOException $e) {
- if (!$this->isAutocommit() && $this->conn !== null && $this->onError == "abort") {
- try {
- $this->conn->rollback();
- } catch (PDOException $ex) {}
- }
- throw new BuildException($e->getMessage(), $this->location);
- } catch (PDOException $e){
- if (!$this->isAutocommit() && $this->conn !== null && $this->onError == "abort") {
- try {
- $this->conn->rollback();
- } catch (PDOException $ex) {}
- }
- throw new BuildException($e->getMessage(), $this->location);
- }
-
- // Close the formatters.
- $this->closeFormatters();
- $this->log($this->goodSql . " of " . $this->totalSql .
- " SQL statements executed successfully");
- } catch (Exception $e) {
- $this->transactions = $savedTransaction;
- $this->sqlCommand = $savedSqlCommand;
- throw $e;
- }
- // finally {
- $this->transactions = $savedTransaction;
- $this->sqlCommand = $savedSqlCommand;
- }
- /**
- * read in lines and execute them
- * @throws PDOException, IOException
- */
- public function runStatements(Reader $reader) {
- $sql = "";
- $line = "";
- $sqlBacklog = "";
- $hasQuery = false;
- $in = new BufferedReader($reader);
- try {
- while (($line = $in->readLine()) !== null) {
- $line = trim($line);
- $line = ProjectConfigurator::replaceProperties($this->project, $line,
- $this->project->getProperties());
- if (($line != $this->delimiter) && (
- StringHelper::startsWith("//", $line) ||
- StringHelper::startsWith("--", $line) ||
- StringHelper::startsWith("#", $line))) {
- continue;
- }
- if (strlen($line) > 4
- && strtoupper(substr($line,0, 4)) == "REM ") {
- continue;
- }
- // MySQL supports defining new delimiters
- if (preg_match('/DELIMITER [\'"]?([^\'" $]+)[\'"]?/i', $line, $matches)) {
- $this->setDelimiter($matches[1]);
- continue;
- }
- if ($sqlBacklog !== "") {
- $sql = $sqlBacklog;
- $sqlBacklog = "";
- }
- $sql .= " " . $line . "\n";
- // 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 (strpos($line, "--") !== false) {
- $sql .= "\n";
- }
- // DELIM_ROW doesn't need this (as far as i can tell)
- if ($this->delimiterType == self::DELIM_NORMAL) {
- $reg = "#((?:\"(?:\\\\.|[^\"])*\"?)+|'(?:\\\\.|[^'])*'?|" . preg_quote($this->delimiter) . ")#";
- $sqlParts = preg_split($reg, $sql, 0, PREG_SPLIT_DELIM_CAPTURE);
- $sqlBacklog = "";
- foreach ($sqlParts as $sqlPart) {
- // we always want to append, even if it's a delim (which will be stripped off later)
- $sqlBacklog .= $sqlPart;
- // we found a single (not enclosed by ' or ") delimiter, so we can use all stuff before the delim as the actual query
- if ($sqlPart === $this->delimiter) {
- $sql = $sqlBacklog;
- $sqlBacklog = "";
- $hasQuery = true;
- }
- }
- }
- if ($hasQuery || ($this->delimiterType == self::DELIM_ROW && $line == $this->delimiter)) {
- // this assumes there is always a delimter on the end of the SQL statement.
- $sql = StringHelper::substring($sql, 0, strlen($sql) - 1 - strlen($this->delimiter));
- $this->log("SQL: " . $sql, Project::MSG_VERBOSE);
- $this->execSQL($sql);
- $sql = "";
- $hasQuery = false;
- }
- }
- // Catch any statements not followed by ;
- if ($sql !== "") {
- $this->execSQL($sql);
- }
- } catch (PDOException $e) {
- throw $e;
- }
- }
- /**
- * Whether the passed-in SQL statement is a SELECT statement.
- * This does a pretty simple match, checking to see if statement starts with
- * 'select' (but not 'select into').
- *
- * @param string $sql
- * @return boolean Whether specified SQL looks like a SELECT query.
- */
- protected function isSelectSql($sql)
- {
- $sql = trim($sql);
- return (stripos($sql, 'select') === 0 && stripos($sql, 'select into ') !== 0);
- }
- /**
- * Exec the sql statement.
- * @throws PDOException
- */
- protected function execSQL($sql) {
- // Check and ignore empty statements
- if (trim($sql) == "") {
- return;
- }
- try {
- $this->totalSql++;
- $this->statement = $this->conn->prepare($sql);
- $this->statement->execute();
- $this->log($this->statement->rowCount() . " rows affected", Project::MSG_VERBOSE);
- // only call processResults() for statements that return actual data (such as 'select')
- if ($this->statement->columnCount() > 0)
- {
- $this->processResults();
- }
- $this->statement->closeCursor();
- $this->statement = null;
- $this->goodSql++;
- } catch (PDOException $e) {
- $this->log("Failed to execute: " . $sql, Project::MSG_ERR);
- if ($this->onError != "continue") {
- throw new BuildException("Failed to execute SQL", $e);
- }
- $this->log($e->getMessage(), Project::MSG_ERR);
- }
- }
- /**
- * Returns configured PDOResultFormatter objects (which were created from PDOSQLExecFormatterElement objects).
- * @return array PDOResultFormatter[]
- */
- protected function getConfiguredFormatters()
- {
- $formatters = array();
- foreach ($this->formatters as $fe) {
- $formatters[] = $fe->getFormatter();
- }
- return $formatters;
- }
- /**
- * Initialize the formatters.
- */
- protected function initFormatters() {
- $formatters = $this->getConfiguredFormatters();
- foreach ($formatters as $formatter) {
- $formatter->initialize();
- }
- }
- /**
- * Run cleanup and close formatters.
- */
- protected function closeFormatters() {
- $formatters = $this->getConfiguredFormatters();
- foreach ($formatters as $formatter) {
- $formatter->close();
- }
- }
- /**
- * Passes results from query to any formatters.
- * @throw PDOException
- */
- protected function processResults() {
- try {
- $this->log("Processing new result set.", Project::MSG_VERBOSE);
- $formatters = $this->getConfiguredFormatters();
- while ($row = $this->statement->fetch($this->fetchMode)) {
- foreach ($formatters as $formatter) {
- $formatter->processRow($row);
- }
- }
- } catch (Exception $x) {
- $this->log("Error processing reults: " . $x->getMessage(), Project::MSG_ERR);
- foreach ($formatters as $formatter) {
- $formatter->close();
- }
- throw $x;
- }
- }
- }
- /**
- * "Inner" class that 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.
- *
- * @package phing.tasks.ext.pdo
- */
- class PDOSQLExecTransaction {
- private $tSrcFile = null;
- private $tSqlCommand = "";
- private $parent;
- function __construct($parent)
- {
- // Parent is required so that we can log things ...
- $this->parent = $parent;
- }
- public function setSrc(PhingFile $src)
- {
- $this->tSrcFile = $src;
- }
- public function addText($sql)
- {
- $this->tSqlCommand .= $sql;
- }
- /**
- * @throws IOException, PDOException
- */
- public function runTransaction()
- {
- if (!empty($this->tSqlCommand)) {
- $this->parent->log("Executing commands", Project::MSG_INFO);
- $this->parent->runStatements(new StringReader($this->tSqlCommand));
- }
- if ($this->tSrcFile !== null) {
- $this->parent->log("Executing file: " . $this->tSrcFile->getAbsolutePath(),
- Project::MSG_INFO);
- $reader = new FileReader($this->tSrcFile);
- $this->parent->runStatements($reader);
- $reader->close();
- }
- }
- }
|