482 lines
12 KiB
PHP
482 lines
12 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* This file is a part of MyWebSQL package
|
||
|
|
*
|
||
|
|
* @file: lib/tableeditor.php
|
||
|
|
* @author Samnan ur Rehman
|
||
|
|
* @copyright (c) 2008-2014 Samnan ur Rehman
|
||
|
|
* @web http://mywebsql.net
|
||
|
|
* @license http://mywebsql.net/license
|
||
|
|
*/
|
||
|
|
|
||
|
|
class tableEditor {
|
||
|
|
var $db;
|
||
|
|
var $table;
|
||
|
|
var $fields;
|
||
|
|
var $deleteFields;
|
||
|
|
var $indexes;
|
||
|
|
var $properties;
|
||
|
|
var $_loaded;
|
||
|
|
var $_modified;
|
||
|
|
|
||
|
|
var $backquote;
|
||
|
|
|
||
|
|
var $_sql; // last generated sql is saved for debugging purposes
|
||
|
|
|
||
|
|
function __construct(&$db) {
|
||
|
|
$this->db = $db;
|
||
|
|
$this->_loaded = false;
|
||
|
|
$this->_modified = false;
|
||
|
|
$this->fields = false;
|
||
|
|
$this->deleteFields = false;
|
||
|
|
$this->indexes = false;
|
||
|
|
$this->properties = false;
|
||
|
|
|
||
|
|
$this->backquote = $db->getBackQuotes();
|
||
|
|
}
|
||
|
|
|
||
|
|
function loadTable($fields=true, $indexes=true, $props=true) {
|
||
|
|
if($fields == true) {
|
||
|
|
$sql = "show fields from ".$this->backquote.$this->db->escape($this->table).$this->backquote;
|
||
|
|
if (!$this->db->query($sql, "create_stmt"))
|
||
|
|
return FALSE;
|
||
|
|
|
||
|
|
$this->fields = array();
|
||
|
|
while($row = $this->db->fetchRow("create_stmt"))
|
||
|
|
$this->fields[] = $this->fetchFieldInfo($row);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($indexes == true) {
|
||
|
|
if (!$this->fetchIndexInfo())
|
||
|
|
return FALSE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($props == true) {
|
||
|
|
$row = $this->db->getTableProperties($this->table);
|
||
|
|
$this->properties = $this->fetchTableProperties($row);
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->_loaded = true;
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
function setName($name) {
|
||
|
|
$this->table = $name;
|
||
|
|
$this->_modified = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function setFields($fields) {
|
||
|
|
$this->fields = $fields;
|
||
|
|
$this->_modified = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function setIndexes($indexes) {
|
||
|
|
$this->indexes = $indexes;
|
||
|
|
$this->_modified = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function setProperties($properties) {
|
||
|
|
$this->properties = $properties;
|
||
|
|
$this->_modified = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function deleteFields($fields) {
|
||
|
|
$this->deleteFields = $fields;
|
||
|
|
$this->_modified = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getName() {
|
||
|
|
return $this->table;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getFields() {
|
||
|
|
return $this->fields;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getIndexes() {
|
||
|
|
return $this->indexes;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getProperties() {
|
||
|
|
return $this->properties;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getSql() {
|
||
|
|
return $this->_sql;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getCreateStatement() {
|
||
|
|
if(!$this->_modified)
|
||
|
|
return $this->_sql;
|
||
|
|
|
||
|
|
$str = 'CREATE TABLE '.$this->backquote.$this->db->escape($this->table).$this->backquote." (\n";
|
||
|
|
$list = $this->fields;
|
||
|
|
|
||
|
|
for($i=0; $i<count($list); $i++) {
|
||
|
|
$one = $list[$i];
|
||
|
|
|
||
|
|
$str .= $this->getFieldDefinition($one) . ",\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
$key = $this->generateKeyStatement();
|
||
|
|
$str = $key == '' ? substr($str, 0, -2) : $str . $key;
|
||
|
|
|
||
|
|
$str .= "\n)\n";
|
||
|
|
|
||
|
|
$str .= $this->generatePropsStatement();
|
||
|
|
|
||
|
|
$this->_sql = $str;
|
||
|
|
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAlterStatement() {
|
||
|
|
if(!$this->_modified)
|
||
|
|
return $this->_sql;
|
||
|
|
|
||
|
|
$str = '';
|
||
|
|
|
||
|
|
if ($this->deleteFields) {
|
||
|
|
$list = $this->deleteFields;
|
||
|
|
|
||
|
|
for($i=0; $i<count($list); $i++) {
|
||
|
|
$fname = $list[$i];
|
||
|
|
$str .= 'DROP COLUMN ' . $this->backquote . $fname . $this->backquote . ",\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->fields) {
|
||
|
|
$list = $this->fields;
|
||
|
|
for($i=0; $i<count($list); $i++) {
|
||
|
|
$one = $list[$i];
|
||
|
|
$state = $one->fstate;
|
||
|
|
if ($state == 'new') {
|
||
|
|
$str .= 'ADD COLUMN ' . $this->getFieldDefinition($one);
|
||
|
|
if ($i == 0)
|
||
|
|
$str .= ' FIRST';
|
||
|
|
else {
|
||
|
|
$prev_field = $list[$i-1];
|
||
|
|
$str .= ' AFTER ' . $this->backquote . $prev_field->fname . $this->backquote;
|
||
|
|
}
|
||
|
|
$str .= ",\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
for($i=0; $i<count($list); $i++) {
|
||
|
|
$one = $list[$i];
|
||
|
|
$state = $one->fstate;
|
||
|
|
if ($state == 'change')
|
||
|
|
$str .= 'CHANGE ' . $this->backquote . $one->oname . $this->backquote . ' ' . $this->getFieldDefinition($one) . ",\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($this->fields || $this->indexes)
|
||
|
|
$str .= $this->generatePrimaryKeyStatement();
|
||
|
|
|
||
|
|
if ($this->properties) {
|
||
|
|
$str2 = $this->generatePropsStatement();
|
||
|
|
if ($str2 != '')
|
||
|
|
$str .= $str2;
|
||
|
|
else
|
||
|
|
$str = substr($str, 0, -2);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($str != '')
|
||
|
|
$str = 'ALTER TABLE '.$this->backquote.$this->db->escape($this->table).$this->backquote."\n" . $str;
|
||
|
|
|
||
|
|
$this->_sql = $str;
|
||
|
|
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* used in create command */
|
||
|
|
function generateKeyStatement() {
|
||
|
|
$str = '';
|
||
|
|
$numKeys = 0;
|
||
|
|
$list = $this->fields;
|
||
|
|
|
||
|
|
for($i=0; $i<count($list); $i++) {
|
||
|
|
$one = $list[$i];
|
||
|
|
if ($one->fpkey) {
|
||
|
|
$str .= $this->backquote . $this->db->escape($one->fname) . $this->backquote . ',';
|
||
|
|
$numKeys++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($numKeys > 0) {
|
||
|
|
$str = 'PRIMARY KEY (' . substr($str, 0, -1) . ')';
|
||
|
|
}
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* used in alter command */
|
||
|
|
function generatePrimaryKeyStatement() {
|
||
|
|
$this->fetchIndexInfo();
|
||
|
|
$pkey = isset($this->indexes['PRIMARY']) ? $this->indexes['PRIMARY'] : array();
|
||
|
|
|
||
|
|
$str = '';
|
||
|
|
|
||
|
|
$old = array();
|
||
|
|
for($i=0; $i<count($pkey); $i++)
|
||
|
|
$old[] = $pkey[$i]->column;
|
||
|
|
|
||
|
|
$new = array();
|
||
|
|
for($j=0; $j<count($this->fields); $j++) {
|
||
|
|
$one = $this->fields[$j];
|
||
|
|
if ($one->fpkey == 'Yes')
|
||
|
|
$new[] = $one->oname;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( count($old) > 0 && !$this->isIndexMatch($old, $new) )
|
||
|
|
$str .= " DROP PRIMARY KEY,\n";
|
||
|
|
|
||
|
|
if ( count($new) > 0 && !$this->isIndexMatch($old, $new) ) {
|
||
|
|
$str2 = '';
|
||
|
|
for($i=0; $i<count($this->fields); $i++) {
|
||
|
|
$one = $this->fields[$i];
|
||
|
|
if ($one->fpkey == 'Yes')
|
||
|
|
$str2 .= $this->backquote.$one->fname.$this->backquote.',';
|
||
|
|
}
|
||
|
|
$str .= " ADD PRIMARY KEY(".substr($str2, 0, -1)."),\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* used in create, alter command */
|
||
|
|
function generatePropsStatement() {
|
||
|
|
if(!$this->_modified)
|
||
|
|
return '';
|
||
|
|
$str = '';
|
||
|
|
|
||
|
|
$row = $this->db->getTableProperties($this->table);
|
||
|
|
$oldProps = $this->fetchTableProperties($row);
|
||
|
|
|
||
|
|
if(v($this->properties->engine) && v($this->properties->engine) != v($oldProps->engine))
|
||
|
|
$str .= ' ENGINE='.$this->db->escape($this->properties->engine);
|
||
|
|
|
||
|
|
if(v($this->properties->charset) && v($this->properties->charset) != v($oldProps->charset))
|
||
|
|
$str .= ' CHARSET='.$this->db->escape($this->properties->charset);
|
||
|
|
|
||
|
|
if(v($this->properties->collation) && v($this->properties->collation) != v($oldProps->collation))
|
||
|
|
$str .= ' COLLATE='.$this->db->escape($this->properties->collation);
|
||
|
|
|
||
|
|
if(v($this->properties->comment) && v($this->properties->comment) != v($oldProps->comment))
|
||
|
|
$str .= " COMMENT='".$this->db->escape($this->properties->comment) . "'";
|
||
|
|
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
function fetchFieldInfo($row) {
|
||
|
|
$field = new StdClass();
|
||
|
|
$field->fname = $row['Field'];
|
||
|
|
|
||
|
|
$len = strpos($row['Type'], '(');
|
||
|
|
if ($len === FALSE) { // normal field type without any length
|
||
|
|
$field->ftype = strtolower($row['Type']);
|
||
|
|
$field->flen = '';
|
||
|
|
$field->flist = '';
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$field->ftype = strtolower(substr($row['Type'], 0, $len));
|
||
|
|
if ($field->ftype == 'enum' || $field->ftype == 'set') { // enum or set type with list of values
|
||
|
|
$field->flen = '';
|
||
|
|
$regex = "/\('(.*)'\)/";
|
||
|
|
preg_match_all($regex, $row['Type'], $list);
|
||
|
|
$field->flist = array_map(function($s) { return str_replace("''", "'", $s); }, explode("','", $list[1][0]));
|
||
|
|
}
|
||
|
|
else { // field type with valid length identifier
|
||
|
|
$len2 = strpos($row['Type'], ')', $len);
|
||
|
|
$field->flen = ($len2 === FALSE ) ? '' : substr($row['Type'], $len+1, $len2-$len-1);
|
||
|
|
$field->flist = '';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$field->fval = $row['Default'];
|
||
|
|
//@todo: fix the following with proper regex search
|
||
|
|
$field->fsign = strpos($row['Type'], ' unsigned') === FALSE ? '0' : '1';
|
||
|
|
$field->fzero = strpos($row['Type'], ' zerofill') === FALSE ? '0' : '1';
|
||
|
|
$field->fpkey = $row['Key'] == 'PRI' ? '1' : '0';
|
||
|
|
$field->fauto = strpos($row['Extra'], 'auto_increment') === FALSE ? '0' : '1';
|
||
|
|
$field->fnull = $row['Null'] == 'NO' ? '1' : '0';
|
||
|
|
|
||
|
|
if ( $field->fnull == '1' && $field->fval == null)
|
||
|
|
$field->fval = '';
|
||
|
|
|
||
|
|
return $field;
|
||
|
|
}
|
||
|
|
|
||
|
|
function fetchIndexInfo($return = false) {
|
||
|
|
$indexes = array();
|
||
|
|
|
||
|
|
$sql = "show indexes from ".$this->backquote.$this->db->escape($this->table).$this->backquote;
|
||
|
|
if (!$this->db->query($sql, "index_stmt"))
|
||
|
|
return FALSE;
|
||
|
|
while($row = $this->db->fetchRow("index_stmt")) {
|
||
|
|
$key_name = $row['Key_name'];
|
||
|
|
$index = new StdClass();
|
||
|
|
$index->column = $row['Column_name'];
|
||
|
|
$index->type = $row['Index_type'];
|
||
|
|
$index->unique = $row['Non_unique'] == '1' ? '0' : '1';
|
||
|
|
$index->order = $row['Seq_in_index'];
|
||
|
|
|
||
|
|
if (!isset($indexes[$key_name]))
|
||
|
|
$indexes[$key_name] = array();
|
||
|
|
|
||
|
|
$indexes[$key_name][] = $index;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ($return)
|
||
|
|
return $indexes;
|
||
|
|
|
||
|
|
$this->indexes = $indexes;
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
function fetchTableProperties($row) {
|
||
|
|
$props = new StdClass();
|
||
|
|
$props->engine = $row['Engine'];
|
||
|
|
$props->charset = '';
|
||
|
|
$props->collation = $row['Collation'];
|
||
|
|
$props->comment = $row['Comment'];
|
||
|
|
|
||
|
|
// find out the charset is a bit trickier than you think...
|
||
|
|
$sql = "show collation like '".$this->db->escape($props->collation)."'";
|
||
|
|
if (!$this->db->query($sql, "temp_stmt") || $this->db->numRows("temp_stmt") == 0)
|
||
|
|
return $props;
|
||
|
|
|
||
|
|
$row = $this->db->fetchRow("temp_stmt");
|
||
|
|
$props->charset = $row['Charset'];
|
||
|
|
|
||
|
|
return $props;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getFieldDefinition($one) {
|
||
|
|
$str = $this->backquote.$one->fname.$this->backquote.' '.$one->ftype;
|
||
|
|
|
||
|
|
if (v($one->flist) && count($one->flist) > 0) { // enum/set values
|
||
|
|
$str2 = '(';
|
||
|
|
for($j=0; $j<count($one->flist);$j++)
|
||
|
|
$str2 .= "'" . str_replace("'", "\'", $one->flist[$j]) . "',";
|
||
|
|
$str .= substr($str2, 0, -1) . ')';
|
||
|
|
unset($str2);
|
||
|
|
}
|
||
|
|
else if (v($one->flen))
|
||
|
|
$str .= '(' . $one->flen . ')';
|
||
|
|
|
||
|
|
if ($one->fsign)
|
||
|
|
$str .= ' UNSIGNED';
|
||
|
|
|
||
|
|
if ($one->fzero)
|
||
|
|
$str .= ' ZEROFILL';
|
||
|
|
|
||
|
|
if ( $one->fval != '' ) {
|
||
|
|
$str .= ' DEFAULT ' . $this->getDefaultValueText($one->fval);
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $one->fnull )
|
||
|
|
$str .= ' NOT NULL';
|
||
|
|
|
||
|
|
if ($one->fauto)
|
||
|
|
$str .= ' AUTO_INCREMENT';
|
||
|
|
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getDefaultValueText($val) {
|
||
|
|
if ($val == 'NULL')
|
||
|
|
return 'NULL';
|
||
|
|
|
||
|
|
return $val;
|
||
|
|
}
|
||
|
|
|
||
|
|
function getAlterIndexStatement() {
|
||
|
|
if(!$this->_modified)
|
||
|
|
return $this->_sql;
|
||
|
|
|
||
|
|
$oldList = $this->fetchIndexInfo(true);
|
||
|
|
$list = $this->indexes;
|
||
|
|
|
||
|
|
$str = 'ALTER TABLE '.$this->backquote.$this->db->escape($this->table).$this->backquote."\n";
|
||
|
|
|
||
|
|
// delete indexes no longer required
|
||
|
|
foreach($oldList as $key => $info) {
|
||
|
|
if(array_key_exists($key, $list) === FALSE) {
|
||
|
|
if ($key == 'PRIMARY')
|
||
|
|
$str .= 'DROP PRIMARY KEY'. ",\n";
|
||
|
|
else
|
||
|
|
$str .= 'DROP KEY ' . $this->backquote . $key . $this->backquote . ",\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// add/modify remaining indexes
|
||
|
|
foreach($list as $key => $info) {
|
||
|
|
$match = false;
|
||
|
|
if(array_key_exists($key, $oldList)) {
|
||
|
|
$match = $this->matchIndexes($info, $oldList[$key]);
|
||
|
|
// if indexes are different, only then delete and recreate
|
||
|
|
if ( !$match ) {
|
||
|
|
if ($key == 'PRIMARY')
|
||
|
|
$str .= 'DROP PRIMARY KEY'. ",\n";
|
||
|
|
else
|
||
|
|
$str .= 'DROP KEY ' . $this->backquote . $key . $this->backquote . ",\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// add index only if added/changed
|
||
|
|
if ( !$match ) {
|
||
|
|
$str .= 'ADD ';
|
||
|
|
if (v($info[0]->unique) && $key != 'PRIMARY')
|
||
|
|
$str .= 'UNIQUE ';
|
||
|
|
if (v($info[0]->type) == 'FULLTEXT')
|
||
|
|
$str .= 'FULLTEXT ';
|
||
|
|
|
||
|
|
if ($key == 'PRIMARY')
|
||
|
|
$str .= 'PRIMARY KEY ';
|
||
|
|
else
|
||
|
|
$str .= 'KEY ' . $this->backquote . $this->db->escape($key) . $this->backquote;
|
||
|
|
|
||
|
|
$str .= '(';
|
||
|
|
foreach($info as $field) {
|
||
|
|
if (v($field->length) != '')
|
||
|
|
$str .= $this->backquote . $this->db->escape($field->column) . $this->backquote . '(' . $field->length . '),';
|
||
|
|
else
|
||
|
|
$str .= $this->backquote . $this->db->escape($field->column) . $this->backquote . ',';
|
||
|
|
}
|
||
|
|
|
||
|
|
$str = substr($str, 0, -1);
|
||
|
|
$str .= ')' . ",\n";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$str = substr($str, 0, -2);
|
||
|
|
$this->_sql = $str;
|
||
|
|
|
||
|
|
return $str;
|
||
|
|
}
|
||
|
|
|
||
|
|
function matchIndexes($new, $old) {
|
||
|
|
if (count($new) != count($old))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
foreach($new as $key => $info) {
|
||
|
|
if (!isset($old[$key]) || $info != $old[$key])
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach($old as $key => $info) {
|
||
|
|
if (!isset($new[$key]) || $info != $new[$key])
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
function isIndexMatch($old, $new) {
|
||
|
|
sort($old);
|
||
|
|
sort($new);
|
||
|
|
|
||
|
|
return ($old === $new);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
?>
|