MySchemaShell

Ten skrypt zawiera kilka bugów (nie update’uje indeksów) i niepotrzebnych śmieci. Jest jednak użyteczny w przypadku, gdy mamy pewność, że baza danych jest pusta (start_data -drop), dlatego go umieszczam:

require_once(CONSOLE_LIBS . DS . 'schema.php');


/**
 * SchemaSchell with basic synchronisation added
 *
 * @author        Grzegorz Pawlik <www.grzregorzpawlik.com>
 * @version 1.3 
 * @note since 1.1 CHANGE oprerations are allowed  by default
 *       since 1.2 Not existing tables are created instead of crashing + -non-interactive mode is aviable
 *       since 1.3 fixed problem when You answered 'n' for change and MySchema didn't listen
 *                 index altering problem solved
 * @todo dorobić możliwośc wywołania update'u bazy razem z generowaniem tabel bez konieczności ingerencji kogoś z zewnątrz (-ad-only)
 * @todo with CHANGE operation - show old AND new table definition
 * @todo gdy został dodany indek (np primary key) to update nie działa (próbuje usunąć nieistniejące indeksy)
 * @todo gdy nie ma tabel, a następnie robimy update najpierw dodaje tabele, a następnie próbuje je update'ować (np
 *        dodawać pole, które już istnieje)
 */
class MySchemaShell extends SchemaShell {

/**
 * Override initialize
 *
 * @access public
 */
   function initialize() {
      $this->_welcome();
      $this->out('MySchema Shell (extended Cake Schema Shell)');
      $this->hr();
   }
   
   /**
    * Convert field details array into readable string
    *
    * @param array $details 
    * @return string with one line readable details
    * @access public
    * @note maybe this functionallity should go to Set class?
    */
   function __fieldDetailsToString($details, $indexes = false) {
      $str = '';
      
//      debug($details);
      foreach($details as $name => $value) {

         $value = ($value===false)? 'FALSE': $value;
         $value = ($value===null)? 'NULL': $value;
         
         if($indexes) {
            $str .= " ".$value['column'].": unique = ".$value['unique'].";";
         }else {
         
           $str .= " ".$name.":".$value.";";
         }
         
      }
      $str = trim($str, " ;");
      
      return $str;

   }
   
   /**
    * Allows user to decide which changes in DB are ok
    *
    * @param array $compare result from Schema::compare()
    * @return array without unwanted changes
    * @access private
    */
   function __humanCheck($compare) {
      $unset = 0;
      
      foreach($compare as $table => $changes) {
         $msg_table = "in ".$table . " table we are about to...\n";
         foreach($changes as $action => $field) {
            $msg_action = "\t...". strtoupper($action)."\n";
            foreach($field as $name=> $details) {
               
               /**
                * czasem jak jest update indeksów, to próbuje zrobić drop indexes (ale indeksów nie ma, więc wywala błąd)
                */
               if($details === null) {
                  unset($compare[$table][$action][$name]);
               }else {
                  
                  if($name=='indexes'){
                     $indexes = true;
                     $field = "";
                  }else {
                     $indexes = false;
                     $field = "field ";
                  }
                  
                  
                  $msg_field = "\t\t... ". $field . $name . "(" . $this->__fieldDetailsToString($details, $indexes). ")\n";
                  $save_change = ($action=='add' || $action=='change')? 'y' : 'n';
                  
                  /**
                   * don't display messages, and dont ask for anything if -add-only passed
                   */
                  if( !(isset($this->params['add-only'])) ) {
                    if($msg_table) {
                       $this->out($msg_table, false);
                       $msg_table = false;
                    }
                    if($msg_action) {
                       $this->out($msg_action, false);
                       $msg_action = false;
                    }
                    if($msg_field) {
                       $this->out($msg_field, false);
                       $msg_field = false;
                    }
                    $save_change = $this->in("Do You agree ?", null, $save_change);
                  }
                  
                  if($save_change === 'n') {
                     unset($compare[$table][$action][$name]);
                     $unset++;
                  }
               }
               
            }
         }
      }

      
      if( isset($this->params['add-only']) && $this->params['add-only'] == 1 && $unset) {
         $this->out("\n\t***Accorging to -add-only option there are $unset DROP'S ommited***");
      }
      
      return $compare;
   }
   
