home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / PEAR / phing / tasks / ext / pdo / PDOSQLExecTask.php < prev    next >
Encoding:
PHP Script  |  2007-10-23  |  15.9 KB  |  583 lines

  1. <?php
  2. /*
  3.  *  $Id: CreoleSQLExecTask.php 83 2006-07-07 18:17:00Z mrook $
  4.  *
  5.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  6.  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  7.  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  8.  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  9.  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  10.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  11.  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  12.  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  13.  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  14.  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  15.  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  16.  *
  17.  * This software consists of voluntary contributions made by many individuals
  18.  * and is licensed under the LGPL. For more information please see
  19.  * <http://phing.info>.
  20.  */
  21.  
  22. require_once 'phing/tasks/ext/pdo/PDOTask.php';
  23. include_once 'phing/system/io/StringReader.php';
  24. include_once 'phing/tasks/ext/pdo/PDOSQLExecFormatterElement.php';
  25.  
  26. /**
  27.  * Executes a series of SQL statements on a database using PDO.
  28.  *
  29.  * <p>Statements can
  30.  * either be read in from a text file using the <i>src</i> attribute or from 
  31.  * between the enclosing SQL tags.</p>
  32.  * 
  33.  * <p>Multiple statements can be provided, separated by semicolons (or the 
  34.  * defined <i>delimiter</i>). Individual lines within the statements can be 
  35.  * commented using either --, // or REM at the start of the line.</p>
  36.  * 
  37.  * <p>The <i>autocommit</i> attribute specifies whether auto-commit should be 
  38.  * turned on or off whilst executing the statements. If auto-commit is turned 
  39.  * on each statement will be executed and committed. If it is turned off the 
  40.  * statements will all be executed as one transaction.</p>
  41.  * 
  42.  * <p>The <i>onerror</i> attribute specifies how to proceed when an error occurs 
  43.  * during the execution of one of the statements. 
  44.  * The possible values are: <b>continue</b> execution, only show the error;
  45.  * <b>stop</b> execution and commit transaction;
  46.  * and <b>abort</b> execution and transaction and fail task.</p>
  47.  *
  48.  * @author    Hans Lellelid <hans@xmpl.org> (Phing)
  49.  * @author    Jeff Martin <jeff@custommonkey.org> (Ant)
  50.  * @author    Michael McCallum <gholam@xtra.co.nz> (Ant)
  51.  * @author    Tim Stephenson <tim.stephenson@sybase.com> (Ant)
  52.  * @package   phing.tasks.ext
  53.  * @version   $Revision: 1.21 $
  54.  */
  55. class PDOSQLExecTask extends PDOTask {
  56.  
  57.     /**
  58.      * Count of how many statements were executed successfully.
  59.      * @var int
  60.      */
  61.     private $goodSql = 0;
  62.  
  63.     /**
  64.      * Count of total number of SQL statements.
  65.      * @var int
  66.      */
  67.     private $totalSql = 0;
  68.  
  69.     const DELIM_ROW = "row";
  70.     const DELIM_NORMAL = "normal";
  71.  
  72.     /**
  73.      * Database connection
  74.      * @var PDO
  75.      */
  76.     private $conn = null;
  77.  
  78.     /**
  79.      * Files to load
  80.      * @var array FileSet[]
  81.      */
  82.     private $filesets = array();
  83.  
  84.     /**
  85.      * Formatter elements.
  86.      * @var array PDOSQLExecFormatterElement[]
  87.      */
  88.     private $formatters = array();
  89.  
  90.     /**
  91.      * SQL statement
  92.      * @var PDOStatement
  93.      */
  94.     private $statement;
  95.  
  96.     /**
  97.      * SQL input file
  98.      * @var PhingFile
  99.      */
  100.     private $srcFile;
  101.  
  102.     /**
  103.      * SQL input command
  104.      * @var string
  105.      */
  106.     private $sqlCommand = "";
  107.  
  108.     /**
  109.      * SQL transactions to perform
  110.      */
  111.     private $transactions = array();
  112.  
  113.     /**
  114.      * SQL Statement delimiter (for parsing files)
  115.      * @var string
  116.      */
  117.     private $delimiter = ";";
  118.  
  119.     /**
  120.      * The delimiter type indicating whether the delimiter will
  121.      * only be recognized on a line by itself
  122.      */
  123.     private $delimiterType = "normal"; // can't use constant just defined
  124.  
  125.     /**
  126.      * Action to perform if an error is found
  127.      **/
  128.     private $onError = "abort";
  129.  
  130.     /**
  131.      * Encoding to use when reading SQL statements from a file
  132.      */
  133.     private $encoding = null;
  134.  
  135.     /**
  136.      * Fetch mode for PDO select queries.
  137.      * @var int
  138.      */
  139.     private $fetchMode;
  140.  
  141.     /**
  142.      * Set the name of the SQL file to be run.
  143.      * Required unless statements are enclosed in the build file
  144.      */
  145.     public function setSrc(PhingFile $srcFile) {
  146.         $this->srcFile = $srcFile;
  147.     }
  148.  
  149.     /**
  150.      * Set an inline SQL command to execute. 
  151.      * NB: Properties are not expanded in this text.
  152.      */
  153.     public function addText($sql) {
  154.         $this->sqlCommand .= $sql;
  155.     }
  156.  
  157.     /**
  158.      * Adds a set of files (nested fileset attribute).
  159.      */
  160.     public function addFileset(FileSet $set) {
  161.         $this->filesets[] = $set;
  162.     }
  163.  
  164.     /**
  165.      * Creates a new PDOSQLExecFormatterElement for <formatter> element.
  166.      * @return PDOSQLExecFormatterElement
  167.      */
  168.     public function createFormatter()
  169.     {
  170.         $fe = new PDOSQLExecFormatterElement($this);
  171.         $this->formatters[] = $fe;
  172.         return $fe;
  173.     }
  174.  
  175.     /**
  176.      * Add a SQL transaction to execute
  177.      */
  178.     public function createTransaction() {
  179.         $t = new PDOSQLExecTransaction($this);
  180.         $this->transactions[] = $t;
  181.         return $t;
  182.     }
  183.  
  184.     /**
  185.      * Set the file encoding to use on the SQL files read in
  186.      *
  187.      * @param encoding the encoding to use on the files
  188.      */
  189.     public function setEncoding($encoding) {
  190.         $this->encoding = $encoding;
  191.     }
  192.  
  193.     /**
  194.      * Set the statement delimiter.
  195.      *
  196.      * <p>For example, set this to "go" and delimitertype to "ROW" for
  197.      * Sybase ASE or MS SQL Server.</p>
  198.      *
  199.      * @param delimiter
  200.      */
  201.     public function setDelimiter($delimiter)
  202.     {
  203.         $this->delimiter = $delimiter;
  204.     }
  205.  
  206.     /**
  207.      * Set the Delimiter type for this sql task. The delimiter type takes two
  208.      * values - normal and row. Normal means that any occurence of the delimiter
  209.      * terminate the SQL command whereas with row, only a line containing just
  210.      * the delimiter is recognized as the end of the command.
  211.      *
  212.      * @param string $delimiterType
  213.      */
  214.     public function setDelimiterType($delimiterType)
  215.     {
  216.         $this->delimiterType = $delimiterType;
  217.     }
  218.  
  219.     /**
  220.      * Action to perform when statement fails: continue, stop, or abort
  221.      * optional; default "abort"
  222.      */
  223.     public function setOnerror($action) {
  224.         $this->onError = $action;
  225.     }
  226.  
  227.     /**
  228.      * Sets the fetch mode to use for the PDO resultset.
  229.      * @param mixed $mode The PDO fetchmode integer or constant name.
  230.      */
  231.     public function setFetchmode($mode) {
  232.         if (is_numeric($mode)) {
  233.             $this->fetchMode = (int) $mode;
  234.         } else {
  235.             if (defined($mode)) {
  236.                 $this->fetchMode = constant($mode);
  237.             } else {
  238.                 throw new BuildException("Invalid PDO fetch mode specified: " . $mode, $this->getLocation());
  239.             }
  240.         }
  241.     }
  242.  
  243.     /**
  244.      * Gets a default output writer for this task.
  245.      * @return Writer
  246.      */
  247.     private function getDefaultOutput()
  248.     {
  249.         return new LogWriter($this);
  250.     }
  251.  
  252.     /**
  253.      * Load the sql file and then execute it
  254.      * @throws BuildException
  255.      */
  256.     public function main()  {
  257.  
  258.         // Set a default fetchmode if none was specified
  259.         // (We're doing that here to prevent errors loading the class is PDO is not available.)
  260.         if ($this->fetchMode === null) {
  261.             $this->fetchMode = PDO::FETCH_BOTH;
  262.         }
  263.  
  264.         // Initialize the formatters here.  This ensures that any parameters passed to the formatter
  265.         // element get passed along to the actual formatter object
  266.         foreach($this->formatters as $fe) {
  267.             $fe->prepare();
  268.         }
  269.  
  270.         $savedTransaction = array();
  271.         for($i=0,$size=count($this->transactions); $i < $size; $i++) {
  272.             $savedTransaction[] = clone $this->transactions[$i];
  273.         }
  274.  
  275.         $savedSqlCommand = $this->sqlCommand;
  276.  
  277.         $this->sqlCommand = trim($this->sqlCommand);
  278.  
  279.         try {
  280.             if ($this->srcFile === null && $this->sqlCommand === ""
  281.             && empty($this->filesets)) {
  282.                 if (count($this->transactions) === 0) {
  283.                     throw new BuildException("Source file or fileset, "
  284.                     . "transactions or sql statement "
  285.                     . "must be set!", $this->location);
  286.                 }
  287.             }
  288.  
  289.             if ($this->srcFile !== null && !$this->srcFile->exists()) {
  290.                 throw new BuildException("Source file does not exist!", $this->location);
  291.             }
  292.  
  293.             // deal with the filesets
  294.             foreach($this->filesets as $fs) {
  295.                 $ds = $fs->getDirectoryScanner($this->project);
  296.                 $srcDir = $fs->getDir($this->project);
  297.                 $srcFiles = $ds->getIncludedFiles();
  298.                 // Make a transaction for each file
  299.                 foreach($srcFiles as $srcFile) {
  300.                     $t = $this->createTransaction();
  301.                     $t->setSrc(new PhingFile($srcDir, $srcFile));
  302.                 }
  303.             }
  304.  
  305.             // Make a transaction group for the outer command
  306.             $t = $this->createTransaction();
  307.             if ($this->srcFile) $t->setSrc($this->srcFile);
  308.             $t->addText($this->sqlCommand);
  309.             $this->conn = $this->getConnection();
  310.  
  311.             try {
  312.  
  313.                 $this->statement = null;
  314.  
  315.                 // Initialize the formatters.
  316.                 $this->initFormatters();
  317.  
  318.                 try {
  319.  
  320.                     // Process all transactions
  321.                     for ($i=0,$size=count($this->transactions); $i < $size; $i++) {
  322.                         if (!$this->isAutocommit()) {
  323.                             $this->log("Beginning transaction", Project::MSG_VERBOSE);
  324.                             $this->conn->beginTransaction();
  325.                         }
  326.                         $this->transactions[$i]->runTransaction();
  327.                         if (!$this->isAutocommit()) {
  328.                             $this->log("Commiting transaction", Project::MSG_VERBOSE);
  329.                             $this->conn->commit();
  330.                         }
  331.                     }
  332.                 } catch (Exception $e) {
  333.                     throw $e;
  334.                 }
  335.             } catch (IOException $e) {
  336.                 if (!$this->isAutocommit() && $this->conn !== null && $this->onError == "abort") {
  337.                     try {
  338.                         $this->conn->rollback();
  339.                     } catch (PDOException $ex) {}
  340.                 }
  341.                 throw new BuildException($e->getMessage(), $this->location);
  342.             } catch (PDOException $e){
  343.                 if (!$this->isAutocommit() && $this->conn !== null && $this->onError == "abort") {
  344.                     try {
  345.                         $this->conn->rollback();
  346.                     } catch (PDOException $ex) {}
  347.                 }
  348.                 throw new BuildException($e->getMessage(), $this->location);
  349.             }
  350.                 
  351.             // Close the formatters.
  352.             $this->closeFormatters();
  353.  
  354.             $this->log($this->goodSql . " of " . $this->totalSql .
  355.                 " SQL statements executed successfully");
  356.  
  357.         } catch (Exception $e) {
  358.             $this->transactions = $savedTransaction;
  359.             $this->sqlCommand = $savedSqlCommand;
  360.             throw $e;
  361.         }
  362.         // finally {
  363.         $this->transactions = $savedTransaction;
  364.         $this->sqlCommand = $savedSqlCommand;
  365.  
  366.     }
  367.  
  368.  
  369.     /**
  370.      * read in lines and execute them
  371.      * @throws PDOException, IOException 
  372.      */
  373.     public function runStatements(Reader $reader) {
  374.         $sql = "";
  375.         $line = "";
  376.         $in = new BufferedReader($reader);
  377.         try {
  378.             while (($line = $in->readLine()) !== null) {
  379.                 $line = trim($line);
  380.                 $line = ProjectConfigurator::replaceProperties($this->project, $line,
  381.                 $this->project->getProperties());
  382.  
  383.                 if (StringHelper::startsWith("//", $line) ||
  384.                 StringHelper::startsWith("--", $line) ||
  385.                 StringHelper::startsWith("#", $line)) {
  386.                     continue;
  387.                 }
  388.  
  389.                 if (strlen($line) > 4
  390.                 && strtoupper(substr($line,0, 4)) == "REM ") {
  391.                     continue;
  392.                 }
  393.  
  394.                 $sql .= " " . $line;
  395.                 $sql = trim($sql);
  396.  
  397.                 // SQL defines "--" as a comment to EOL
  398.                 // and in Oracle it may contain a hint
  399.                 // so we cannot just remove it, instead we must end it
  400.                 if (strpos($line, "--") !== false) {
  401.                     $sql .= "\n";
  402.                 }
  403.  
  404.                 if ($this->delimiterType == self::DELIM_NORMAL
  405.                 && StringHelper::endsWith($this->delimiter, $sql)
  406.                 || $this->delimiterType == self::DELIM_ROW
  407.                 && $line == $this->delimiter) {
  408.                     $this->log("SQL: " . $sql, Project::MSG_VERBOSE);
  409.                     $this->execSQL(StringHelper::substring($sql, 0, strlen($sql) - strlen($this->delimiter) - 1));
  410.                     $sql = "";
  411.                 }
  412.             }
  413.  
  414.             // Catch any statements not followed by ;
  415.             if ($sql !== "") {
  416.                 $this->execSQL($sql);
  417.             }
  418.         } catch (PDOException $e) {
  419.             throw new BuildException("Error running statements", $e);
  420.         }
  421.     }
  422.  
  423.     /**
  424.      * Whether the passed-in SQL statement is a SELECT statement.
  425.      * This does a pretty simple match, checking to see if statement starts with
  426.      * 'select' (but not 'select into').
  427.      * 
  428.      * @param string $sql
  429.      * @return boolean Whether specified SQL looks like a SELECT query.
  430.      */
  431.     protected function isSelectSql($sql)
  432.     {
  433.         $sql = trim($sql);
  434.         return (stripos($sql, 'select') === 0 && stripos($sql, 'select into ') !== 0);
  435.     }
  436.  
  437.     /**
  438.      * Exec the sql statement.
  439.      * @throws PDOException 
  440.      */
  441.     protected function execSQL($sql) {
  442.  
  443.         // Check and ignore empty statements
  444.         if (trim($sql) == "") {
  445.             return;
  446.         }
  447.  
  448.         try {
  449.             $this->totalSql++;
  450.  
  451.             $this->statement = $this->conn->prepare($sql);
  452.             $this->statement->execute();
  453.             $this->log($this->statement->rowCount() . " rows affected", Project::MSG_VERBOSE);
  454.  
  455.             $this->processResults();
  456.  
  457.             $this->statement->closeCursor();
  458.             $this->statement = null;
  459.  
  460.             $this->goodSql++;
  461.  
  462.         } catch (PDOException $e) {
  463.             $this->log("Failed to execute: " . $sql, Project::MSG_ERR);
  464.             if ($this->onError != "continue") {
  465.                 throw new BuildException("Failed to execute SQL", $e);
  466.             }
  467.             $this->log($e->getMessage(), Project::MSG_ERR);
  468.         }
  469.     }
  470.  
  471.     /**
  472.      * Returns configured PDOResultFormatter objects (which were created from PDOSQLExecFormatterElement objects).
  473.      * @return array PDOResultFormatter[]
  474.      */
  475.     protected function getConfiguredFormatters()
  476.     {
  477.         $formatters = array();
  478.         foreach ($this->formatters as $fe) {
  479.             $formatters[] = $fe->getFormatter();
  480.         }
  481.         return $formatters;
  482.     }
  483.  
  484.     /**
  485.      * Initialize the formatters.
  486.      */
  487.     protected function initFormatters() {
  488.         $formatters = $this->getConfiguredFormatters();
  489.         foreach ($formatters as $formatter) {
  490.             $formatter->initialize();
  491.         }
  492.  
  493.     }
  494.  
  495.     /**
  496.      * Run cleanup and close formatters.
  497.      */
  498.     protected function closeFormatters() {
  499.         $formatters = $this->getConfiguredFormatters();
  500.         foreach ($formatters as $formatter) {
  501.             $formatter->close();
  502.         }
  503.     }
  504.  
  505.     /**
  506.      * Passes results from query to any formatters.
  507.      * @throw PDOException
  508.      */
  509.     protected function processResults() {
  510.  
  511.         try {
  512.  
  513.             $this->log("Processing new result set.", Project::MSG_VERBOSE);
  514.  
  515.             $formatters = $this->getConfiguredFormatters();
  516.  
  517.             while ($row = $this->statement->fetch($this->fetchMode)) {
  518.                 foreach ($formatters as $formatter) {
  519.                     $formatter->processRow($row);
  520.                 }
  521.             }
  522.  
  523.         } catch (Exception $x) {
  524.             $this->log("Error processing reults: " . $x->getMessage(), Project::MSG_ERR);
  525.             foreach ($formatters as $formatter) {
  526.                 $formatter->close();
  527.             }
  528.             throw $x;
  529.         }
  530.  
  531.     }
  532. }
  533.  
  534. /**
  535.  * "Inner" class that contains the definition of a new transaction element.
  536.  * Transactions allow several files or blocks of statements
  537.  * to be executed using the same JDBC connection and commit
  538.  * operation in between.
  539.  */
  540. class PDOSQLExecTransaction {
  541.  
  542.     private $tSrcFile = null;
  543.     private $tSqlCommand = "";
  544.     private $parent;
  545.  
  546.     function __construct($parent)
  547.     {
  548.         // Parent is required so that we can log things ...
  549.         $this->parent = $parent;
  550.     }
  551.  
  552.     public function setSrc(PhingFile $src)
  553.     {
  554.         $this->tSrcFile = $src;
  555.     }
  556.  
  557.     public function addText($sql)
  558.     {
  559.         $this->tSqlCommand .= $sql;
  560.     }
  561.  
  562.     /**
  563.      * @throws IOException, PDOException
  564.      */
  565.     public function runTransaction()
  566.     {
  567.         if (!empty($this->tSqlCommand)) {
  568.             $this->parent->log("Executing commands", Project::MSG_INFO);
  569.             $this->parent->runStatements(new StringReader($this->tSqlCommand));
  570.         }
  571.  
  572.         if ($this->tSrcFile !== null) {
  573.             $this->parent->log("Executing file: " . $this->tSrcFile->getAbsolutePath(),
  574.             Project::MSG_INFO);
  575.             $reader = new FileReader($this->tSrcFile);
  576.             $this->parent->runStatements($reader);
  577.             $reader->close();
  578.         }
  579.     }
  580. }
  581.  
  582.  
  583.