2 /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4 // +----------------------------------------------------------------------+
5 // | Akelos Framework - http://www.akelos.org |
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 2002-2006, Akelos Media, S.L. & Bermi Ferrer Martinez |
8 // | Released under the GNU Lesser General Public License, see LICENSE.txt|
9 // +----------------------------------------------------------------------+
12 * @package ActiveSupport
13 * @subpackage Installer
14 * @author Bermi Ferrer <bermi a.t akelos c.om>
15 * @copyright Copyright (c) 2002-2006, Akelos Media, S.L. http://www.akelos.org
16 * @license GNU Lesser General Public License <http://www.gnu.org/copyleft/lesser.html>
19 require_once(AK_LIB_DIR
.DS
.'Ak.php');
20 require_once(AK_LIB_DIR
.DS
.'AkActiveRecord.php');
21 file_exists(AK_APP_DIR
.DS
.'shared_model.php') ?
require_once(AK_APP_DIR
.DS
.'shared_model.php') : null;
22 defined('AK_APP_INSTALLERS_DIR') ?
null : define('AK_APP_INSTALLERS_DIR', AK_APP_DIR
.DS
.'installers');
24 // Install scripts might use more RAM than normal requests.
25 @ini_set
('memory_limit', -1);
31 * Akelos natively supports the following column data types.
33 * integer|int, float, decimal,
35 * datetime|timestamp, date,
39 * Caution: Because boolean is virtual tinyint on mysql, you can't use tinyint for other things!
42 * == Default settings for columns ==
44 * AkInstaller suggests some default values for the column-details.
48 * $this->createTable('Post','title,body,created_at,is_draft');
51 * will actually create something like this:
53 * title => string(255), body => text, created_at => datetime, is_draft => boolean not null default 0 index
56 * column_name | default setting
57 * -------------------------------+--------------------------------------------
58 * id | integer not null auto_increment primary_key
59 * *_id,*_by | integer index
60 * description,content,body | text
61 * position | integer index
62 * *_count | integer default 0
63 * lock_version | integer default 1
66 * is_*,has_*,do_*,does_*,are_* | boolean not null default 0 index
67 * *somename | multilingual column => en_somename, es_somename
75 var $available_tables = array();
78 var $warn_if_same_version = true;
80 function AkInstaller($db_connection = null)
82 if(empty($db_connection)){
83 $this->db
=& AkDbAdapter
::getInstance();
85 $this->db
=& $db_connection;
88 $this->data_dictionary
=& $this->db
->getDictionary();
89 $this->available_tables
= $this->getAvailableTables();
92 function install($version = null, $options = array())
94 $version = (is_null($version)) ?
max($this->getAvailableVersions()) : $version;
95 return $this->_upgradeOrDowngrade('up', $version , $options);
98 function up($version = null, $options = array())
100 return $this->_upgradeOrDowngrade('up', $version, $options);
103 function uninstall($version = null, $options = array())
105 $version = (is_null($version)) ?
0 : $version;
106 return $this->_upgradeOrDowngrade('down', $version, $options);
109 function down($version = null, $options = array())
111 return $this->_upgradeOrDowngrade('down', $version, $options);
114 function _upgradeOrDowngrade($action, $version = null, $options = array())
116 if(in_array('quiet',$options) && AK_ENVIRONMENT
== 'development'){
117 $this->vervose
= false;
118 }elseif(!empty($this->vervose
) && AK_ENVIRONMENT
== 'development'){
122 $current_version = $this->getInstalledVersion($options);
123 $available_versions = $this->getAvailableVersions();
125 $action = stristr($action,'down') ?
'down' : 'up';
128 $newest_version = max($available_versions);
129 $version = isset($version[0]) && is_numeric($version[0]) ?
$version[0] : $newest_version;
130 $versions = range($current_version+
1,$version);
132 if($current_version > $version){
133 echo Ak
::t("You can't upgrade to version %version, when you are currently on version %current_version", array('%version'=>$version,'%current_version'=>$current_version));
137 $version = !empty($version[0]) && is_numeric($version[0]) ?
$version[0] : 0;
138 $versions = range($current_version, empty($version) ?
1 : $version+
1);
140 if($current_version == 0){
142 }elseif($current_version < $version){
143 echo Ak
::t("You can't downgrade to version %version, when you just have installed version %current_version", array('%version'=>$version,'%current_version'=>$current_version));
148 if($this->warn_if_same_version
&& $current_version == $version){
149 echo Ak
::t("Can't go $action to version %version, you're already on version %version", array('%version'=>$version));
153 if(AK_CLI
&& !empty($this->vervose
) && AK_ENVIRONMENT
== 'development'){
154 echo Ak
::t(ucfirst($action).'grading');
157 if(!empty($versions) && is_array($versions)){
158 foreach ($versions as $version){
159 if(!$this->_runInstallerMethod($action, $version, $options)){
170 function installVersion($version, $options = array())
172 return $this->_runInstallerMethod('up', $version, $options);
175 function uninstallVersion($version, $options = array())
177 return $this->_runInstallerMethod('down', $version, $options);
181 * Runs a a dow_1, up_3 method and wraps it into a transaction.
183 function _runInstallerMethod($method_prefix, $version, $options = array(), $version_number = null)
185 $method_name = $method_prefix.'_'.$version;
186 if(!method_exists($this, $method_name)){
190 $version_number = empty($version_number) ?
($method_prefix=='down' ?
$version-1 : $version) : $version_number;
192 $this->transactionStart();
193 if($this->$method_name($options) === false){
194 $this->transactionFail();
196 $success = !$this->transactionHasFailed();
197 $this->transactionComplete();
199 $this->setInstalledVersion($version_number, $options);
204 function getInstallerName()
206 return str_replace('installer','',strtolower(get_class($this)));
209 function _versionPath($options = array())
211 $mode = empty($options['mode']) ? AK_ENVIRONMENT
: $options['mode'];
212 return AK_TMP_DIR
.DS
.'installer_versions'.DS
.(empty($this->module
)?
'':$this->module
.DS
).$mode.'_'.$this->getInstallerName().'_version.txt';
216 function _versionPath_Deprecated($options = array())
218 $mode = empty($options['mode']) ? AK_ENVIRONMENT
: $options['mode'];
219 return AK_APP_INSTALLERS_DIR
.DS
.(empty($this->module
)?
'':$this->module
.DS
).'versions'.DS
.$mode.'_'.$this->getInstallerName().'_version.txt';
223 function _moveOldVersionsFileToNewLocation($options)
225 $old_filename = $this->_versionPath_Deprecated($options);
226 if (is_file($old_filename)){
227 $this->setInstalledVersion(Ak
::file_get_contents($old_filename),$options);
228 Ak
::file_delete($old_filename);
229 Ak
::file_put_contents(AK_APP_INSTALLERS_DIR
.DS
.'versions'.DS
.'NOTE',"Version information is now stored in the temp folder. \n\rYou can savely move this files here over there to tmp/installer_versions/* or delete this directory if empty.");
233 function getInstalledVersion($options = array())
235 $version_file = $this->_versionPath($options);
237 $this->_moveOldVersionsFileToNewLocation($options);
239 if(!is_file($version_file)){
240 $this->setInstalledVersion(0, $options);
242 return Ak
::file_get_contents($this->_versionPath($options));
246 function setInstalledVersion($version, $options = array())
248 return Ak
::file_put_contents($this->_versionPath($options), $version);
252 function getAvailableVersions()
255 foreach(get_class_methods($this) as $method_name){
256 if(preg_match('/^up_([0-9]*)$/',$method_name, $match)){
257 $versions[] = $match[1];
265 function modifyTable($table_name, $column_options = null, $table_options = array())
267 return $this->_createOrModifyTable($table_name, $column_options, $table_options);
271 * Adds a new column to the table called $table_name
273 function addColumn($table_name, $column_details)
275 $column_details = $this->_getColumnsAsAdodbDataDictionaryString($column_details);
276 return $this->data_dictionary
->ExecuteSQLArray($this->data_dictionary
->AddColumnSQL($table_name, $column_details));
279 function changeColumn($table_name, $column_details)
281 $column_details = $this->_getColumnsAsAdodbDataDictionaryString($column_details);
282 return $this->data_dictionary
->ExecuteSQLArray($this->data_dictionary
->AlterColumnSQL($table_name, $column_details));
285 function removeColumn($table_name, $column_name)
287 return $this->data_dictionary
->ExecuteSQLArray($this->data_dictionary
->DropColumnSQL($table_name, $column_name));
290 function renameColumn($table_name, $old_column_name, $new_column_name)
292 return $this->db
->renameColumn($table_name,$old_column_name,$new_column_name);
296 function createTable($table_name, $column_options = null, $table_options = array())
298 if($this->tableExists($table_name)){
299 trigger_error(Ak
::t('Table %table_name already exists on the database', array('%table_name'=>$table_name)), E_USER_NOTICE
);
302 $this->timestamps
= (!isset($table_options['timestamp']) ||
(isset($table_options['timestamp']) && $table_options['timestamp'])) &&
303 (!strstr($column_options, 'created') && !strstr($column_options, 'updated'));
304 return $this->_createOrModifyTable($table_name, $column_options, $table_options);
307 function _createOrModifyTable($table_name, $column_options = null, $table_options = array())
309 if(empty($column_options) && $this->_loadDbDesignerDbSchema()){
310 $column_options = $this->db_designer_schema
[$table_name];
311 }elseif(empty($column_options)){
312 trigger_error(Ak
::t('You must supply details for the table you are creating.'), E_USER_ERROR
);
316 $column_options = is_string($column_options) ?
array('columns'=>$column_options) : $column_options;
318 $default_column_options = array(
319 'sequence_table' => false
321 $column_options = array_merge($default_column_options, $column_options);
323 $default_table_options = array(
324 'mysql' => 'TYPE=InnoDB',
327 $table_options = array_merge($default_table_options, $table_options);
329 $column_string = $this->_getColumnsAsAdodbDataDictionaryString($column_options['columns']);
331 $create_or_alter_table_sql = $this->data_dictionary
->ChangeTableSQL($table_name, str_replace(array(' UNIQUE', ' INDEX', ' FULLTEXT', ' HASH'), '', $column_string), $table_options);
332 $result = $this->data_dictionary
->ExecuteSQLArray($create_or_alter_table_sql, false);
335 $this->available_tables
[] = $table_name;
337 trigger_error(Ak
::t("Could not create or alter table %name using the SQL \n--------\n%sql\n--------\n", array('%name'=>$table_name, '%sql'=>$create_or_alter_table_sql[0])), E_USER_ERROR
);
340 $columns_to_index = $this->_getColumnsToIndex($column_string);
342 foreach ($columns_to_index as $column_to_index => $index_type){
343 $this->addIndex($table_name, $column_to_index.($index_type != 'INDEX' ?
' '.$index_type : ''));
346 if(isset($column_options['index_columns'])){
347 $this->addIndex($table_name, $column_options['index_columns']);
350 if($column_options['sequence_table'] ||
$this->_requiresSequenceTable($column_string)){
351 $this->createSequence($table_name);
357 function dropTable($table_name, $options = array())
359 $result = $this->tableExists($table_name) ?
$this->db
->execute('DROP TABLE '.$table_name) : true;
361 unset($this->available_tables
[array_search($table_name, $this->available_tables
)]);
362 if(!empty($options['sequence'])){
363 $this->dropSequence($table_name);
368 function dropTables()
370 $args = func_get_args();
372 $num_args = count($args);
373 $options = $num_args > 1 && is_array($args[$num_args-1]) ?
array_shift($args) : array();
374 $tables = count($args) > 1 ?
$args : (is_array($args[0]) ?
$args[0] : Ak
::toArray($args[0]));
375 foreach ($tables as $table){
376 $this->dropTable($table, $options);
381 function addIndex($table_name, $columns, $index_name = '')
383 $index_name = ($index_name == '') ?
'idx_'.$table_name.'_'.$columns : $index_name;
384 $index_options = array();
385 if(preg_match('/(UNIQUE|FULLTEXT|HASH)/',$columns,$match)){
386 $columns = trim(str_replace($match[1],'',$columns),' ');
387 $index_options[] = $match[1];
389 return $this->tableExists($table_name) ?
$this->data_dictionary
->ExecuteSQLArray($this->data_dictionary
->CreateIndexSQL($index_name, $table_name, $columns, $index_options)) : false;
392 function removeIndex($table_name, $columns_or_index_name)
394 if(!$this->tableExists($table_name)){
397 $available_indexes = $this->db
->getIndexes($table_name);
398 $index_name = isset($available_indexes[$columns_or_index_name]) ?
$columns_or_index_name : 'idx_'.$table_name.'_'.$columns_or_index_name;
399 if(!isset($available_indexes[$index_name])){
400 trigger_error(Ak
::t('Index %index_name does not exist.', array('%index_name'=>$index_name)), E_USER_NOTICE
);
403 return $this->data_dictionary
->ExecuteSQLArray($this->data_dictionary
->DropIndexSQL($index_name, $table_name));
406 function dropIndex($table_name, $columns_or_index_name)
408 return $this->removeIndex($table_name,$columns_or_index_name);
411 function createSequence($table_name)
413 $result = $this->tableExists('seq_'.$table_name) ?
false : $this->db
->connection
->CreateSequence('seq_'.$table_name);
414 $this->available_tables
[] = 'seq_'.$table_name;
418 function dropSequence($table_name)
420 $result = $this->tableExists('seq_'.$table_name) ?
$this->db
->connection
->DropSequence('seq_'.$table_name) : true;
422 unset($this->available_tables
[array_search('seq_'.$table_name, $this->available_tables
)]);
428 function getAvailableTables()
430 if(empty($this->available_tables
)){
431 $this->available_tables
= $this->db
->availableTables();
433 return $this->available_tables
;
436 function tableExists($table_name)
438 return in_array($table_name,$this->getAvailableTables());
441 function _getColumnsAsAdodbDataDictionaryString($columns)
443 $columns = $this->_setColumnDefaults($columns);
444 $this->_ensureColumnNameCompatibility($columns);
446 $equivalences = array(
447 '/ ((limit|max|length) ?= ?)([0-9]+)([ \n\r,]+)/'=> ' (\3) ',
448 '/([ \n\r,]+)default([ =]+)([^\'^,^\n]+)/i'=> ' DEFAULT \'\3\'',
449 '/([ \n\r,]+)(integer|int)([( \n\r,]+)/'=> '\1 I \3',
450 '/([ \n\r,]+)float([( \n\r,]+)/'=> '\1 F \2',
451 '/([ \n\r,]+)decimal([( \n\r,]+)/'=> '\1 N \2',
452 '/([ \n\r,]+)datetime([( \n\r,]+)/'=> '\1 T \2',
453 '/([ \n\r,]+)date([( \n\r,]+)/'=> '\1 D \2',
454 '/([ \n\r,]+)timestamp([( \n\r,]+)/'=> '\1 T \2',
455 '/([ \n\r,]+)time([( \n\r,]+)/'=> '\1 T \2',
456 '/([ \n\r,]+)text([( \n\r,]+)/'=> '\1 XL \2',
457 '/([ \n\r,]+)string([( \n\r,]+)/'=> '\1 C \2',
458 '/([ \n\r,]+)binary([( \n\r,]+)/'=> '\1 B \2',
459 '/([ \n\r,]+)boolean([( \n\r,]+)/'=> '\1 L'.($this->db
->type()=='mysql'?
'(1)':'').' \2',
460 '/ NOT( |_)?NULL/i'=> ' NOTNULL',
461 '/ AUTO( |_)?INCREMENT/i'=> ' AUTO ',
463 '/ ([\(,]+)/'=> '\1',
464 '/ INDEX| IDX/i'=> ' INDEX ',
465 '/ UNIQUE/i'=> ' UNIQUE ',
466 '/ HASH/i'=> ' HASH ',
467 '/ FULL_?TEXT/i'=> ' FULLTEXT ',
468 '/ ((PRIMARY( |_)?)?KEY|pk)/i'=> ' KEY',
471 return trim(preg_replace(array_keys($equivalences),array_values($equivalences), ' '.$columns.' '), ' ');
474 function _setColumnDefaults($columns)
476 $columns = Ak
::toArray($columns);
477 foreach ((array)$columns as $column){
478 $column = trim($column, "\n\t\r, ");
480 $single_columns[$column] = $this->_setColumnDefault($column);
483 if(!empty($this->timestamps
) && !isset($single_columns['created_at']) && !isset($single_columns['updated_at'])){
484 $single_columns['updated_at'] = $this->_setColumnDefault('updated_at');
485 $single_columns['created_at'] = $this->_setColumnDefault('created_at');
487 return join(",\n", $single_columns);
490 function _setColumnDefault($column)
492 return $this->_needsDefaultAttributes($column) ?
$this->_setDefaultAttributes($column) : $column;
495 function _needsDefaultAttributes($column)
497 return preg_match('/^(([A-Z0-9_\(\)]+)|(.+ string[^\(.]*)|(\*.*))$/i',$column);
500 function _setDefaultAttributes($column)
502 $rules = $this->getDefaultColumnAttributesRules();
503 foreach ($rules as $regex=>$replacement){
504 if(is_string($replacement)){
505 $column = preg_replace($regex,$replacement,$column);
506 }elseif(preg_match($regex,$column,$match)){
507 $column = call_user_func_array($replacement,$match);
514 * Returns a key => value pair of regular expressions that will trigger methods
515 * to cast database columns to their respective default values or a replacement expression.
517 function getDefaultColumnAttributesRules()
520 '/^\*(.*)$/i' => array(&$this,'_castToMultilingualColumn'),
521 '/^(description|content|body)$/i' => '\1 text',
522 '/^(lock_version)$/i' => '\1 integer default \'1\'',
523 '/^(.+_count)$/i' => '\1 integer default \'0\'',
524 '/^(id)$/i' => 'id integer not null auto_increment primary_key',
525 '/^(.+)_(id|by)$/i' => '\1_\2 integer index',
526 '/^(position)$/i' => '\1 integer index',
527 '/^(.+_at)$/i' => '\1 datetime',
528 '/^(.+_on)$/i' => '\1 date',
529 '/^(is_|has_|do_|does_|are_)([A-Z0-9_]+)$/i' => '\1\2 boolean not null default \'0\' index', //
530 '/^([A-Z0-9_]+) *(\([0-9]+\))?$/i' => '\1 string\2', // Everything else will default to string
531 '/^((.+ )string([^\(.]*))$/i' => '\2string(255)\3', // If we don't set the string lenght it will fail, so if not present will set it to 255
535 function _castToMultilingualColumn($found, $column)
538 foreach (Ak
::langs() as $lang){
539 $columns[] = $lang.'_'.ltrim($column);
541 return $this->_setColumnDefaults($columns);
544 function _getColumnsToIndex($column_string)
546 $columns_to_index = array();
547 foreach (explode(',',$column_string.',') as $column){
548 if(preg_match('/([A-Za-z0-9_]+) (.*) (INDEX|UNIQUE|FULLTEXT|HASH) ?(.*)$/i',$column,$match)){
549 $columns_to_index[$match[1]] = $match[3];
552 return $columns_to_index;
555 function _getUniqueValueColumns($column_string)
557 $unique_columns = array();
558 foreach (explode(',',$column_string.',') as $column){
559 if(preg_match('/([A-Za-z0-9_]+) (.*) UNIQUE ?(.*)$/',$column,$match)){
560 $unique_columns[] = $match[1];
563 return $unique_columns;
566 function _requiresSequenceTable($column_string)
568 if(in_array($this->db
->type(),array('mysql','postgre'))){
571 foreach (explode(',',$column_string.',') as $column){
572 if(preg_match('/([A-Za-z0-9_]+) (.*) AUTO (.*)$/',$column)){
575 if(preg_match('/^id /',$column)){
584 * Transaction support for database operations
586 * Transactions are enabled automatically for Intaller objects, But you can nest transactions within models.
587 * This transactions are nested, and only the othermost will be executed
589 * $UserInstalller->transactionStart();
590 * $UserInstalller->addTable('id, name');
592 * if(!isCompatible()){
593 * $User->transactionFail();
596 * $User->transactionComplete();
598 function transactionStart()
600 return $this->db
->startTransaction();
603 function transactionComplete()
605 return $this->db
->stopTransaction();
608 function transactionFail()
610 return $this->db
->failTransaction();
613 function transactionHasFailed()
615 return $this->db
->hasTransactionFailed();
619 * Promts for a variable on console scripts
621 function promtUserVar($message, $options = array())
623 $f = fopen("php://stdin","r");
624 $default_options = array(
629 $options = array_merge($default_options, $options);
631 echo "\n".$message.(empty($options['default'])?
'': ' ['.$options['default'].']').': ';
632 $user_input = fgets($f, 25600);
633 $value = trim($user_input,"\n\r\t ");
634 $value = empty($value) ?
$options['default'] : $value;
635 if(empty($value) && empty($options['optional'])){
636 echo "\n\nThis setting is not optional.";
638 return $this->promtUserVar($message, $options);
641 return empty($value) ?
$options['default'] : $value;
644 function _loadDbDesignerDbSchema()
646 if($path = $this->_getDbDesignerFilePath()){
647 $this->db_designer_schema
= Ak
::convert('DBDesigner','AkelosDatabaseDesign', Ak
::file_get_contents($path));
648 return !empty($this->db_designer_schema
);
653 function _getDbDesignerFilePath()
655 $path = AK_APP_INSTALLERS_DIR
.DS
.$this->getInstallerName().'.xml';
656 return file_exists($path) ?
$path : false;
659 function _ensureColumnNameCompatibility($columns)
661 $columns = explode(',',$columns.',');
662 foreach ($columns as $column){
663 $column = trim($column);
664 $column = substr($column, 0, strpos($column.' ',' '));
665 $this->_canUseColumn($column);
669 function _canUseColumn($column_name)
671 $invalid_columns = $this->_getInvalidColumnNames();
672 if(in_array($column_name, $invalid_columns)){
674 $method_name_part = AkInflector
::camelize($column_name);
675 require_once(AK_LIB_DIR
.DS
.'AkActiveRecord.php');
676 $method_name = (method_exists(new AkActiveRecord(), 'set'.$method_name_part)?
'set':'get').$method_name_part;
678 trigger_error(Ak
::t('A method named %method_name exists in the AkActiveRecord class'.
679 ' which will cause a recusion problem if you use the column %column_name in your database. '.
680 'You can disable automatic %type by setting the constant %constant to false '.
681 'in your configuration file.', array(
682 '%method_name'=> $method_name,
683 '%column_name' => $column_name,
684 '%type' => Ak
::t($method_name[0] == 's' ?
'setters' : 'getters'),
685 '%constant' => Ak
::t($method_name[0] == 's' ?
'AK_ACTIVE_RECORD_ENABLE_CALLBACK_SETTERS' : 'AK_ACTIVE_RECORD_ENABLE_CALLBACK_GETTERS'),
691 function _getInvalidColumnNames()
693 return defined('AK_INVALID_ACTIVE_RECORD_COLUMNS') ?
explode(',',AK_INVALID_ACTIVE_RECORD_COLUMNS
) : array('sanitized_conditions_array','conditions','inheritance_column','inheritance_column',
694 'subclasses','attribute','attributes','attribute','attributes','accessible_attributes','protected_attributes',
695 'serialized_attributes','available_attributes','attribute_caption','primary_key','column_names','content_columns',
696 'attribute_names','combined_subattributes','available_combined_attributes','connection','connection','primary_key',
697 'table_name','table_name','only_available_atrributes','columns_for_atrributes','columns_with_regex_boundaries','columns',
698 'column_settings','column_settings','akelos_data_type','class_for_database_table_mapping','display_field','display_field',
699 'internationalized_columns','available_locales','current_locale','attribute_by_locale','attribute_locales',
700 'attribute_by_locale','attribute_locales','attributes_before_type_cast','attribute_before_type_cast','serialize_attribute',
701 'available_attributes_quoted','attributes_quoted','column_type','value_for_date_column','observable_state',
702 'observable_state','observers','errors','base_errors','errors_on','full_error_messages','array_from_ak_string',
703 'attribute_condition','association_handler','associated','associated_finder_sql_options','association_option',
704 'association_option','association_id','associated_ids','associated_handler_name','associated_type','association_type',
705 'collection_handler_name','model_name','model_name','parent_model_name','parent_model_name');
708 function execute($sql)
710 return $this->db
->execute($sql);
713 function debug($toggle = null)
715 $this->db
->connection
->debug
= $toggle === null ?
!$this->db
->connection
->debug
: $toggle;
720 echo Ak
::t("Description:
721 Database migrations is a sort of SCM like subversion, but for database settings.
723 The migration command takes the name of an installer located on your
724 /app/installers folder and runs one of the following commands:
726 - \"install\" + (options version number): Will update to the provided version
727 number or to the latest one in no version is given.
729 - \"uninstall\" + (options version number): Will downgrade to the provided
730 version number or to the lowest one in no version is given.
732 Current version number will be sorted at app/installers/installer_name_version.txt.
735 >> migrate framework install
737 Will run the default database schema for the framework.
738 This generates the tables for handling database driven sessions and cache.