   function _createTablesIfNotExists($Schema) {
      
      
      $tables = Configure::listObjects('model') ; //lista modeli
      
      $db =& ConnectionManager::getDataSource($this->Schema->connection);
      
      $sources = $db->listSources(); // lista tabel fizycznie obecnych w bazie
      
      
      $create = array();
      // $Schema->tables - tabele w pliku schema.php
      foreach ($Schema->tables as $table => $fields) {
         if(!in_array($table, $sources )) {
            $create[$table] = $db->createSchema($Schema, $table);
         }
      }
      
      
      

      if(!empty($create)) {
         $ret =  $this->__run($create, 'create', $Schema);
         
      }
      
//      return $Schema;
   }
   
/**
 * Update database with Schema object
 * Should be called via the run method
 *
 * @access private
 */
   function __update($Schema, $table = null) {

      $this->_createTablesIfNotExists($Schema);

      
//      parent::startup();
//      $Schema = $this->Schema->load();
      
      $db =& ConnectionManager::getDataSource($this->Schema->connection);
      /**
       * need to disable source caching
       */
      $db->cacheSources = false;

      $this->out('Comparing Database to Schema...');

      $Old = $this->Schema->read();
      $compare = $this->Schema->compare($Old, $Schema);
      

      if(isset($this->params['with-check'])) {
         $compare = $this->__humanCheck($compare);
      }
      $contents = array();

      if (empty($table)) {
         foreach ($compare as $table => $changes) {
            $contents[$table] = $db->alterSchema(array($table => $changes), $table);
         }
      } elseif (isset($compare[$table])) {
         $contents[$table] = $db->alterSchema(array($table => $compare[$table]), $table);
      }


      if (empty($contents)) {
         $this->out(__('Schema is up to date.', true));
         $this->_stop();
      }

      /**
       * check if silent mode is on
       */
      if(!isset($this->params['non-interactive'])) {
         $this->out("\n" . __('The following statements will run.', true));
         $this->out(array_map('trim', $contents));
         if ('y' == $this->in(__('Are you sure you want to alter the tables?', true), array('y', 'n'), 'n')) {
            $this->out('');
            $this->out(__('Updating Database...', true));
            $this->__run($contents, 'update', $Schema);
         }
      }else {
            $this->out("\n" . __('The following statements will run.', true));
            $this->out(array_map('trim', $contents));
            $this->__run($contents, 'update', $Schema);
      }

      $this->out(__('End update.', true));
   }


/**
 * Displays help contents
 *
 * @access public
 */
   function help() {
      $this->out("The Schema Shell generates a schema object from \n\t\tthe database and updates the database from the schema.");
      $this->hr();
      $this->out("Usage: cake my_schema <command> <arg1> <arg2>...");
      $this->hr();
      $this->out('Params:');
      $this->out("\n\t-connection <config>\n\t\tset db config <config>. uses 'default' if none is specified");
      $this->out("\n\t-path <dir>\n\t\tpath <dir> to read and write schema.php.\n\t\tdefault path: ". $this->Schema->path);
      $this->out("\n\t-name <name>\n\t\tclassname to use.");
      $this->out("\n\t-file <name>\n\t\tfile <name> to read and write.\n\t\tdefault file: ". $this->Schema->file);
      $this->out("\n\t-s <number>\n\t\tsnapshot <number> to use for run.");
      $this->out("\n\t-dry\n\t\tPerform a dry run on 'run' commands.\n\t\tQueries will be output to window instead of executed.");
      $this->out("\n\t-f\n\t\tforce 'generate' to create a new schema.");
      $this->out("\n\t-with-check\n\t\tYou will be asked if particullar ADD or DROP field are valid or not.");
      $this->out("\n\t-add-only\n\t\tWhen used with -with-check - You won't be bothered about field DROP's or ADD's\n\t\tno DROP's would be performed, and all ADD's are allowed.");
      $this->out("\n\t-non-interactive\n\t\tWhen used with -add-only - You won't be wheather run or not SQL statements. They're just run based on -add-only behavior.");
      $this->out('Commands:');
      $this->out("\n\tschema help\n\t\tshows this help message.");
      $this->out("\n\tschema view\n\t\tread and output contents of schema file");
      $this->out("\n\tschema generate\n\t\treads from 'connection' writes to 'path'\n\t\tTo force generation of all tables into the schema, use the -f param.\n\t\tUse 'schema generate snapshot <number>' to generate snapshots\n\t\twhich you can use with the -s parameter in the other operations.");
      $this->out("\n\tschema dump <filename>\n\t\tDump database sql based on schema file to <filename>. \n\t\tIf <filename> is write, schema dump will be written to a file\n\t\tthat has the same name as the app directory.");
      $this->out("\n\tschema run create <schema> <table>\n\t\tDrop and create tables based on schema file\n\t\toptional <schema> arg for selecting schema name\n\t\toptional <table> arg for creating only one table\n\t\tpass the -s param with a number to use a snapshot\n\t\tTo see the changes, perform a dry run with the -dry param");
      $this->out("\n\tschema run update <schema> <table>\n\t\talter tables based on schema file\n\t\toptional <schema> arg for selecting schema name.\n\t\toptional <table> arg for altering only one table.\n\t\tTo use a snapshot, pass the -s param with the snapshot number\n\t\tTo see the changes, perform a dry run with the -dry param".
                 "\n\t\tTo perform synchronization under Your supervision use -with-check\n\t\t\tfor friendly (automagic) synchronisation use -add-only".
                 "\n\t\t\tUse -non-interactive with -auto-check to make it done without any assistance.\n\t\t\t Useful when building system (like Phing) need to run update.");
      $this->out("");
      $this->_stop();
   }
}

Share Button

Leave a Reply

Your email address will not be published. Required fields are marked *