Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 39ac7dc

Browse files
committedJun 4, 2013
Initial commit for CakePHP SQLMigration plugin
0 parents  commit 39ac7dc

File tree

4 files changed

+323
-0
lines changed

4 files changed

+323
-0
lines changed
 

‎Config/Schema/schema.php

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
class SqlMigrationSchema extends CakeSchema {
4+
var $name = 'SqlMigration';
5+
6+
function before($event = array()) {
7+
return true;
8+
}
9+
10+
function after($event = array()) {
11+
}
12+
13+
var $schema_versions = array (
14+
'id' => array('type' => 'integer', 'null' => false, 'default' => NULL, 'key' => 'primary'),
15+
'version' => array('type' => 'integer', 'null' => false, 'default' => '-1'),
16+
'status' => array('type' => 'string', 'null' => true),
17+
'created' => array('type' => 'datetime', 'null' => true, 'default' => NULL),
18+
'modified' => array('type' => 'datetime', 'null' => true, 'default' => NULL),
19+
20+
'indexes' => array(
21+
'PRIMARY' => array('column' => 'id', 'unique' => 1),
22+
'schema_version_idx' => array('column' => 'version', 'unique' => 0),
23+
'schema_version_created_idx' => array('column' => 'created', 'unique' => 0)),
24+
25+
'tableParameters' => array('charset' => 'utf8', 'collate' => 'utf8_general_ci', 'engine' => 'MyISAM')
26+
);
27+
28+
29+
} // End SqlMigrationSchema
30+
31+
?>

