NEW - bug 217191: Need way to synch changes with database
https://bugs.eclipse.org/bugs/show_bug.cgi?id=217191
diff --git a/classes/database_versioning/abstractschemachecker.class.php b/classes/database_versioning/abstractschemachecker.class.php
new file mode 100644
index 0000000..16f1811
--- /dev/null
+++ b/classes/database_versioning/abstractschemachecker.class.php
@@ -0,0 +1,367 @@
+<?php
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Eclipse Foundation
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Bjorn Freeman-Benson - initial API and implementation
+ * Ward Cunningham - initial API and implementation
+ * Karl Mathias - initial API and implementation
+ *******************************************************************************/
+
+
+function ourTrim(&$item1, $key){$item1 = trim($item1);}
+
+abstract class AbstractSchemaChecker {
+ abstract public function check_and_modify( $context );
+
+ public function check_and_modify_table( $databasename, $tablename, $schemas, $context ) {
+ $dbh = $context->database( $databasename );
+ $tablenamesuffix = $context->initmode;
+
+ echo "working on table '$tablename$tablenamesuffix' \n";
+
+ $createfunction = "create_$tablename";
+ if( $context->testmode && $context->initmode === false ) {
+ echo "..creating in memory <br>\n";
+ $sql = "DROP TABLE IF EXISTS $tablename ";
+ mysql_remember_query( $sql, $dbh );
+ mysql_error_check();
+ $sql = $this->$createfunction('');
+ $translations = array(
+ '/param(\d+) TEXT/' => 'param\1 varchar(1024)',
+ '/\s[Tt][Ee][Xx][Tt]/' => ' varchar(1024)',
+ '/blob/' => 'varbinary(8192)',
+ '/ENGINE=([A-Za-z]+)/' => '',
+ '/mediumtext/' => 'varchar(256)',
+ '/tinytext/' => 'varchar(256)',
+ '/FULLTEXT/' => '',
+ );
+ $a1 = array();
+ $a2 = array();
+ foreach( $translations as $k => $v ) {
+ $a1[] = $k;
+ $a2[] = $v;
+ }
+ $sql = preg_replace( $a1, $a2, $sql );
+ $sql = $sql . " ENGINE=MEMORY SELECT * FROM $tablename" . '_prototype';
+ $this->create_db( $sql, $dbh );
+ return true;
+ } else {
+ $table_name = $tablename.$tablenamesuffix;
+ $result = mysql_remember_query( "DESCRIBE $tablename$tablenamesuffix", $dbh );
+ if( strlen(mysql_error()) > 0 ) {
+ if( $context->devmode
+ || $context->testmode
+ || $databasename == 'myfoundation' ) {
+ echo "..does not exist, creating <br>\n";
+ $this->create_db( $this->$createfunction($tablenamesuffix), $dbh );
+ $result = mysql_remember_query( "DESCRIBE $tablename$tablenamesuffix", $dbh );
+ $str = $this->table_has_schema($result, $tablename . $tablenamesuffix, $schemas[count($schemas)], $dbh);
+ if( $str === false )
+ return true;
+ else {
+ echo $str;
+ echo "..schema does not match after creation of table $tablename$tablenamesuffix, error <br>\n";
+ return false;
+ }
+ } else {
+// print mysql_error();
+ echo "..does not exist, error <br>\n";
+ $this->createTableFromSchema($table_name,$schemas[count($schemas)],$dbh,$context);
+ $result = mysql_remember_query( "DESCRIBE $tablename$tablenamesuffix", $dbh );
+ $str = $this->table_has_schema($result, $tablename . $tablenamesuffix, $schemas[count($schemas)], $dbh);
+ if( $str === false )
+ return true;
+ else {
+ echo $str;
+ echo "..schema does not match after creation, error <br>\n";
+ return false;
+ }
+ }
+ }
+ $lastmatch = 0;
+ $laststr = '';
+ for( $i = 1; $i <= count($schemas); $i++ ) {
+ if( $i > 1 )
+ $result = mysql_remember_query( "DESCRIBE $tablename$tablenamesuffix", $dbh );
+ $str = $this->table_has_schema($result, $tablename . $tablenamesuffix, $schemas[$i], $dbh);
+ if( $str === false )
+ $lastmatch = $i;
+ else
+ $laststr = $str;
+ }
+ if( $lastmatch == 0 ) {
+ if( $context->devmode
+ || $context->testmode ) {
+ echo "..no matching schema, deleting and recreating <br>\n";
+ mysql_remember_query( "DROP TABLE $tablename$tablenamesuffix", $dbh);
+ mysql_error_check();
+ $this->create_db( $this->$createfunction($tablenamesuffix), $dbh );
+ $result = mysql_remember_query( "DESCRIBE $tablename$tablenamesuffix", $dbh );
+ $str = $this->table_has_schema($result, $tablename . $tablenamesuffix, $schemas[count($schemas)], $dbh);
+ if( $str === false )
+ return true;
+ else {
+ echo $str;
+ echo "..schema does not match after creation, error <br>\n";
+ return false;
+ }
+ } else {
+ echo $laststr;
+ echo "..no matching schema, error <br>\n";
+ return false;
+ }
+ }
+
+ if( $lastmatch == count($schemas) ) {
+ echo "..correct <br>\n";
+ return true;
+ }
+ if( $context->devmode
+ || $context->testmode
+ || $databasename == 'myfoundation' ) {
+ echo "..old schema, updating <br>\n";
+ for( $i = $lastmatch; $i < count($schemas); $i++ ) {
+ $modifyfunction = 'modify_' . $tablename . '_' . $i . '_' . ($i+1);
+ $this->$modifyfunction( $tablenamesuffix, $dbh );
+ }
+ $result = mysql_remember_query( "DESCRIBE $tablename$tablenamesuffix", $dbh );
+ $str = $this->table_has_schema($result, $tablename . $tablenamesuffix, $schemas[count($schemas)], $dbh);
+ if( $str === false )
+ return true;
+ else {
+ echo $str;
+ echo "..schema does not match after modification, error <br>\n";
+ return false;
+ }
+ } else {
+ $this->updateTableFromSchema($table_name,$schemas[count($schemas)],$dbh,$context);
+
+ echo "..old schema, error <br>\n";
+ return false;
+
+ }
+ }
+ }
+
+ private function createTableFromSchema($table_name,$schema,$dbh,$context){
+ $fields = $this->splitSchema($schema);
+ $primary_keys = array();
+ foreach($fields as $field){
+ $query_guts .= $this->fieldToQuery($field,$primary_keys).",";
+ }
+ $query = "create table $table_name ( ".trim($query_guts,",\n");
+ if(!empty($primary_keys['PRI'])){
+ $query .= ", primary key(".implode($primary_keys['PRI'],",").")";
+ }
+ if(!empty($primary_keys['MUL'])){
+ foreach($primary_keys['MUL'] as $mul){
+ $query .= ", index($mul)";
+ }
+ }
+ $query .= ")";
+
+ mysql_query($query,$dbh);
+ }
+
+
+ //NEED TO HANDLE EDGES CASES
+ // - WHEN ALL FIELDS ARE DROPPED FROM TABLE.. TREAT LIKE DROP TABLE CREATE TABLE WITH NEW SCHEMA.
+
+ private function updateTableFromSchema($table_name,$schema,$dbh,$context){
+ $old_fields = $this->getTableDescription($table_name,$dbh);
+ $new_fields = $this->splitSchema($schema);
+ //run prerefactor quries
+
+print "\n";//gogo
+
+ //find all old fields that have changed
+ //find all fields to drop
+ foreach($old_fields as $k => $v){
+ if($diff = array_diff($new_fields[$k], $v)){
+ print "MODIFY - $k\n";
+ if($diff['Default'] or $diff['Field']){
+ $modq = "alter table $table_name modify $k ".$new_fields[$k]['Type']." ".$new_fields[$k]['Default']. " ".$diff['Extra'];
+ mysql_remember_query($modq,$dbh);
+ }
+ }else{
+ //drop new field
+ print "DROP - $k\n";
+ $query = "alter table $table_name drop $k ";
+ if($v['Key'] == "PRI"){
+ $query .= " DROP PRIMARY KEY ";
+ }
+ if($v['Key'] == "MUL"){
+ $query .= " DROP INDEX ";
+ }
+ mysql_query($query);
+ }
+ }
+
+ //find all to add
+ $endqueries[] = array();
+ foreach($new_fields as $k => $v){
+ if(!isset($old_fields[$k])){
+ print "add!!!! $k\n";
+
+ $keys = array();
+ $query = "alter table $table_name add ";
+ $query.= $this->fieldToquery($v,&$keys);
+
+ if(is_array($keys['PRI']) and in_array($k,$keys['PRI'])){
+ $query .= " PRIMARY KEY ";
+ }
+ if(is_array($keys['MUL']) and in_array($k,$keys['MUL'])){
+// $query .= " INDEX ";
+ $endqueries[] = "alter table $table_name add index ($k) ";
+ }
+
+ print $query."\n";
+ mysql_query($query);
+ foreach($endqueries as $q){
+ mysql_query($q);
+ }
+
+ }
+ }
+ //check schema
+
+ }
+
+ private function fieldToquery($fields,&$keys){
+//gogo
+// print_r($fields);
+ list($type,$size) = explode("(" , trim($fields['Type'],") ") );
+
+ foreach($fields as $k => $v){
+ if($k == 'Null'){
+ if($v == 'NO'){
+ $v = "not null";
+ }else{
+ $v = "null";
+ }
+ }
+ if($k == 'Default'){
+ if($v != "")
+ $v = "DEFAULT $v";
+ else
+ $v = "";
+
+ }
+ if($k == 'Key'){
+ switch($v){
+ case 'PRI':
+ $keys['PRI'][] = $fields['Field'];
+ break;
+ case 'MUL':
+ $keys['MUL'][] = $fields['Field'];
+ break;
+ default:
+ break;
+ }
+ $v = ""; //clear the value they keys will be appened to end of query
+ }
+
+ $line .= " $v ";
+ }
+ return $line;
+ }
+
+ private function getTableDescription($table_name,$dbh){
+ $query = "DESCRIBE $table_name";
+ print $query;
+ $result = mysql_query($query,$dbh);
+ $ret = array();
+ while( $row = mysql_fetch_assoc($result) ) {
+ $ret[$row['Field']] = $row;
+ }
+ return $ret;
+ }
+
+ private function splitSchema($schema){ //todo need to break out the same functionality from table_has_schema
+ $lines = explode("\n",trim($schema));
+
+ $breaker = array_shift($lines);
+ $fieldKeys = explode("|",trim(array_shift($lines),"|"));
+ array_walk($fieldKeys, 'ourTrim');
+
+ array_shift($lines); //shift off the next filler line +----+---
+
+ while($line = array_shift($lines)){
+ if($line == $breaker){
+ break;
+ }
+ $fieldValues = explode("|",trim(trim($line),"|"));
+ $aray = array_combine($fieldKeys,$fieldValues);
+
+ $aray['seen'] = false;
+ array_walk($aray, 'ourTrim');
+ $ret[$aray['Field']] = $aray;
+ }
+
+// print_r($ret);
+ return $ret;
+ }
+
+ private function create_db( $sql, $dbh ) {
+ mysql_remember_query( $sql, $dbh );
+ mysql_error_check();
+ }
+
+ /* returns error string if error, null if schema matches */
+ private function table_has_schema( $result, $tablename, $schemadescription, $dbh ) {
+ $schema = $this->splitSchema($schemadescription);
+
+ while( $row = mysql_fetch_assoc($result) ) {
+ if( !array_key_exists($row['Field'], $schema) ) {
+ $rtrn .= "..column " . $row['Field'] . " not in schema <br>\n";
+ continue;
+ }
+ $schema[$row['Field']]['seen'] = true;
+ $rec = $schema[$row['Field']];
+ if( $rec['Type'] != $row['Type'] ) {
+ $rtrn .= "..column " . $row['Field'] . " with different type (" . $rec['Type'] . " vs " . $row['Type'] . ") <br>\n";
+ continue;
+ }
+ $a = $rec['Null'];
+ $b = $row['Null'];
+ if( $a == '' ) $a = 'NO';
+ if( $b == '' ) $b = 'NO';
+ /* some versions of MySQL seem to ignore NOT NULL when applied to timestamps */
+ if( $row['Type'] == 'timestamp' ) $b = 'NO';
+ /* */
+ if( $a != $b ) {
+ $rtrn .= "..column " . $row['Field']. " with different NULL (" . $rec['Null'] . " vs " . $row['Null'] . ") <br>\n";
+ continue;
+ }
+ if( $rec['Key'] != $row['Key']) {
+ if( $rec['Key']) {
+ $rtrn .= "..column " . $row['Field']. " with different keys (" . $rec['Key'] . ") vs (" . $row['Key']. ") <br>\n";
+ continue;
+ // note that we allow the table to have more keys than the schema, but not fewer
+ }
+ }
+ if( $rec['Extra'] != $row['Extra']) {
+ $rtrn .= "..column " . $row['Field']. " with different extra infomration (" . $rec['Extra'] . " vs " . $row['Extra'] . ") <br>\n";
+ continue;
+ }
+ // not able to check 'default' at this time
+ }
+
+ foreach( $schema as $rec ) {
+ if( !$rec['seen']) {
+ $rtrn .= "..column " . $rec['Field'] . " not in table <br>\n";
+ continue;
+ }
+ }
+ if( $rtrn == '' ) return false;
+ return $rtrn;
+ }
+}
+
+
+?>
\ No newline at end of file
diff --git a/classes/database_versioning/check-database-schema.class.php b/classes/database_versioning/check-database-schema.class.php
new file mode 100644
index 0000000..7928a81
--- /dev/null
+++ b/classes/database_versioning/check-database-schema.class.php
@@ -0,0 +1,44 @@
+<?php
+/*******************************************************************************
+ * Copyright (c) 2006-2007 Eclipse Foundation
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Bjorn Freeman-Benson - initial API and implementation
+ * Ward Cunningham - initial API and implementation
+ * Karl Mathias - initial API and implementation
+ *******************************************************************************/
+
+//require_once( $GLOBALS['CLASSES_DIRECTORY'] . "functions.php" );
+//require_once( $GLOBALS['CLASSES_DIRECTORY'] . "context.class.php" );
+
+
+
+class CheckAndModifyDatabaseSchema {
+ public function check_and_modify( $context ) {
+ $rtrn = true;
+ $dir = $context->get("components_directory");
+ foreach( dirs($dir) as $component ) {
+ print $dir . "$component/check-database-schema.php\n";
+
+ if( file_exists( $dir . "$component/check-database-schema.php" ) ) {
+ echo "Checking db schema for table '$component'\n";
+ mysql_select_db($component);
+ // the included code uses the context to check (and perhaps modify) the database(s) schemas
+ include_once( $dir . "$component/check-database-schema.php" );
+ $checkername = $component . "_SchemaChecker";
+ $obj = new $checkername ;
+ if( !$obj->check_and_modify( $context ) ) {
+ echo "***Trouble during checking database schema for table: '$component' ****\n";
+ $rtrn = false;
+ }
+ }
+ }
+ return $rtrn;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/classes/database_versioning/check_databases.php b/classes/database_versioning/check_databases.php
new file mode 100644
index 0000000..1c718a0
--- /dev/null
+++ b/classes/database_versioning/check_databases.php
@@ -0,0 +1,59 @@
+<?php
+/*******************************************************************************
+ * Copyright (c) 2007 Eclipse Foundation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Eclipse Foundation
+*******************************************************************************/
+
+include("check-database-schema.class.php");
+include("abstractschemachecker.class.php");
+
+
+global $spent_quries;
+$spent_quries = array();
+
+function mysql_remember_query($sql,$dbh){
+ global $spent_quries;
+ $spent_quries[] = $sql;
+ return mysql_query($sql,$dbh);
+}
+
+function dirs($path){
+ $dirs = array();
+ $list = scandir($path);
+ foreach($list as $dir){
+ if($dir == "." || $dir == "..")
+ continue;
+ if(is_dir($path.$dir)){
+ $dirs[] = $dir;
+ }
+ }
+
+ return $dirs;
+}
+
+class context{
+ function get($get){
+ return $this->$get;
+ }
+ function database($dbname){
+ return $this->dbhs[$dbname];
+ }
+}
+$context = new context();
+$context->components_directory = "tables/";
+$context->dbhs['refactor_test'] = mysql_connect("localhost","root","","refactor_test");
+
+$check = new CheckAndModifyDatabaseSchema();
+$worked = $check->check_and_modify( $context );
+
+print "\n";
+
+print_r($spent_quries);
+
+?>
\ No newline at end of file
diff --git a/classes/database_versioning/tables/refactor_test/check-database-schema.php b/classes/database_versioning/tables/refactor_test/check-database-schema.php
new file mode 100644
index 0000000..9957fd9
--- /dev/null
+++ b/classes/database_versioning/tables/refactor_test/check-database-schema.php
@@ -0,0 +1,38 @@
+<?php
+
+class refactor_test_SchemaChecker extends AbstractSchemaChecker {
+
+ public function check_and_modify( $context ) {
+ return $this->check_test_table($context);
+ }
+
+ public function check_test_table($context){
+ $schemas = array();
+ $schemas[1] = "
++--------+--------------+------+-----+---------+-------+
+| Field | Type | Null | Key | Default | Extra |
++--------+--------------+------+-----+---------+-------+
+| bug_id | mediumint(9) | NO | PRI | 0 | |
+| who | mediumint(9) | NO | | 0 | |
++--------+--------------+------+-----+---------+-------+
+";
+
+$schemas[2] = "
++------------------+--------------+------+-----+---------+----------------+
+| Field | Type | Null | Key | Default | Extra |
++------------------+--------------+------+-----+---------+----------------+
+| bug_id | smallint(6) | NO | PRI | NULL | auto_increment |
+| name | varchar(64) | NO | MUL | | |
+| product_id | smallint(6) | NO | MUL | 0 | |
+| initialowner | mediumint(9) | NO | | 0 | |
+| initialqacontact | mediumint(9) | YES | | NULL | |
+| description | mediumtext | NO | | | |
++------------------+--------------+------+-----+---------+----------------+
+";
+
+ return $this->check_and_modify_table("refactor_test","created_table",$schemas,$context);
+ }
+
+}
+
+?>
\ No newline at end of file