sf-static/sql/lib/tableeditor.php
2023-02-28 19:21:07 +01:00

482 lines
No EOL
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);
}
}
?>