‎Console/Command/SqlMigrationShell.php

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
<?php
2+
3+
/*
4+
* Based on:
5+
* http://bakery.cakephp.org/articles/erma/2010/01/05/cakephp-sql-shell-simple-and-powerful
6+
/
7+
*/
8+
9+
App::uses('Folder', 'Utility');
10+
11+
App::uses('ConnectionManager', 'Model');
12+
App::uses('SchemaVersion', 'SqlMigration.Model');
13+
14+
class SqlMigrationShell extends Shell {
15+
16+
/**
17+
* Connection used
18+
*
19+
* @var string
20+
*/
21+
private $connection = 'default';
22+
23+
/**
24+
* This plugin's name
25+
*
26+
* @var string
27+
*/
28+
private $myName = 'SqlMigration';
29+
30+
/**
31+
* Table holding schema version
32+
*
33+
* @var string
34+
*/
35+
private $tableName = 'schema_versions';
36+
37+
38+
/**
39+
* Sucess status
40+
*
41+
*/
42+
private $successStatus = 'STATUS';
43+
44+
/**
45+
* Skipped status
46+
*
47+
*/
48+
private $skippedStatus = 'SKIPPED';
49+
50+
/**
51+
* Data model
52+
*
53+
*/
54+
private $schemaVersionModel;
55+
56+
/*
57+
* Overridding this method will prevent welcome message
58+
*/
59+
public function _welcome() {
60+
$this->out('SQL Migration plugin');
61+
}
62+
63+
64+
/*
65+
* Function called if no other command is psecified
66+
*/
67+
public function main() {
68+
69+
$this->out('SqlMigration shell');
70+
71+
$this->schemaVersionModel = new SchemaVersion();
72+
73+
//$this->checkMigrationShema();
74+
$this->out('Executing migration');
75+
//$this->executeMigration();
76+
//$v = $this->getVersion();
77+
$this->update();
78+
79+
} // End function main()
80+
81+
/**
82+
* Get latest version
83+
* @return int Latest version number
84+
*/
85+
private function getVersion() {
86+
$latest = $this->schemaVersionModel->find('first', array(
87+
'order' => array('created DESC'),
88+
'limit' => 1
89+
));
90+
if ( $latest && is_numeric($latest['SchemaVersion']['version']) ) {
91+
return (int)$latest['SchemaVersion']['version'];
92+
}
93+
else {
94+
$this->out('No version found. Assuming 0.');
95+
return 0;
96+
}
97+
} // End function getVersion()
98+
99+
/**
100+
* Get all version history
101+
* @return int Latest version number
102+
*/
103+
private function getAllVersions() {
104+
$all = $this->schemaVersionModel->find('all', array(
105+
'order' => array('created ASC')
106+
));
107+
return $all;
108+
} // End function getAllVersions()
109+
110+
/**
111+
* Set version
112+
* Create new verision if doesn't exists, update if existing.
113+
* @param int $version Version
114+
* @param String $status Status of upgrade to version
115+
*/
116+
private function setVersion($version, $status) {
117+
$existingVersion = $this->schemaVersionModel->findByVersion($version);
118+
if ( $existingVersion ) {
119+
$this->schemaVersionModel->id = $existingVersion['SchemaVersion']['id'];
120+
$this->schemaVersionModel->saveField('status', $status);
121+
}
122+
else {
123+
$data = array('SchemaVersion' => array(
124+
'version' => $version,
125+
'status' => $status));
126+
$this->schemaVersionModel->create();
127+
$saved = $this->schemaVersionModel->save($data);
128+
if ( !$saved ) {
129+
$this->out('Unable to set version');
130+
$this->_stop();
131+
}
132+
}
133+
} // End function setVersion()
134+
135+
136+
/**
137+
* Get SQL to run (from file) for a given version
138+
* @param int $verion Version number
139+
* @return String SQL to run
140+
*/
141+
private function getSql($version) {
142+
if (($text = file_get_contents($filename = APP.'Config/Sql/upgrade-'.$version.'.sql')) !== false) {
143+
return $text;
144+
} else {
145+
$this->out("Couldn't load contents of file {$filename}, unable to uograde/downgrade");
146+
$this->_stop();
147+
}
148+
} // End function getSql()
149+
150+
151+
/**
152+
* Run the update.
153+
* This will try to run all upgrade SQL file in order of version.
154+
* It wil also try to run./re-run any version that might have been
155+
* skipped previously
156+
*/
157+
private function update() {
158+
$sqlFolder = new Folder(APP.'Config/Sql');
159+
list($dirs, $files) = $sqlFolder->read();
160+
$upgrades = array();
161+
foreach ($files as $i => $file) {
162+
if (preg_match( '/upgrade-(\d+)\.sql$/', $file, $matches)) {
163+
//unset($files[$i]);
164+
// $this->out($matches[1]);
165+
$upgrades[(int)$matches[1]] = $file;
166+
}
167+
}
168+
ksort($upgrades);
169+
$version = max(array_keys($upgrades));
170+
$this->out('Upgrading up to version : '.$version);
171+
172+
$allVersions = $this->getAllVersions();
173+
// $this->out(print_r($allVersions));
174+
175+
// Try to run missing/skipped versions
176+
$this->out('Looking for missing versions');
177+
foreach ($allVersions as $v ) {
178+
if ( $v['SchemaVersion']['status'] === $this->skippedStatus && isset($upgrades[$v['SchemaVersion']['version']]) ) {
179+
$this->out('Running skipped version: ' . $upgrades[$v['SchemaVersion']['version']]);
180+
if ( !$this->executeSql($v['SchemaVersion']['version']) ) {
181+
break;
182+
}
183+
}
184+
}
185+
// Run upgrades up to the highest/latest verion of the upgrade files found
186+
for ($currentVersion = $this->getVersion(); $currentVersion < $version; $currentVersion = $this->getVersion()) {
187+
$this->out('Currently at Version '.$currentVersion);
188+
$this->out('Updating to Version '.($currentVersion+1));
189+
if ( !isset($upgrades[$currentVersion+1]) ) {
190+
$this->out('No upgrade file for version '.($currentVersion+1).'. Skipping');
191+
$this->setVersion((int)($currentVersion+1), $this->skippedStatus);
192+
continue;
193+
}
194+
if ( !$this->executeSql($currentVersion+1) ) {
195+
break;
196+
}
197+
}
198+
$this->out('Now at version '.$this->getVersion());
199+
} // End function update
200+
201+
/**
202+
* Execute SQL file for a given version
203+
* @param int $version Version to execute
204+
* @return boolean False if user choose to not run the SQL. 'Skip' will return true
205+
*/
206+
private function executeSql($version) {
207+
$this->out('Executing sql:');
208+
$this->hr();
209+
$this->out($sql = $this->getSql($version));
210+
$this->hr();
211+
$a = $this->in('Execute SQL? [y/n/s]');
212+
if ( $a === 'n') {
213+
return false;
214+
}
215+
else if ( $a === 's') {
216+
return true;
217+
} else {
218+
$this->out('Launching MySQL to execute SQL');
219+
$database = ConnectionManager::getDataSource('default')->config;
220+
$sql_file = APP.'Config/Sql/upgrade-'.$version.'.sql';
221+
exec("mysql --host=${database['host']} --user=${database['login']} --password=${database['password']} --database=${database['database']} < ${sql_file}");
222+
$this->setVersion((int)($version), $this->successStatus);
223+
}
224+
return true;
225+
} // End function executeSql()
226+
227+
228+
/**
229+
* Check if the appropriate database exists for the plugin
230+
* @return [type] [description]
231+
*/
232+
public function setup() {
233+
234+
$ds = ConnectionManager::getDataSource($this->connection);
235+
$result = $ds->execute("SHOW TABLES LIKE '".$this->tableName."'")->fetch();
236+
if ( empty($result) ) {
237+
$this->out('Looks like this plugin was never used. Creating table needed');
238+
$this->createMigrationSchema();
239+
}
240+
else {
241+
$this->out('Updating database table needed by plugin');
242+
$this->updateMigrationSchema();
243+
}
244+
245+
} // End function checkMigrationSchema()
246+
247+
248+
/**
249+
* Update the database table for the plugin
250+
*/
251+
private function updateMigrationSchema() {
252+
253+
// Command to run
254+
$command = 'schema update --quiet --plugin '.$this->myName.' --connection ' . $this->connection;
255+
// Dispatch to shell
256+
$this->dispatchShell($command);
257+
$this->out('Updated');
258+
259+
} // End function updateMigrationchema()
260+
261+
262+
/**
263+
* Create database table needed by plugin
264+
*/
265+
private function createMigrationSchema() {
266+
267+
// Command to run
268+
$command = 'schema create --quiet --plugin '.$this->myName.' --connection ' . $this->connection;
269+
// Dispatch to shell
270+
$this->dispatchShell($command);
271+
$this->out('Created');
272+
273+
} // End function createMigrationchema()
274+
275+
}
276+
?>

‎Model/SchemaVersion.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
/**
4+
* Default model for schema version
5+
*
6+
*/
7+
App::uses('Model', 'Model');
8+
9+
class SchemaVersion extends Model {
10+
11+
12+
} // End class SchemaVersion
13+
14+
?>

‎README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
CakePHP SQL Migration Plugin
2+
============================

0 commit comments

Comments
 (0)