/oiserver/lib/symfony/plugins/sfPropelPlugin/lib/vendor/phing/tasks/ext/pdo/PDOSQLExecTask.php

http://openirudi.googlecode.com/ · PHP · 614 lines · 306 code · 91 blank · 217 comment · 49 complexity · 0b20b34904491ba3db8f548784c745d7 MD5 · raw file

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