";
$sql->db_Select_gen('SET SQL_QUOTE_SHOW_CREATE = 1');
$qry = 'SHOW CREATE TABLE `'.$prefix.$table_name."`";
if (!($z = $sql->db_Select_gen($qry))) { return FALSE; }
$row = $sql->db_Fetch(MYSQL_NUM);
$tmp = str_replace("`", "", stripslashes($row[1])).';'; // Add semicolon to work with our parser
$count = preg_match_all("#CREATE\s+?TABLE\s+?`{0,1}({$prefix}{$table_name})`{0,1}\s+?\((.*?)\)\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is",$tmp,$matches,PREG_SET_ORDER);
if ($count === FALSE) { return "Error occurred";}
if (!$count) { return "No matches"; }
return $matches;
}
// Routine to do first-level parse of table structure
//---------------------------------------------------
// Given the name of a file, returns an array, with each element being a table creation definition.
// Tracks the last file read - only reads it once
// If the file name is an empty string, uses a previously read/set buffer
// If a table name is given, returns only that table's info; otherwise returns a list of all tables
// The table name must include a prefix where appropriate (although not required with standard E107 table definition files)
// Each element is itself an array:
// [0] - The complete string which creates a table (unless a prefix needs adding to the table name), including terminating ';'
// [1] - The table name. Any backticks are stripped
// [2] - Field definitions, with the surrounding (...) stripped
// [3] - The 'TYPE' field ('TYPE=' is stripped) and any AUTO-INCREMENT definition or other text.
// function get_table_def($table_name='',$file_name = e_ADMIN."sql/core_sql.php")
function get_table_def($table_name='',$file_name ="")
{
if ($file_name != '')
{ // Read in and buffer a new file (if we've not already got one)
if ($this->last_file != $file_name)
{
if (!is_readable($file_name)) { return "No file"; }
$temp = file_get_contents($file_name);
// Strip any php header
$this->file_buffer = preg_replace("#\<\?php.*?\?\>#mis",'',$temp);
$this->last_file = $file_name;
}
}
if (!$table_name) { $table_name = '\w+?'; }
// Regex should be identical to that in get_current_table (apart from the source text variable name)
$count = preg_match_all("#CREATE\s+?TABLE\s+?`{0,1}({$table_name})`{0,1}\s+?\((.*?)\)\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is",$this->file_buffer,$matches,PREG_SET_ORDER);
if ($count === false) { return "Error occurred"; }
if (!$count) { return "No matches"; }
return $matches;
}
// Parses the block of lines which make up the field and index definitions
// Returns an array where each entry is the definitions of a field or index
function parse_field_defs($text)
{
$ans = array();
$text = str_replace("\r","\n",$text);
$field_lines = explode("\n",$text);
foreach ($field_lines as $fv)
{
unset($defs);
$fv = trim(str_replace(' ',' ',$fv));
if (substr($fv,-1) == ',') { $fv = trim(substr($fv,0,-1)); }
// echo "Line: ".$fv."
";
if ($fv)
{
$fd = explode(' ',$fv);
switch (strtoupper($fd[0]))
{
case 'PRIMARY' :
if (strtoupper($fd[1]) == 'KEY')
$defs['type'] = 'pkey';
$defs['name'] = $fd[2];
break;
case 'UNIQUE' :
if (count($fd) < 3)
{
echo "Truncated definition after UNIQUE {$i}: ".$fd[1]."
";
}
elseif (strtoupper($fd[1]) == 'KEY')
{
$defs['type'] = 'ukey';
$defs['name'] = $fd[2];
if (isset($fd[3])) $defs['keyfield'] = $fd[3]; else $defs['keyfield'] = '['.$fd[2].']';
}
else
{
echo "Unrecognised word after UNIQUE in definition {$i}: ".$fd[1]."
";
}
break;
case 'KEY' :
$defs['type'] = 'key';
$defs['name'] = $fd[1];
if (isset($fd[2])) {
$defs['keyfield'] = $fd[2];
}
else
{
$defs['keyfield'] = '['.$fd[1].']';
}
break;
default : // Must be a DB field name
$defs['type'] = 'field';
$defs['name'] = $fd[0];
$defs['fieldtype'] = $fd[1];
$i = 2; // First unused field
if ((strpos($fd[1],'int') === 0) || (strpos($fd[1],'tinyint') === 0) || (strpos($fd[1],'smallint') === 0) || (strpos($fd[1],'bigint') === 0))
{
if (isset($fd[2]) && (strtoupper($fd[2]) == 'UNSIGNED'))
{
$defs['vartype'] = $fd[2];
$i++;
}
}
while ($i < count($fd))
{
switch(strtoupper($fd[$i]))
{
case 'NOT' :
if (isset($fd[$i+1]) && strtoupper($fd[$i+1]) == 'NULL')
{
$i++;
$defs['nulltype'] = 'NOT NULL';
}
else
{ // Syntax error
echo "Unrecognised word in definition {$i} after 'NOT': ".$fd[$i+1]."
";
}
break;
case 'DEFAULT' :
if (isset($fd[$i+1]))
{
$i++;
$defs['default'] = $fd[$i];
}
break;
case 'COLLATE' :
$i++; // Just skip over - we ignore collation
break;
case 'AUTO_INCREMENT' :
$defs['autoinc'] = TRUE;
break;
default :
echo "Unknown definition {$i}: ".$fd[$i]."
";
}
$i++;
}
}
if (count($defs) > 1)
{
$ans[] = $defs;
}
else
{
echo "Partial definition
";
}
}
}
if (!count($ans)) { return FALSE; }
return $ans;
}
// Utility routine - given our array-based definition, create a string MySQL field definition
function make_def($list)
{
switch ($list['type'])
{
case 'key' :
return 'KEY '.$list['name'].' ('.str_replace(array('(',')'),'',$list['keyfield']).')';
case 'ukey' :
return 'UNIQUE KEY '.$list['name'].' ('.$list['name'].')';
case 'pkey' :
return 'PRIMARY KEY ('.$list['name'].')';
case 'field' : // Require a field - got a key. so add a field at the end
$def = $list['name'];
if (isset($list['fieldtype'])) { $def .= ' '.$list['fieldtype']; }
if (isset($list['vartype'])) { $def .= ' '.$list['vartype']; }
if (isset($list['nulltype'])) { $def .= ' '.$list['nulltype']; }
if (isset($list['default'])) { $def .= ' default '.$list['default']; }
if (varsettrue($list['autoinc'])) { $def .= ' auto_increment'; }
return $def;
}
return "Cannot generate definition for: ".$list['type'].' '.$list['name'];
}
// Compare two field/index lists as generated by parse_field_defs
// If $stop_on_error is TRUE, returns TRUE if the same, false if different
// Return a text list of differences, plus an array of MySQL queries to fix
// List1 is the reference, List 2 is the actual
// This version looks ahead on a failed match, and moves a field up in the table if already defined - should retain as much as possible
function compare_field_lists($list1, $list2, $stop_on_error=FALSE)
{
$i = 0; // Counts records in list1 (required format)
$j = 0; // Counts records in $created_list (our 'table so far' list)
$error_list = array(); // Free text list of differences
$change_list = array(); // MySQL statements to implement changes
$created_list = array(); // List of field defs that we build up (just names)
while ($i < count($list1))
{
if (count($list2) == 0)
{ // Missing field at end
if ($stop_on_error) { return FALSE; }
$error_list[] = 'Missing field at end: '.$list1[$i]['name'];
$change_list[] = 'ADD '.$this->make_def($list1[$i]);
$created_list[$j] = $list1[$i]['name'];
$j++;
}
elseif ($list1[$i]['type'] == $list2[0]['type'])
{ // Worth doing a compare - fields are same type
// echo $i.': compare - '.$list1[$i]['name'].', '.$list2[0]['name'].'
';
if (strcasecmp($list1[$i]['name'],$list2[0]['name']) != 0)
{ // Names differ, so need to add or subtract a field.
// echo $i.': names differ - '.$list1[$i]['name'].', '.$list2[0]['name'].'
';
if ($stop_on_error) { return FALSE; }
$found = FALSE;
for ($k = $i+1; $k < count($list1); $k++)
{
// echo "Compare ".$list1[$k]['name'].' with '.$list2[0]['name'];
if (strcasecmp($list1[$k]['name'],$list2[0]['name']) == 0)
{ // Field in list2 found later in list1; do nothing
// echo " - match
";
$found = TRUE;
break;
}
// echo " - no match
";
}
if (!$found)
{ // Field in existing DB no longer required
$error_list[] = 'Obsolete field: '.$list2[0]['name'];
$change_list[] = 'DROP '.($list2[0]['type'] == 'field' ? '' : 'INDEX ').$list2[0]['name'];
array_shift($list2);
continue;
}
$found = FALSE;
for ($k = 0; $k < count($list2); $k++)
{
// echo "Compare ".$list1[$i]['name'].' with '.$list2[$k]['name'];
if (strcasecmp($list1[$i]['name'],$list2[$k]['name']) == 0)
{ // Field found; we need to move it up
// echo " - match
";
$found = TRUE;
break;
}
// echo " - no match
";
}
if ($found)
{
$error_list[] = 'Field out of position: '.$list2[$k]['name'];
$change_list[] = 'MODIFY '.$this->make_def($list1[$i]).(count($created_list) ? ' AFTER '.$created_list[count($created_list)-1] : ' FIRST');
array_splice($list2,$k,1); // Finished with this element - delete it, and renumber the keys
$created_list[$j] = $list1[$i]['name'];
$j++;
// The above also amends any parameters as necessary
}
else
{ // Need to insert a field
$error_list[] = 'Missing field: '.$list1[$i]['name'].' (found: '.$list2[0]['type'].' '.$list2[0]['name'].')';
switch ($list1[$i]['type'])
{
case 'key' :
case 'ukey' :
case 'pkey' : // Require a key
$change_list[] = 'ADD '.$this->make_def($list1[$i]);
$error_list[] = 'Missing index: '.$list1[$i]['name'];
$created_list[$j] = $list1[$i]['name'];
$j++;
break;
case 'field' :
$change_list[] = 'ADD '.$this->make_def($list1[$i]).(count($created_list) ? ' AFTER '.$created_list[count($created_list)-1] : ' FIRST');
$error_list[] = 'Missing field: '.$list1[$i]['name'].' (found: '.$list2[0]['type'].' '.$list2[0]['name'].')';
$created_list[$j] = $list1[$i]['name'];
$j++;
break;
}
}
}
else
{ // Field/index is present as required; may be changes though
// Any difference and we need to update the table
// echo $i.': name match - '.$list1[$i]['name'].'
';
foreach ($list1[$i] as $fi => $v)
{
$t = $list2[0][$fi];
if (stripos($v,'varchar') !== FALSE) { $v = substr($v,3); } // Treat char, varchar the same
if (stripos($t,'varchar') !== FALSE) { $t = substr($t,3); } // Treat char, varchar the same
if (strcasecmp($t , $v) !== 0)
{
if ($stop_on_error) { return FALSE; }
$error_list[] = 'Incorrect definition: '.$fi.' = '.$v;
$change_list[] = 'MODIFY '.$this->make_def($list1[$i]);
break;
}
}
array_shift($list2);
$created_list[$j] = $list1[$i]['name'];
$j++;
}
}
else
{ // Field type has changed. We know fields come before indexes. So something's missing
// echo $i.': types differ - '.$list1[$i]['type'].' '.$list1[$i]['name'].', '.$list2[$k]['type'].' '.$list2[$k]['name'].'
';
if ($stop_on_error) { return FALSE; }
switch ($list1[$i]['type'])
{
case 'key' :
case 'ukey' :
case 'pkey' : // Require a key - got a field, or a key of a different type
while ((count($list2)>0) && ($list2[0]['type'] == 'field'))
{
$error_list[] = 'Extra field: '.$list2[0]['name'];
$change_list[] = 'DROP '.$list2[0]['name'];
array_shift($list2);
}
if ((count($list2) == 0) || ($list1[$i]['type'] != $list2[0]['type']))
{ // need to add a key
$change_list[] = 'ADD '.$this->make_def($list1[$i]);
$error_list[] = 'Missing index: '.$list1[$i]['name'];
$created_list[$j] = $list1[$i]['name'];
$j++;
}
break;
case 'field' : // Require a field - got a key. so add a field at the end
$error_list[] = 'Missing field: '.$list1[$i]['name'].' (found: '.$list2[0]['type'].' '.$list2[0]['name'].')';
$change_list[] = 'ADD '.$this->make_def($list1[$i]);
break;
default :
$error_list[] = 'Unknown field type: '.$list1[$i]['type'];
$change_list[] = ''; // Null entry to keep them in step
}
} // End - missing or extra field
$i++; // On to next field
}
if (count($list2))
{ // Surplus fields in actual table
// Echo count($list2)." fields at end to delete
";
foreach ($list2 as $f)
{
switch ($f['type'])
{
case 'key' :
case 'ukey' :
case 'pkey' : // Require a key - got a field
$error_list[] = 'Extra index: '.$list2[0]['name'];
$change_list[] = 'DROP INDEX '.$list2[0]['name'];
break;
case 'field' :
$error_list[] = 'Extra field: '.$list2[0]['name'];
$change_list[] = 'DROP '.$list2[0]['name'];
break;
}
}
}
if ($stop_on_error) return TRUE; // If doing a simple comparison and we get to here, all matches
return array($error_list, $change_list);
}
function make_changes_list($result)
{
if (!is_array($result)) { return "Not an array
"; }
$text = "
";
for ($i = 0; $i < count($result[0]); $i++)
{
$text .= "{$result[0][$i]} | ";
$text .= "{$result[1][$i]} | ";
$text .= "
\n";
}
$text .= "
";
return $text;
}
// Return a table of info from the output of get_table_def
function make_table_list($result)
{
if (!is_array($result)) { return "Not an array
"; }
$text = "";
for ($i = 0; $i < count($result); $i++)
{
$text .= "{$result[$i][0]} | ";
$text .= "{$result[$i][1]} | ";
$text .= "{$result[$i][2]} | ";
$text .= "{$result[$i][3]} |
\n";
}
$text .= "
";
return $text;
}
// Return a table of info from the output of parse_field_defs()
function make_field_list($fields)
{
$text = "";
foreach ($fields as $f)
{
switch($f['type'])
{
case 'pkey' :
$text .= "PRIMARY KEY | {$f['name']} | |
";
break;
case 'ukey' :
$text .= "UNIQUE KEY | {$f['name']} | {$f['keyfield']} |
";
break;
case 'key' :
$text .= "KEY | {$f['name']} | {$f['keyfield']} |
";
break;
case 'field' :
$text .= "FIELD | {$f['name']} | {$f['fieldtype']}";
if (isset($f['vartype'])) { $text .= " ".$f['vartype']; }
$text .= " | ";
if (isset($f['nulltype']))
{
$text .= "{$f['nulltype']} | ";
}
else
{
$text .= " | ";
}
if (isset($f['default']))
{
$text .= "default {$f['default']} | ";
}
elseif (isset($f['autoinc']))
{
$text .= "AUTO_INCREMENT | ";
}
else
{
$text .= " | ";
}
$text .= "
";
break;
default :
$text .= "!!Unknown!! | {$f['type']} | |
";
}
}
$text .= "
--Ends--
";
return $text;
}
//--------------------------------------------------
// Update a table to required structure
//--------------------------------------------------
// $newStructure is an array element as returned from get_table_def()
// If $mlUpdate is TRUE, applies same query to all tables of same language
// Return TRUE on success.
// Return text string if $justCheck is TRUE and changes needed
// Return text string on most failures
// Return FALSE on certain failures (generally indicative of code/system problems)
function update_table_structure($newStructure, $justCheck=FALSE, $makeNewifNotExist = TRUE, $mlUpdate = FALSE)
{
global $sql;
// Pull out table name
$tableName = $newStructure[1];
if (!$sql->db_Table_exists($tableName))
{
if ($makeNewifNotExist === FALSE) { return 'Table doesn\'t exist'; }
if ($sql->db_Select_gen($newStructure[0])) { return TRUE; }
return 'Error creating new table: '.$tableName;
}
$reqFields = $this->parse_field_defs($newStructure[2]); // Required field definitions
if (E107_DBG_FILLIN8) { echo "Required table structure:
".$this->make_field_list($reqFields); }
if ((($actualDefs = $this->get_current_table($tableName)) === FALSE) || !is_array($actualDefs)) // Get actual table definition (Adds current default prefix)
{
return "Couldn't get table structure: {$tableName}
";
}
else
{
// echo $db_parser->make_table_list($actual_defs);
$actualFields = $this->parse_field_defs($actualDefs[0][2]); // Split into field definitions
if (E107_DBG_FILLIN8) { echo 'Actual table structure:
'.$this->make_field_list($actualFields); }
$diffs = $this->compare_field_lists($reqFields,$actualFields); // Work out any differences
if (count($diffs[0]))
{ // Changes needed
if ($justCheck) { return 'Field changes rqd; table: '.$tableName.'
'; }
// Do the changes here
if (E107_DBG_FILLIN8) { echo "List of changes found:
".$this->make_changes_list($diffs); }
$qry = 'ALTER TABLE '.MPREFIX.$tableName.' '.implode(', ',$diffs[1]);
if (E107_DBG_FILLIN8) { echo 'Update Query used: '.$qry.'
'; }
if ($mlUpdate)
{
$ret = $sql->db_Query_all($qry); // Returns TRUE = success, FALSE = fail
}
else
{
$ret = $sql->db_Select_gen($qry);
}
if ($ret === FALSE)
{
return $sql->dbError() ;
}
}
return TRUE; // Success even if no changes required
}
return FALSE;
}
function createTable($pathToSqlFile='', $tableName='', $addPrefix=true, $renameTable='')
{
$e107 = e107::getInstance();
$tmp = $this->get_table_def($tableName, $pathToSqlFile);
$createText = $tmp[0][0];
$newTableName = ($renameTable ? $renameTable : $tableName);
if($addPrefix) { $newTableName = MPREFIX.$newTableName; }
if($newTableName != $tableName)
{
$createText = preg_replace('#create +table +(\w*?) +#i', 'CREATE TABLE '.$newTableName.' ', $createText);
}
return $e107->sql->db_Select_gen($createText);
}
}
?>