2010-01-23 02:07:50 +00:00
|
|
|
<?php
|
2007-12-08 15:11:50 +00:00
|
|
|
/*
|
2009-09-13 20:22:39 +00:00
|
|
|
* e107 website system
|
|
|
|
*
|
2013-03-24 13:07:15 +01:00
|
|
|
* Copyright (C) 2008-2013 e107 Inc (e107.org)
|
2009-09-13 20:22:39 +00:00
|
|
|
* Released under the terms and conditions of the
|
|
|
|
* GNU General Public License (http://www.gnu.org/licenses/gpl.txt)
|
|
|
|
*
|
|
|
|
* Database utilities
|
|
|
|
*
|
2007-12-08 15:11:50 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
2009-09-13 20:22:39 +00:00
|
|
|
Database utilities for admin tasks:
|
|
|
|
Get structure of a table from a database
|
|
|
|
Get structure of a table from a file
|
|
|
|
First level parse of table structure
|
|
|
|
Parse of field definitions part of table structure
|
|
|
|
Comparison of two structures, including generation of MySQL to make them the same
|
|
|
|
Some crude printing utilities
|
|
|
|
Note: there are some uncommented 'echo' statements which are intentional to highlight that something's gone wrong! (not that it should, of course)
|
|
|
|
*/
|
2007-12-08 15:11:50 +00:00
|
|
|
|
2011-06-15 05:41:06 +00:00
|
|
|
|
2022-04-04 10:54:24 -07:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2007-12-08 15:11:50 +00:00
|
|
|
class db_table_admin
|
|
|
|
{
|
2020-11-29 14:05:14 -08:00
|
|
|
protected $file_buffer = ''; // Contents of a file
|
|
|
|
protected $last_file = '';
|
|
|
|
protected $errors = array();
|
2009-09-13 20:22:39 +00:00
|
|
|
|
2008-12-20 00:55:29 +00:00
|
|
|
// Get list of fields and keys for a table - return FALSE if unsuccessful
|
|
|
|
// Return as for get_table_def
|
2022-04-04 10:54:24 -07:00
|
|
|
/**
|
|
|
|
* @param $table_name
|
|
|
|
* @param $prefix
|
|
|
|
* @return false|mixed|string
|
|
|
|
*/
|
2008-12-20 00:55:29 +00:00
|
|
|
function get_current_table($table_name, $prefix = "")
|
|
|
|
{
|
2013-03-24 13:07:15 +01:00
|
|
|
$sql = e107::getDb();
|
2010-01-23 02:07:50 +00:00
|
|
|
if(!isset($sql))
|
|
|
|
{
|
|
|
|
$sql = new db;
|
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
|
|
|
if (!$prefix)
|
|
|
|
{
|
|
|
|
$prefix = MPREFIX;
|
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
// echo "Get table structure for: {$table_name}, prefix: {$prefix}<br />";
|
2016-02-15 00:56:08 -08:00
|
|
|
$sql->gen('SET SQL_QUOTE_SHOW_CREATE = 1');
|
2008-12-20 00:55:29 +00:00
|
|
|
$qry = 'SHOW CREATE TABLE `'.$prefix.$table_name."`";
|
2016-02-15 00:56:08 -08:00
|
|
|
if (!($z = $sql->gen($qry)))
|
2009-09-13 20:22:39 +00:00
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
2020-12-23 13:23:48 -08:00
|
|
|
$row = $sql->fetch('num');
|
2009-09-13 20:22:39 +00:00
|
|
|
$tmp = str_replace("`", "", stripslashes($row[1])).';'; // Add semicolon to work with our parser
|
2020-12-17 13:13:29 -08:00
|
|
|
$count = preg_match_all("#CREATE\s+?TABLE\s+?`?({$prefix}{$table_name})`?\s+?\((.*?)\)\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is", $tmp, $matches, PREG_SET_ORDER);
|
2009-09-13 20:22:39 +00:00
|
|
|
if ($count === FALSE)
|
|
|
|
{
|
|
|
|
return "Error occurred";
|
|
|
|
}
|
|
|
|
if (!$count)
|
|
|
|
{
|
|
|
|
return "No matches";
|
|
|
|
}
|
2020-11-29 14:05:14 -08:00
|
|
|
|
|
|
|
if(isset($matches[0][2]) && is_string($matches[0][2]))
|
|
|
|
{
|
|
|
|
$matches[0][2] = trim($matches[0][2]);
|
|
|
|
}
|
|
|
|
|
2008-12-20 00:55:29 +00:00
|
|
|
return $matches;
|
2007-12-08 15:11:50 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
2009-11-20 22:23:02 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2010-01-23 02:07:50 +00:00
|
|
|
*
|
2009-11-20 22:23:02 +00:00
|
|
|
* @param string $table_name - If specified, 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)
|
2020-11-29 14:05:14 -08:00
|
|
|
* @param string $file_name
|
2009-11-20 22:23:02 +00:00
|
|
|
* @return string|array
|
|
|
|
* - if error, returns a brief text message
|
|
|
|
* - if successful, returns an array of table definitions, each of which 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.
|
|
|
|
*/
|
2009-09-13 20:22:39 +00:00
|
|
|
function get_table_def($table_name = '', $file_name = "")
|
2007-12-08 15:11:50 +00:00
|
|
|
{
|
2008-12-20 00:55:29 +00:00
|
|
|
if ($file_name != '')
|
2009-09-13 20:22:39 +00:00
|
|
|
{ // Read in and buffer a new file (if we've not already got one)
|
2008-12-20 00:55:29 +00:00
|
|
|
if ($this->last_file != $file_name)
|
2007-12-08 15:11:50 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
if (!is_readable($file_name))
|
|
|
|
{
|
|
|
|
return "No file";
|
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
$temp = file_get_contents($file_name);
|
|
|
|
// Strip any php header
|
2009-11-20 22:23:02 +00:00
|
|
|
$temp = preg_replace("#\<\?php.*?\?\>#mis", '', $temp);
|
|
|
|
// Strip any comments (only /*...*/ supported
|
|
|
|
$this->file_buffer = preg_replace("#\/\*.*?\*\/#mis", '', $temp);
|
2008-12-20 00:55:29 +00:00
|
|
|
$this->last_file = $file_name;
|
2007-12-08 15:11:50 +00:00
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
if (!$table_name)
|
|
|
|
{
|
|
|
|
$table_name = '\w+?';
|
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
// Regex should be identical to that in get_current_table (apart from the source text variable name)
|
2020-12-17 13:13:29 -08:00
|
|
|
$count = preg_match_all("#CREATE\s+?TABLE\s+?`?({$table_name})`?\s+?\((.*?)\)\s+?(?:TYPE|ENGINE)\s*\=\s*(.*?);#is", $this->file_buffer, $matches, PREG_SET_ORDER);
|
2009-09-13 20:22:39 +00:00
|
|
|
if ($count === false)
|
|
|
|
{
|
|
|
|
return "Error occurred";
|
|
|
|
}
|
|
|
|
if (!$count)
|
|
|
|
{
|
|
|
|
return "No matches";
|
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
return $matches;
|
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
2008-12-20 00:55:29 +00:00
|
|
|
// 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
|
2022-04-04 10:54:24 -07:00
|
|
|
/**
|
|
|
|
* @param $text
|
|
|
|
* @return array|false
|
|
|
|
*/
|
2008-12-20 00:55:29 +00:00
|
|
|
function parse_field_defs($text)
|
|
|
|
{
|
2021-09-04 15:06:19 +02:00
|
|
|
$text = (string) $text;
|
2009-09-13 20:22:39 +00:00
|
|
|
$ans = array(
|
|
|
|
);
|
|
|
|
$text = str_replace("\r", "\n", $text);
|
|
|
|
$field_lines = explode("\n", $text);
|
2020-11-29 14:05:14 -08:00
|
|
|
|
|
|
|
$defs = array();
|
2008-12-20 00:55:29 +00:00
|
|
|
foreach ($field_lines as $fv)
|
|
|
|
{
|
|
|
|
unset($defs);
|
2009-09-13 20:22:39 +00:00
|
|
|
$fv = trim(str_replace(' ', ' ', $fv));
|
2009-12-01 20:05:54 +00:00
|
|
|
$fv = str_replace('`', '', $fv);
|
2009-09-13 20:22:39 +00:00
|
|
|
if (substr($fv, -1) == ',')
|
|
|
|
{
|
|
|
|
$fv = trim(substr($fv, 0, -1));
|
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
// echo "Line: ".$fv."<br />";
|
|
|
|
if ($fv)
|
2007-12-08 15:11:50 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
$fd = explode(' ', $fv);
|
2008-12-20 00:55:29 +00:00
|
|
|
switch (strtoupper($fd[0]))
|
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
case 'PRIMARY':
|
|
|
|
if (strtoupper($fd[1]) == 'KEY')
|
|
|
|
$defs['type'] = 'pkey';
|
2008-12-20 00:55:29 +00:00
|
|
|
$defs['name'] = $fd[2];
|
|
|
|
break;
|
2009-09-13 20:22:39 +00:00
|
|
|
|
|
|
|
case 'UNIQUE':
|
|
|
|
if (count($fd) < 3)
|
|
|
|
{
|
2020-11-29 14:05:14 -08:00
|
|
|
$this->errors[] = "Truncated definition after UNIQUE {$fv}: ".$fd[1]."<br />";
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
|
|
|
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
|
|
|
|
{
|
2020-11-29 14:05:14 -08:00
|
|
|
$this->errors[] = "Unrecognised word after UNIQUE in definition {$fv}: ".$fd[1]."<br />";
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
break;
|
2009-09-13 20:22:39 +00:00
|
|
|
|
|
|
|
case 'FULLTEXT':
|
|
|
|
if (count($fd) < 3)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2020-11-29 14:05:14 -08:00
|
|
|
$this->errors[] = "Truncated definition after FULLTEXT {$fv}: ".$fd[1]."<br />";
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
elseif (strtoupper($fd[1]) == 'KEY')
|
|
|
|
{
|
|
|
|
$defs['type'] = 'ftkey';
|
|
|
|
$defs['name'] = $fd[2];
|
|
|
|
if (isset($fd[3])) $defs['keyfield'] = $fd[3];
|
|
|
|
else $defs['keyfield'] = '['.$fd[2].']';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-11-29 14:05:14 -08:00
|
|
|
$this->errors[] = "Unrecognised word after FULLTEXT in definition {$fv}: ".$fd[1]."<br />";
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'KEY':
|
|
|
|
$defs['type'] = 'key';
|
|
|
|
$defs['name'] = $fd[1];
|
|
|
|
if (isset($fd[2]))
|
|
|
|
{
|
|
|
|
$defs['keyfield'] = $fd[2];
|
|
|
|
}
|
|
|
|
else
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
$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'))
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
$defs['vartype'] = $fd[2];
|
2008-12-20 00:55:29 +00:00
|
|
|
$i++;
|
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
|
|
|
while ($i < count($fd))
|
|
|
|
{
|
|
|
|
switch (strtoupper($fd[$i]))
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
case 'NOT':
|
|
|
|
if (isset($fd[$i + 1]) && strtoupper($fd[$i + 1]) == 'NULL')
|
|
|
|
{
|
|
|
|
$i++;
|
|
|
|
$defs['nulltype'] = 'NOT NULL';
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // Syntax error
|
2020-11-29 14:05:14 -08:00
|
|
|
$this->errors[] = "Unrecognised word in definition {$i} after 'NOT': ".$fd[$i + 1]."<br />";
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
|
|
|
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:
|
2009-10-22 14:31:28 +00:00
|
|
|
if(E107_DBG_SQLDETAILS)
|
|
|
|
{
|
|
|
|
$mes = e107::getMessage();
|
2010-01-23 02:07:50 +00:00
|
|
|
$mes->add("db_table_admin_class.php :: parse_field_defs() Line: 230 - Unknown definition {$i}: ".$fd[$i], E_MESSAGE_DEBUG);
|
2009-10-22 14:31:28 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
$i++;
|
|
|
|
}
|
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
if (count($defs) > 1)
|
|
|
|
{
|
|
|
|
$ans[] = $defs;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-11-29 14:05:14 -08:00
|
|
|
$this->errors[] = "Partial definition<br />";
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2007-12-08 15:11:50 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
if (!count($ans))
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
return $ans;
|
2007-12-08 15:11:50 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
|
|
|
// Utility routine - given our array-based definition, create a string MySQL field definition
|
2022-04-04 10:54:24 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $list
|
|
|
|
* @return mixed|string
|
|
|
|
*/
|
|
|
|
function make_def($list)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
switch ($list['type'])
|
|
|
|
{
|
|
|
|
case 'key':
|
|
|
|
return 'KEY '.$list['name'].' ('.str_replace(array( '(', ')' ), '', $list['keyfield']).')';
|
|
|
|
case 'ukey':
|
|
|
|
return 'UNIQUE KEY '.$list['name'].' ('.str_replace(array( '(', ')' ), '', $list['keyfield']).')';
|
|
|
|
case 'ftkey':
|
|
|
|
return 'FULLTEXT KEY '.$list['name'].' ('.str_replace(array( '(', ')' ), '', $list['keyfield']).')';
|
|
|
|
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']))
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
$def .= ' '.$list['fieldtype'];
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
if (isset($list['vartype']))
|
|
|
|
{
|
|
|
|
$def .= ' '.$list['vartype'];
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
if (isset($list['nulltype']))
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
$def .= ' '.$list['nulltype'];
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
if (isset($list['default']))
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
$def .= ' default '.$list['default'];
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2015-02-14 23:34:15 -08:00
|
|
|
if (vartrue($list['autoinc']))
|
2009-09-13 20:22:39 +00:00
|
|
|
{
|
|
|
|
$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
|
2022-04-04 10:54:24 -07:00
|
|
|
/**
|
|
|
|
* @param $list1
|
|
|
|
* @param $list2
|
|
|
|
* @param $stop_on_error
|
|
|
|
* @return array[]|bool
|
|
|
|
*/
|
|
|
|
function compare_field_lists($list1, $list2, $stop_on_error = FALSE)
|
2009-09-13 20:22:39 +00:00
|
|
|
{
|
|
|
|
$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'].'<br />';
|
|
|
|
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'].'<br />';
|
|
|
|
if ($stop_on_error)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
$found = FALSE;
|
2020-12-20 11:50:10 -08:00
|
|
|
for ($k = $i + 1, $kMax = count($list1); $k < $kMax; $k++)
|
2009-09-13 20:22:39 +00:00
|
|
|
{
|
|
|
|
// 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<br />";
|
|
|
|
$found = TRUE;
|
2008-12-20 00:55:29 +00:00
|
|
|
break;
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
|
|
|
// echo " - no match<br />";
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
2020-12-20 11:50:10 -08:00
|
|
|
for ($k = 0, $kMax = count($list2); $k < $kMax; $k++)
|
2009-09-13 20:22:39 +00:00
|
|
|
{
|
|
|
|
// 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<br />";
|
|
|
|
$found = TRUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// echo " - no match<br />";
|
|
|
|
}
|
|
|
|
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
|
2008-12-20 00:55:29 +00:00
|
|
|
$created_list[$j] = $list1[$i]['name'];
|
|
|
|
$j++;
|
2009-09-13 20:22:39 +00:00
|
|
|
// 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 'ftkey':
|
|
|
|
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;
|
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
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'].'<br />';
|
|
|
|
foreach ($list1[$i] as $fi=>$v)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
$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]);
|
2008-12-20 00:55:29 +00:00
|
|
|
break;
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
|
|
|
array_shift($list2);
|
|
|
|
$created_list[$j] = $list1[$i]['name'];
|
|
|
|
$j++;
|
|
|
|
}
|
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
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'].'<br />';
|
|
|
|
if ($stop_on_error)
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
switch ($list1[$i]['type'])
|
|
|
|
{
|
|
|
|
case 'key':
|
|
|
|
case 'ukey':
|
|
|
|
case 'ftkey':
|
|
|
|
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<br />";
|
|
|
|
foreach ($list2 as $f)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
switch ($f['type'])
|
|
|
|
{
|
|
|
|
case 'key':
|
|
|
|
case 'ukey':
|
|
|
|
case 'ftkey':
|
|
|
|
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;
|
|
|
|
}
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2007-12-08 15:11:50 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
if ($stop_on_error)
|
|
|
|
return TRUE; // If doing a simple comparison and we get to here, all matches
|
|
|
|
return array(
|
|
|
|
$error_list, $change_list
|
|
|
|
);
|
2007-12-08 15:11:50 +00:00
|
|
|
}
|
2022-04-04 10:54:24 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $result
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
function make_changes_list($result)
|
2008-06-15 09:52:09 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
if (!is_array($result))
|
|
|
|
{
|
|
|
|
return "Not an array<br />";
|
|
|
|
}
|
|
|
|
$text = "<table>";
|
2020-12-20 11:50:10 -08:00
|
|
|
for ($i = 0, $iMax = count($result[0]); $i < $iMax; $i++)
|
2009-09-13 20:22:39 +00:00
|
|
|
{
|
|
|
|
$text .= "<tr><td>{$result[0][$i]}</td>";
|
|
|
|
$text .= "<td>{$result[1][$i]}</td>";
|
|
|
|
$text .= "</tr>\n";
|
|
|
|
}
|
|
|
|
$text .= "</table><br /><br />";
|
|
|
|
return $text;
|
2008-06-15 09:52:09 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
|
|
|
// Return a table of info from the output of get_table_def
|
2022-04-04 10:54:24 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $result
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
function make_table_list($result)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
if (!is_array($result))
|
|
|
|
{
|
|
|
|
return "Not an array<br />";
|
|
|
|
}
|
|
|
|
$text = "<table>";
|
2020-12-20 11:50:10 -08:00
|
|
|
for ($i = 0, $iMax = count($result); $i < $iMax; $i++)
|
2009-09-13 20:22:39 +00:00
|
|
|
{
|
|
|
|
$text .= "<tr><td>{$result[$i][0]}</td>";
|
|
|
|
$text .= "<td>{$result[$i][1]}</td>";
|
|
|
|
$text .= "<td>{$result[$i][2]}</td>";
|
|
|
|
$text .= "<td>{$result[$i][3]}</td></tr>\n";
|
|
|
|
}
|
|
|
|
$text .= "</table><br /><br />";
|
|
|
|
return $text;
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
|
|
|
// Return a table of info from the output of parse_field_defs()
|
2022-04-04 10:54:24 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $fields
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
function make_field_list($fields)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
$text = "<table>";
|
|
|
|
foreach ($fields as $f)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
switch ($f['type'])
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
case 'pkey':
|
|
|
|
$text .= "<tr><td>PRIMARY KEY</td><td>{$f['name']}</td><td> </td></tr>";
|
|
|
|
break;
|
|
|
|
case 'ukey':
|
|
|
|
$text .= "<tr><td>UNIQUE KEY</td><td>{$f['name']}</td><td>{$f['keyfield']}</td></tr>";
|
|
|
|
break;
|
|
|
|
case 'ftkey':
|
|
|
|
$text .= "<tr><td>FULLTEXT KEY</td><td>{$f['name']}</td><td>{$f['keyfield']}</td></tr>";
|
|
|
|
break;
|
|
|
|
case 'key':
|
|
|
|
$text .= "<tr><td>KEY</td><td>{$f['name']}</td><td>{$f['keyfield']}</td></tr>";
|
|
|
|
break;
|
|
|
|
case 'field':
|
|
|
|
$text .= "<tr><td>FIELD</td><td>{$f['name']}</td><td>{$f['fieldtype']}";
|
|
|
|
if (isset($f['vartype']))
|
|
|
|
{
|
|
|
|
$text .= " ".$f['vartype'];
|
|
|
|
}
|
|
|
|
$text .= "</td>";
|
|
|
|
if (isset($f['nulltype']))
|
|
|
|
{
|
|
|
|
$text .= "<td>{$f['nulltype']}</td>";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$text .= "<td> </td>";
|
|
|
|
}
|
|
|
|
if (isset($f['default']))
|
|
|
|
{
|
|
|
|
$text .= "<td>default {$f['default']}</td>";
|
|
|
|
}
|
|
|
|
elseif (isset($f['autoinc']))
|
|
|
|
{
|
|
|
|
$text .= "<td>AUTO_INCREMENT</td>";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$text .= "<td> </td>";
|
|
|
|
}
|
|
|
|
$text .= "</tr>";
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
$text .= "<tr><td>!!Unknown!!</td><td>{$f['type']}</td><td> </td></tr>";
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
$text .= "</table><br /><br />--Ends--<br />";
|
|
|
|
return $text;
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
|
|
|
//--------------------------------------------------
|
|
|
|
// 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)
|
2022-04-04 10:54:24 -07:00
|
|
|
/**
|
|
|
|
* @param $newStructure
|
|
|
|
* @param $justCheck
|
|
|
|
* @param $makeNewifNotExist
|
|
|
|
* @param $mlUpdate
|
|
|
|
* @return bool|string
|
|
|
|
*/
|
|
|
|
function update_table_structure($newStructure, $justCheck = FALSE, $makeNewifNotExist = TRUE, $mlUpdate = FALSE)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
global $sql;
|
|
|
|
// Pull out table name
|
|
|
|
$debugLevel = E107_DBG_SQLDETAILS;
|
2009-10-22 14:31:28 +00:00
|
|
|
|
2009-09-13 20:22:39 +00:00
|
|
|
$tableName = $newStructure[1];
|
2015-07-07 13:02:34 -07:00
|
|
|
if (!$sql->isTable($tableName))
|
2009-09-13 20:22:39 +00:00
|
|
|
{
|
|
|
|
if ($makeNewifNotExist === FALSE)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
return 'Table doesn\'t exist';
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2016-02-15 00:56:08 -08:00
|
|
|
if ($sql->gen($newStructure[0]))
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
return TRUE;
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
return 'Error creating new table: '.$tableName;
|
|
|
|
}
|
|
|
|
$reqFields = $this->parse_field_defs($newStructure[2]); // Required field definitions
|
|
|
|
if ($debugLevel)
|
|
|
|
{
|
|
|
|
echo "Required table structure: <br />".$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}<br />";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// echo $db_parser->make_table_list($actual_defs);
|
|
|
|
$actualFields = $this->parse_field_defs($actualDefs[0][2]); // Split into field definitions
|
|
|
|
if ($debugLevel)
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2009-09-13 20:22:39 +00:00
|
|
|
echo 'Actual table structure: <br />'.$this->make_field_list($actualFields);
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
|
|
|
$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.'<br />';
|
|
|
|
}
|
|
|
|
// Do the changes here
|
|
|
|
if ($debugLevel)
|
|
|
|
{
|
|
|
|
echo "List of changes found:<br />".$this->make_changes_list($diffs);
|
|
|
|
}
|
|
|
|
$qry = 'ALTER TABLE '.MPREFIX.$tableName.' '.implode(', ', $diffs[1]);
|
|
|
|
if ($debugLevel)
|
|
|
|
{
|
|
|
|
echo 'Update Query used: '.$qry.'<br />';
|
|
|
|
}
|
|
|
|
if ($mlUpdate)
|
|
|
|
{
|
|
|
|
$ret = $sql->db_Query_all($qry); // Returns TRUE = success, FALSE = fail
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2013-03-24 13:07:15 +01:00
|
|
|
$ret = $sql->gen($qry);
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
|
|
|
if ($ret === FALSE)
|
|
|
|
{
|
2020-12-18 19:55:12 -08:00
|
|
|
return $sql->dbError(__METHOD__);
|
2009-09-13 20:22:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return TRUE; // Success even if no changes required
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2020-11-29 14:05:14 -08:00
|
|
|
|
2008-12-20 00:55:29 +00:00
|
|
|
}
|
2022-04-04 10:54:24 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param $pathToSqlFile
|
|
|
|
* @param $tableName
|
|
|
|
* @param $addPrefix
|
|
|
|
* @param $renameTable
|
|
|
|
* @return bool|int
|
|
|
|
*/
|
|
|
|
function createTable($pathToSqlFile = '', $tableName = '', $addPrefix = true, $renameTable = '')
|
2008-12-20 00:55:29 +00:00
|
|
|
{
|
2020-11-29 14:05:14 -08:00
|
|
|
// $e107 = e107::getInstance();
|
2009-09-13 20:22:39 +00:00
|
|
|
$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);
|
|
|
|
}
|
2013-03-27 13:31:17 +01:00
|
|
|
return e107::getDb()->gen($createText);
|
2008-08-12 20:26:53 +00:00
|
|
|
}
|
2020-11-29 14:05:14 -08:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Used by $sql->makeTableDef() to create an e_CACHE_DB.$tableName.'.php' file.
|
|
|
|
* @param array $fieldDefs
|
|
|
|
* @return array as returned by parse_field_defs()
|
|
|
|
*/
|
|
|
|
public function make_field_types($fieldDefs=array())
|
|
|
|
{
|
|
|
|
$outDefs = array();
|
|
|
|
|
|
|
|
foreach ($fieldDefs as $k => $v)
|
|
|
|
{
|
|
|
|
switch ($v['type'])
|
|
|
|
{
|
|
|
|
case 'field' :
|
|
|
|
// if (!empty($v['autoinc']))
|
|
|
|
// {
|
|
|
|
//break; Probably include autoinc fields in array
|
|
|
|
// }
|
|
|
|
|
|
|
|
$baseType = preg_replace('#\(.*?\)#', '', $v['fieldtype']); // Should strip any length
|
|
|
|
|
|
|
|
switch ($baseType)
|
|
|
|
{
|
|
|
|
case 'int' :
|
|
|
|
case 'integer':
|
|
|
|
case 'smallint':
|
|
|
|
case 'shortint' :
|
|
|
|
case 'tinyint' :
|
|
|
|
case 'mediumint':
|
|
|
|
case 'bigint':
|
|
|
|
$outDefs['_FIELD_TYPES'][$v['name']] = 'int';
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'char' :
|
|
|
|
case 'text' :
|
|
|
|
case 'varchar' :
|
|
|
|
case 'tinytext' :
|
|
|
|
case 'mediumtext' :
|
|
|
|
case 'longtext' :
|
|
|
|
case 'enum' :
|
|
|
|
$outDefs['_FIELD_TYPES'][$v['name']] = 'escape'; //XXX toDB() causes serious BC issues.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if($v['name'])
|
|
|
|
|
|
|
|
|
|
|
|
if (isset($v['nulltype']) && !isset($v['default']))
|
|
|
|
{
|
|
|
|
$outDefs['_NOTNULL'][$v['name']] = '';
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'pkey' :
|
|
|
|
case 'ukey' :
|
|
|
|
case 'key' :
|
|
|
|
case 'ftkey' :
|
|
|
|
break; // Do nothing with keys for now
|
|
|
|
default :
|
|
|
|
$this->errors[] = "Unexpected field type: {$k} => {$v['type']}<br />";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $outDefs;
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2008-08-12 20:26:53 +00:00
|
|
|
}
|
2009-09-13 20:22:39 +00:00
|
|
|
|
2007-12-08 15:11:50 +00:00
|
|
|
|
|
|
|
|
2020-06-05 11:34:17 -07:00
|
|
|
|