DbDeployTask.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php
  2. /*
  3. * $Id: DbDeployTask.php 905 2010-10-05 16:28:03Z 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/Task.php';
  22. require_once 'phing/tasks/ext/dbdeploy/DbmsSyntaxFactory.php';
  23. /**
  24. * Generate SQL script for db using dbdeploy schema version table and delta scripts
  25. *
  26. * <dbdeploy url="mysql:host=localhost;dbname=test" userid="dbdeploy" password="dbdeploy" dir="db" outputfile="">
  27. *
  28. * @author Luke Crouch at SourceForge (http://sourceforge.net)
  29. * @version $Revision: 905 $
  30. * @package phing.tasks.ext.dbdeploy
  31. */
  32. class DbDeployTask extends Task {
  33. public static $TABLE_NAME = 'changelog';
  34. protected $url;
  35. protected $userid;
  36. protected $password;
  37. protected $dir;
  38. protected $outputFile = 'dbdeploy_deploy.sql';
  39. protected $undoOutputFile = 'dbdeploy_undo.sql';
  40. protected $deltaSet = 'Main';
  41. protected $lastChangeToApply = 999;
  42. protected $dbmsSyntax = null;
  43. function main() {
  44. try{
  45. // get correct DbmsSyntax object
  46. $dbms = substr($this->url, 0, strpos($this->url, ':'));
  47. $dbmsSyntaxFactory = new DbmsSyntaxFactory($dbms);
  48. $this->dbmsSyntax = $dbmsSyntaxFactory->getDbmsSyntax();
  49. // open file handles for output
  50. $outputFileHandle = fopen($this->outputFile, "w+");
  51. $undoOutputFileHandle = fopen($this->undoOutputFile, "w+");
  52. // figure out which revisions are in the db already
  53. $this->appliedChangeNumbers = $this->getAppliedChangeNumbers();
  54. $this->log('Current db revision: ' . $this->getLastChangeAppliedInDb());
  55. // generate sql file needed to take db to "lastChangeToApply" version
  56. $doSql = $this->doDeploy();
  57. $undoSql = $this->undoDeploy();
  58. // write the do and undo SQL to their respective files
  59. fwrite($outputFileHandle, $doSql);
  60. fwrite($undoOutputFileHandle, $undoSql);
  61. } catch (Exception $e){
  62. throw new BuildException($e);
  63. }
  64. }
  65. function getAppliedChangeNumbers(){
  66. if(count($this->appliedChangeNumbers) == 0){
  67. $this->log('Getting applied changed numbers from DB: ' . $this->url );
  68. $appliedChangeNumbers = array();
  69. $dbh = new PDO($this->url, $this->userid, $this->password);
  70. $dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  71. $sql = "SELECT * FROM " . DbDeployTask::$TABLE_NAME . " WHERE delta_set = '$this->deltaSet' ORDER BY change_number";
  72. foreach($dbh->query($sql) as $change){
  73. $appliedChangeNumbers[] = $change['change_number'];
  74. }
  75. $this->appliedChangeNumbers = $appliedChangeNumbers;
  76. }
  77. return $this->appliedChangeNumbers;
  78. }
  79. function getLastChangeAppliedInDb(){
  80. return (count($this->appliedChangeNumbers) > 0) ? max($this->appliedChangeNumbers) : 0;
  81. }
  82. function doDeploy(){
  83. $sqlToPerformDeploy = '';
  84. $lastChangeAppliedInDb = $this->getLastChangeAppliedInDb();
  85. $files = $this->getDeltasFilesArray();
  86. ksort($files);
  87. foreach($files as $fileChangeNumber=>$fileName){
  88. if($fileChangeNumber > $lastChangeAppliedInDb && $fileChangeNumber <= $this->lastChangeToApply){
  89. $sqlToPerformDeploy .= '-- Fragment begins: ' . $fileChangeNumber . ' --' . "\n";
  90. $sqlToPerformDeploy .= 'INSERT INTO ' . DbDeployTask::$TABLE_NAME . ' (change_number, delta_set, start_dt, applied_by, description)'.
  91. ' VALUES ('. $fileChangeNumber .', \''. $this->deltaSet .'\', '. $this->dbmsSyntax->generateTimestamp() .', \'dbdeploy\', \''. $fileName .'\');' . "\n";
  92. $fullFileName = $this->dir . '/' . $fileName;
  93. $fh = fopen($fullFileName, 'r');
  94. $contents = fread($fh, filesize($fullFileName));
  95. // allow construct with and without space added
  96. $deploySQLFromFileSplit = strpos($contents, '-- //@UNDO');
  97. if ($deploySQLFromFileSplit === false)
  98. $deploySQLFromFileSplit = strpos($contents, '--//@UNDO');
  99. $deploySQLFromFile = substr($contents,0,$deploySQLFromFileSplit);
  100. $sqlToPerformDeploy .= $deploySQLFromFile;
  101. $sqlToPerformDeploy .= 'UPDATE ' . DbDeployTask::$TABLE_NAME . ' SET complete_dt = ' . $this->dbmsSyntax->generateTimestamp() . ' WHERE change_number = ' . $fileChangeNumber . ' AND delta_set = \'' . $this->deltaSet . '\';' . "\n";
  102. $sqlToPerformDeploy .= '-- Fragment ends: ' . $fileChangeNumber . ' --' . "\n";
  103. }
  104. }
  105. return $sqlToPerformDeploy;
  106. }
  107. function undoDeploy(){
  108. $sqlToPerformUndo = '';
  109. $lastChangeAppliedInDb = $this->getLastChangeAppliedInDb();
  110. $files = $this->getDeltasFilesArray();
  111. krsort($files);
  112. foreach($files as $fileChangeNumber=>$fileName){
  113. if($fileChangeNumber > $lastChangeAppliedInDb && $fileChangeNumber <= $this->lastChangeToApply){
  114. $fullFileName = $this->dir . '/' . $fileName;
  115. $fh = fopen($fullFileName, 'r');
  116. $contents = fread($fh, filesize($fullFileName));
  117. $undoSQLFromFile = substr($contents,strpos($contents, '-- //@UNDO')+10);
  118. $sqlToPerformUndo .= $undoSQLFromFile;
  119. $sqlToPerformUndo .= 'DELETE FROM ' . DbDeployTask::$TABLE_NAME . ' WHERE change_number = ' . $fileChangeNumber . ' AND delta_set = \'' . $this->deltaSet . '\';' . "\n";
  120. $sqlToPerformUndo .= '-- Fragment ends: ' . $fileChangeNumber . ' --' . "\n";
  121. }
  122. }
  123. return $sqlToPerformUndo;
  124. }
  125. function getDeltasFilesArray(){
  126. $baseDir = realpath($this->dir);
  127. $dh = opendir($baseDir);
  128. $fileChangeNumberPrefix = '';
  129. while(($file = readdir($dh)) !== false){
  130. if(preg_match('[\d+]', $file, $fileChangeNumberPrefix)){
  131. $files[intval($fileChangeNumberPrefix[0])] = $file;
  132. }
  133. }
  134. return $files;
  135. }
  136. function setUrl($url){
  137. $this->url = $url;
  138. }
  139. function setUserId($userid){
  140. $this->userid = $userid;
  141. }
  142. function setPassword($password){
  143. $this->password = $password;
  144. }
  145. function setDir($dir){
  146. $this->dir = $dir;
  147. }
  148. function setOutputFile($outputFile){
  149. $this->outputFile = $outputFile;
  150. }
  151. function setUndoOutputFile($undoOutputFile){
  152. $this->undoOutputFile = $undoOutputFile;
  153. }
  154. function setLastChangeToApply($lastChangeToApply){
  155. $this->lastChangeToApply = $lastChangeToApply;
  156. }
  157. function setDeltaSet($deltaSet){
  158. $this->deltaSet = $deltaSet;
  159. }
  160. /**
  161. * Add a new fileset.
  162. * @return FileSet
  163. */
  164. public function createFileSet() {
  165. $this->fileset = new FileSet();
  166. $this->filesets[] = $this->fileset;
  167. return $this->fileset;
  168. }
  169. }