2 /* vim: set expandtab sw=4 ts=4 sts=4: */
4 * Holds the PMA_Table class
8 if (! defined('PHPMYADMIN')) {
13 * Handles everything related to tables
15 * @todo make use of PMA_Message and PMA_Error
21 * UI preferences properties
23 const PROP_SORTED_COLUMN
= 'sorted_col';
24 const PROP_COLUMN_ORDER
= 'col_order';
25 const PROP_COLUMN_VISIB
= 'col_visib';
27 static public $cache = array();
30 * @var string engine (innodb, myisam, bdb, ...)
35 * @var string type (view, base table, system view)
42 var $settings = array();
45 * @var array UI preferences
50 * @var array errors occurred
52 var $errors = array();
57 var $messages = array();
60 * @var string table name
62 protected $_name = '';
65 * @var string database name
67 protected $_db_name = '';
70 * @var PMA_DatabaseInterface
77 * @param string $table_name table name
78 * @param string $db_name database name
79 * @param PMA_DatabaseInterface $dbi database interface for the table
81 function __construct($table_name, $db_name, PMA_DatabaseInterface
$dbi = null)
84 $dbi = $GLOBALS['dbi'];
87 $this->setName($table_name);
88 $this->setDbName($db_name);
94 * @see PMA_Table::getName()
95 * @return string table name
99 return $this->getName();
103 * return the last error
105 * @return string the last error
107 function getLastError()
109 return end($this->errors
);
113 * return the last message
115 * @return string the last message
117 function getLastMessage()
119 return end($this->messages
);
125 * @param string $table_name new table name
129 function setName($table_name)
131 $this->_name
= $table_name;
137 * @param boolean $backquoted whether to quote name with backticks ``
139 * @return string table name
141 function getName($backquoted = false)
144 return PMA_Util
::backquote($this->_name
);
150 * sets database name for this table
152 * @param string $db_name database name
156 function setDbName($db_name)
158 $this->_db_name
= $db_name;
162 * returns database name for this table
164 * @param boolean $backquoted whether to quote name with backticks ``
166 * @return string database name for this table
168 function getDbName($backquoted = false)
171 return PMA_Util
::backquote($this->_db_name
);
173 return $this->_db_name
;
177 * returns full name for table, including database name
179 * @param boolean $backquoted whether to quote name with backticks ``
183 function getFullName($backquoted = false)
185 return $this->getDbName($backquoted) . '.'
186 . $this->getName($backquoted);
190 * returns whether the table is actually a view
192 * @param string $db database
193 * @param string $table table
195 * @return boolean whether the given is a view
197 static public function isView($db = null, $table = null)
199 if (empty($db) ||
empty($table)) {
203 // use cached data or load information with SHOW command
204 if (isset(PMA_Table
::$cache[$db][$table])
205 ||
$GLOBALS['cfg']['Server']['DisableIS']
207 $type = PMA_Table
::sGetStatusInfo($db, $table, 'TABLE_TYPE');
208 return $type == 'VIEW' ||
$type == 'SYSTEM VIEW';
211 // information_schema tables are 'SYSTEM VIEW's
212 if ($db == 'information_schema') {
216 // query information_schema
217 $result = $GLOBALS['dbi']->fetchResult(
219 FROM information_schema.VIEWS
220 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($db) . "'
221 AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($table) . "'"
223 return $result ?
true : false;
227 * Returns whether the table is actually an updatable view
229 * @param string $db database
230 * @param string $table table
232 * @return boolean whether the given is an updatable view
234 static public function isUpdatableView($db = null, $table = null)
236 if (empty($db) ||
empty($table)) {
240 $result = $GLOBALS['dbi']->fetchResult(
242 FROM information_schema.VIEWS
243 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($db) . "'
244 AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($table) . "'
245 AND IS_UPDATABLE = 'YES'"
247 return $result ?
true : false;
251 * Returns the analysis of 'SHOW CREATE TABLE' query for the table.
252 * In case of a view, the values are taken from the information_schema.
254 * @param string $db database
255 * @param string $table table
257 * @return array analysis of 'SHOW CREATE TABLE' query for the table
259 static public function analyzeStructure($db = null, $table = null)
261 if (empty($db) ||
empty($table)) {
265 $analyzed_sql = array();
266 if (self
::isView($db, $table)) {
267 // For a view, 'SHOW CREATE TABLE' returns the definition,
268 // but the structure of the view. So, we try to mock
269 // the result of analyzing 'SHOW CREATE TABLE' query.
270 $analyzed_sql[0] = array();
271 $analyzed_sql[0]['create_table_fields'] = array();
273 $results = $GLOBALS['dbi']->fetchResult(
274 "SELECT COLUMN_NAME, DATA_TYPE
275 FROM information_schema.COLUMNS
276 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($db) . "'
277 AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($table) . "'"
280 foreach ($results as $result) {
281 $analyzed_sql[0]['create_table_fields'][$result['COLUMN_NAME']]
283 'type' => /*overload*/mb_strtoupper($result['DATA_TYPE'])
287 $show_create_table = $GLOBALS['dbi']->fetchValue(
289 . PMA_Util
::backquote($db)
290 . '.' . PMA_Util
::backquote($table),
294 $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
296 return $analyzed_sql;
300 * sets given $value for given $param
302 * @param string $param name
303 * @param mixed $value value
307 function set($param, $value)
309 $this->settings
[$param] = $value;
313 * returns value for given setting/param
315 * @param string $param name for value to return
317 * @return mixed value for $param
321 if (isset($this->settings
[$param])) {
322 return $this->settings
[$param];
329 * Checks if this is a merge table
331 * If the ENGINE of the table is MERGE or MRG_MYISAM (alias),
332 * this is a merge table.
334 * @param string $db the database name
335 * @param string $table the table name
337 * @return boolean true if it is a merge table
339 static public function isMerge($db = null, $table = null)
342 // if called static, with parameters
343 if (! empty($db) && ! empty($table)) {
344 $engine = PMA_Table
::sGetStatusInfo(
345 $db, $table, 'ENGINE', null, true
349 // did we get engine?
350 if (empty($engine)) {
354 // any of known merge engines?
356 /*overload*/mb_strtoupper($engine),
357 array('MERGE', 'MRG_MYISAM')
362 * Returns full table status info, or specific if $info provided
363 * this info is collected from information_schema
365 * @param string $db database name
366 * @param string $table table name
367 * @param string $info specific information to be fetched
368 * @param boolean $force_read read new rather than serving from cache
369 * @param boolean $disable_error if true, disables error message
371 * @todo DatabaseInterface::getTablesFull needs to be merged
372 * somehow into this class or at least better documented
376 static public function sGetStatusInfo($db, $table, $info = null,
377 $force_read = false, $disable_error = false
379 if (! empty($_SESSION['is_multi_query'])) {
380 $disable_error = true;
383 // sometimes there is only one entry (ExactRows) so
384 // we have to get the table's details
385 if (! isset(PMA_Table
::$cache[$db][$table])
387 ||
count(PMA_Table
::$cache[$db][$table]) == 1
389 $GLOBALS['dbi']->getTablesFull($db, $table);
392 if (! isset(PMA_Table
::$cache[$db][$table])) {
393 // happens when we enter the table creation dialog
394 // or when we really did not get any status info, for example
395 // when $table == 'TABLE_NAMES' after the user tried SHOW TABLES
399 if (null === $info) {
400 return PMA_Table
::$cache[$db][$table];
403 // array_key_exists allows for null values
404 if (!array_key_exists($info, PMA_Table
::$cache[$db][$table])) {
405 if (! $disable_error) {
407 __('Unknown table status:') . ' ' . $info,
414 return PMA_Table
::$cache[$db][$table][$info];
418 * generates column specification for ALTER or CREATE TABLE syntax
420 * @param string $name name
421 * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
422 * @param string $length length ('2', '5,2', '', ...)
423 * @param string $attribute attribute
424 * @param string $collation collation
425 * @param bool|string $null with 'NULL' or 'NOT NULL'
426 * @param string $default_type whether default is CURRENT_TIMESTAMP,
427 * NULL, NONE, USER_DEFINED
428 * @param string $default_value default value for USER_DEFINED
430 * @param string $extra 'AUTO_INCREMENT'
431 * @param string $comment field comment
432 * @param string $move_to new position for column
434 * @todo move into class PMA_Column
435 * @todo on the interface, some js to clear the default value when the
436 * default current_timestamp is checked
438 * @return string field specification
440 static function generateFieldSpec($name, $type, $length = '',
441 $attribute = '', $collation = '', $null = false,
442 $default_type = 'USER_DEFINED', $default_value = '', $extra = '',
443 $comment = '', $move_to = ''
445 $is_timestamp = /*overload*/mb_strpos(
446 /*overload*/mb_strtoupper($type),
450 $query = PMA_Util
::backquote($name) . ' ' . $type;
452 // allow the possibility of a length for TIME, DATETIME and TIMESTAMP
453 // (will work on MySQL >= 5.6.4)
455 // MySQL permits a non-standard syntax for FLOAT and DOUBLE,
456 // see http://dev.mysql.com/doc/refman/5.5/en/floating-point-types.html
458 $pattern = '@^(DATE|TINYBLOB|TINYTEXT|BLOB|TEXT|'
459 . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN|UUID)$@i';
460 if ($length != '' && ! preg_match($pattern, $type)) {
461 $query .= '(' . $length . ')';
464 if ($attribute != '') {
465 $query .= ' ' . $attribute;
468 $matches = preg_match(
469 '@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i',
472 if (! empty($collation) && $collation != 'NULL' && $matches) {
473 $query .= PMA_generateCharsetQueryPart($collation);
476 if ($null !== false) {
477 if ($null == 'NULL') {
480 $query .= ' NOT NULL';
484 switch ($default_type) {
485 case 'USER_DEFINED' :
486 if ($is_timestamp && $default_value === '0') {
487 // a TIMESTAMP does not accept DEFAULT '0'
488 // but DEFAULT 0 works
489 $query .= ' DEFAULT 0';
490 } elseif ($type == 'BIT') {
491 $query .= ' DEFAULT b\''
492 . preg_replace('/[^01]/', '0', $default_value)
494 } elseif ($type == 'BOOLEAN') {
495 if (preg_match('/^1|T|TRUE|YES$/i', $default_value)) {
496 $query .= ' DEFAULT TRUE';
497 } elseif (preg_match('/^0|F|FALSE|NO$/i', $default_value)) {
498 $query .= ' DEFAULT FALSE';
500 // Invalid BOOLEAN value
501 $query .= ' DEFAULT \''
502 . PMA_Util
::sqlAddSlashes($default_value) . '\'';
504 } elseif ($type == 'BINARY' ||
$type == 'VARBINARY') {
505 $query .= ' DEFAULT 0x' . $default_value;
507 $query .= ' DEFAULT \''
508 . PMA_Util
::sqlAddSlashes($default_value) . '\'';
512 // If user uncheck null checkbox and not change default value null,
513 // default value will be ignored.
514 if ($null !== false && $null !== 'NULL') {
517 // else fall-through intended, no break here
518 case 'CURRENT_TIMESTAMP' :
519 $query .= ' DEFAULT ' . $default_type;
526 if (!empty($extra)) {
527 $query .= ' ' . $extra;
529 if (!empty($comment)) {
530 $query .= " COMMENT '" . PMA_Util
::sqlAddSlashes($comment) . "'";
534 if ($move_to == '-first') { // dash can't appear as part of column name
536 } elseif ($move_to != '') {
537 $query .= ' AFTER ' . PMA_Util
::backquote($move_to);
543 * Counts and returns (or displays) the number of records in a table
545 * @param string $db the current database name
546 * @param string $table the current table name
547 * @param bool $force_exact whether to force an exact count
548 * @param bool $is_view whether the table is a view
550 * @return mixed the number of records if "retain" param is true,
553 static public function countRecords($db, $table, $force_exact = false,
556 if (isset(PMA_Table
::$cache[$db][$table]['ExactRows'])) {
557 $row_count = PMA_Table
::$cache[$db][$table]['ExactRows'];
561 if (null === $is_view) {
562 $is_view = PMA_Table
::isView($db, $table);
565 if (! $force_exact) {
566 if (! isset(PMA_Table
::$cache[$db][$table]['Rows']) && ! $is_view) {
567 $tmp_tables = $GLOBALS['dbi']->getTablesFull($db, $table);
568 if (isset($tmp_tables[$table])) {
569 PMA_Table
::$cache[$db][$table] = $tmp_tables[$table];
572 if (isset(PMA_Table
::$cache[$db][$table]['Rows'])) {
573 $row_count = PMA_Table
::$cache[$db][$table]['Rows'];
579 // for a VIEW, $row_count is always false at this point
580 if (false === $row_count
581 ||
$row_count < $GLOBALS['cfg']['MaxExactCount']
583 // Make an exception for views in I_S and D_D schema in
584 // Drizzle, as these map to in-memory data and should execute
587 ||
(PMA_DRIZZLE
&& $GLOBALS['dbi']->isSystemSchema($db))
589 $row_count = $GLOBALS['dbi']->fetchValue(
590 'SELECT COUNT(*) FROM ' . PMA_Util
::backquote($db) . '.'
591 . PMA_Util
::backquote($table)
594 // For complex views, even trying to get a partial record
595 // count could bring down a server, so we offer an
596 // alternative: setting MaxExactCountViews to 0 will bypass
597 // completely the record counting for views
599 if ($GLOBALS['cfg']['MaxExactCountViews'] == 0) {
602 // Counting all rows of a VIEW could be too long,
603 // so use a LIMIT clause.
604 // Use try_query because it can fail (when a VIEW is
605 // based on a table that no longer exists)
606 $result = $GLOBALS['dbi']->tryQuery(
607 'SELECT 1 FROM ' . PMA_Util
::backquote($db) . '.'
608 . PMA_Util
::backquote($table) . ' LIMIT '
609 . $GLOBALS['cfg']['MaxExactCountViews'],
611 PMA_DatabaseInterface
::QUERY_STORE
613 if (!$GLOBALS['dbi']->getError()) {
614 $row_count = $GLOBALS['dbi']->numRows($result);
615 $GLOBALS['dbi']->freeResult($result);
620 PMA_Table
::$cache[$db][$table]['ExactRows'] = $row_count;
626 } // end of the 'PMA_Table::countRecords()' function
629 * Generates column specification for ALTER syntax
631 * @param string $oldcol old column name
632 * @param string $newcol new column name
633 * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
634 * @param string $length length ('2', '5,2', '', ...)
635 * @param string $attribute attribute
636 * @param string $collation collation
637 * @param bool|string $null with 'NULL' or 'NOT NULL'
638 * @param string $default_type whether default is CURRENT_TIMESTAMP,
639 * NULL, NONE, USER_DEFINED
640 * @param string $default_value default value for USER_DEFINED default
642 * @param string $extra 'AUTO_INCREMENT'
643 * @param string $comment field comment
644 * @param string $move_to new position for column
646 * @see PMA_Table::generateFieldSpec()
648 * @return string field specification
650 static public function generateAlter($oldcol, $newcol, $type, $length,
651 $attribute, $collation, $null, $default_type, $default_value,
652 $extra, $comment, $move_to
654 return PMA_Util
::backquote($oldcol) . ' '
655 . PMA_Table
::generateFieldSpec(
656 $newcol, $type, $length, $attribute,
657 $collation, $null, $default_type, $default_value, $extra,
663 * Inserts existing entries in a PMA_* table by reading a value from an old
666 * @param string $work The array index, which Relation feature to
667 * check ('relwork', 'commwork', ...)
668 * @param string $pma_table The array index, which PMA-table to update
669 * ('bookmark', 'relation', ...)
670 * @param array $get_fields Which fields will be SELECT'ed from the old entry
671 * @param array $where_fields Which fields will be used for the WHERE query
672 * (array('FIELDNAME' => 'FIELDVALUE'))
673 * @param array $new_fields Which fields will be used as new VALUES.
674 * These are the important keys which differ
676 * (array('FIELDNAME' => 'NEW FIELDVALUE'))
678 * @global relation variable
682 static public function duplicateInfo($work, $pma_table, $get_fields,
683 $where_fields, $new_fields
687 if (isset($GLOBALS['cfgRelation']) && $GLOBALS['cfgRelation'][$work]) {
688 $select_parts = array();
689 $row_fields = array();
690 foreach ($get_fields as $get_field) {
691 $select_parts[] = PMA_Util
::backquote($get_field);
692 $row_fields[$get_field] = 'cc';
695 $where_parts = array();
696 foreach ($where_fields as $_where => $_value) {
697 $where_parts[] = PMA_Util
::backquote($_where) . ' = \''
698 . PMA_Util
::sqlAddSlashes($_value) . '\'';
701 $new_parts = array();
702 $new_value_parts = array();
703 foreach ($new_fields as $_where => $_value) {
704 $new_parts[] = PMA_Util
::backquote($_where);
705 $new_value_parts[] = PMA_Util
::sqlAddSlashes($_value);
708 $table_copy_query = '
709 SELECT ' . implode(', ', $select_parts) . '
710 FROM ' . PMA_Util
::backquote($GLOBALS['cfgRelation']['db']) . '.'
711 . PMA_Util
::backquote($GLOBALS['cfgRelation'][$pma_table]) . '
712 WHERE ' . implode(' AND ', $where_parts);
714 // must use PMA_DatabaseInterface::QUERY_STORE here, since we execute
715 // another query inside the loop
716 $table_copy_rs = PMA_queryAsControlUser(
717 $table_copy_query, true, PMA_DatabaseInterface
::QUERY_STORE
720 while ($table_copy_row = @$GLOBALS['dbi']->fetchAssoc($table_copy_rs)) {
721 $value_parts = array();
722 foreach ($table_copy_row as $_key => $_val) {
723 if (isset($row_fields[$_key]) && $row_fields[$_key] == 'cc') {
724 $value_parts[] = PMA_Util
::sqlAddSlashes($_val);
728 $new_table_query = 'INSERT IGNORE INTO '
729 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
731 . PMA_Util
::backquote($GLOBALS['cfgRelation'][$pma_table])
732 . ' (' . implode(', ', $select_parts)
733 . ', ' . implode(', ', $new_parts)
735 . implode('\', \'', $value_parts) . '\', \''
736 . implode('\', \'', $new_value_parts) . '\')';
738 PMA_queryAsControlUser($new_table_query);
739 $last_id = $GLOBALS['dbi']->insertId();
742 $GLOBALS['dbi']->freeResult($table_copy_rs);
748 } // end of 'PMA_Table::duplicateInfo()' function
751 * Copies or renames table
753 * @param string $source_db source database
754 * @param string $source_table source table
755 * @param string $target_db target database
756 * @param string $target_table target table
757 * @param string $what what to be moved or copied (data, dataonly)
758 * @param bool $move whether to move
759 * @param string $mode mode
761 * @return bool true if success, false otherwise
763 static public function moveCopy($source_db, $source_table, $target_db,
764 $target_table, $what, $move, $mode
768 /* Try moving table directly */
769 if ($move && $what == 'data') {
770 $tbl = new PMA_Table($source_table, $source_db);
771 $result = $tbl->rename($target_table, $target_db);
773 $GLOBALS['message'] = $tbl->getLastMessage();
778 // set export settings we need
779 $GLOBALS['sql_backquotes'] = 1;
780 $GLOBALS['asfile'] = 1;
782 // Ensure the target is valid
783 if (! $GLOBALS['pma']->databases
->exists($source_db, $target_db)) {
784 if (! $GLOBALS['pma']->databases
->exists($source_db)) {
785 $GLOBALS['message'] = PMA_Message
::rawError(
787 __('Source database `%s` was not found!'),
788 htmlspecialchars($source_db)
792 if (! $GLOBALS['pma']->databases
->exists($target_db)) {
793 $GLOBALS['message'] = PMA_Message
::rawError(
795 __('Target database `%s` was not found!'),
796 htmlspecialchars($target_db)
803 $source = PMA_Util
::backquote($source_db)
804 . '.' . PMA_Util
::backquote($source_table);
805 if (! isset($target_db) ||
! /*overload*/mb_strlen($target_db)) {
806 $target_db = $source_db;
809 // Doing a select_db could avoid some problems with replicated databases,
810 // when moving table from replicated one to not replicated one
811 $GLOBALS['dbi']->selectDb($target_db);
813 $target = PMA_Util
::backquote($target_db)
814 . '.' . PMA_Util
::backquote($target_table);
816 // do not create the table if dataonly
817 if ($what != 'dataonly') {
818 include_once "libraries/plugin_interface.lib.php";
819 // get Export SQL instance
820 /* @var $export_sql_plugin ExportSql */
821 $export_sql_plugin = PMA_getPlugin(
824 'libraries/plugins/export/',
826 'export_type' => 'table',
827 'single_table' => false,
831 $no_constraints_comments = true;
832 $GLOBALS['sql_constraints_query'] = '';
833 // set the value of global sql_auto_increment variable
834 if (isset($_POST['sql_auto_increment'])) {
835 $GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment'];
838 $sql_structure = $export_sql_plugin->getTableDef(
839 $source_db, $source_table, "\n", $err_url, false, false
841 unset($no_constraints_comments);
842 $parsed_sql = PMA_SQP_parse($sql_structure);
843 $analyzed_sql = PMA_SQP_analyze($parsed_sql);
845 if (empty($analyzed_sql[0]['create_table_fields'])) {
846 // this is not a CREATE TABLE, so find the first VIEW
847 $target_for_view = PMA_Util
::backquote($target_db);
849 if ($parsed_sql[$i]['type'] == 'alpha_reservedWord'
850 && $parsed_sql[$i]['data'] == 'VIEW'
857 unset($analyzed_sql);
859 $table_delimiter = 'quote_backtick';
861 $server_sql_mode = $GLOBALS['dbi']->fetchValue(
862 "SHOW VARIABLES LIKE 'sql_mode'",
866 // ANSI_QUOTES might be a subset of sql_mode, for example
867 // REAL_AS_FLOAT,PIPES_AS_CONCAT,ANSI_QUOTES,IGNORE_SPACE,ANSI
868 if (false !== /*overload*/mb_strpos($server_sql_mode, 'ANSI_QUOTES')
870 $table_delimiter = 'quote_double';
872 $table_delimiter = 'quote_backtick';
874 unset($server_sql_mode);
877 /* Find table name in query and replace it */
878 while ($parsed_sql[$i]['type'] != $table_delimiter) {
882 /* no need to backquote() */
883 if (isset($target_for_view)) {
884 // this a view definition; we just found the first db name
885 // that follows DEFINER VIEW
886 // so change it for the new db name
887 $parsed_sql[$i]['data'] = $target_for_view;
888 // then we have to find all references to the source db
889 // and change them to the target db, ensuring we stay into
890 // the $parsed_sql limits
891 $last = $parsed_sql['len'] - 1;
892 $backquoted_source_db = PMA_Util
::backquote($source_db);
893 for (++
$i; $i <= $last; $i++
) {
894 if ($parsed_sql[$i]['type'] == $table_delimiter
895 && $parsed_sql[$i]['data'] == $backquoted_source_db
896 && $parsed_sql[$i - 1]['type'] != 'punct_qualifier'
898 $parsed_sql[$i]['data'] = $target_for_view;
901 unset($last,$backquoted_source_db);
903 $parsed_sql[$i]['data'] = $target;
906 /* Generate query back */
907 $sql_structure = PMA_SQP_format($parsed_sql, 'query_only');
908 // If table exists, and 'add drop table' is selected: Drop it!
909 if (isset($_REQUEST['drop_if_exists'])
910 && $_REQUEST['drop_if_exists'] == 'true'
912 if (PMA_Table
::isView($target_db, $target_table)) {
913 $drop_query = 'DROP VIEW';
915 $drop_query = 'DROP TABLE';
917 $drop_query .= ' IF EXISTS '
918 . PMA_Util
::backquote($target_db) . '.'
919 . PMA_Util
::backquote($target_table);
920 $GLOBALS['dbi']->query($drop_query);
922 $GLOBALS['sql_query'] .= "\n" . $drop_query . ';';
924 // If an existing table gets deleted, maintain any
925 // entries for the PMA_* tables
926 $maintain_relations = true;
929 @$GLOBALS['dbi']->query($sql_structure);
930 $GLOBALS['sql_query'] .= "\n" . $sql_structure . ';';
932 if (($move ||
isset($GLOBALS['add_constraints']))
933 && !empty($GLOBALS['sql_constraints_query'])
935 $parsed_sql = PMA_SQP_parse($GLOBALS['sql_constraints_query']);
938 // find the first $table_delimiter, it must be the source
940 while ($parsed_sql[$i]['type'] != $table_delimiter) {
942 // maybe someday we should guard against going over limit
943 //if ($i == $parsed_sql['len']) {
948 // replace it by the target table name, no need
950 $parsed_sql[$i]['data'] = $target;
952 // now we must remove all $table_delimiter that follow a
953 // CONSTRAINT keyword, because a constraint name must be
956 $cnt = $parsed_sql['len'] - 1;
958 for ($j = $i; $j < $cnt; $j++
) {
959 $dataUpper = /*overload*/mb_strtoupper($parsed_sql[$j]['data']);
960 if ($parsed_sql[$j]['type'] == 'alpha_reservedWord'
961 && $dataUpper == 'CONSTRAINT'
963 if ($parsed_sql[$j+
1]['type'] == $table_delimiter) {
964 $parsed_sql[$j+
1]['data'] = '';
969 // Generate query back
970 $GLOBALS['sql_constraints_query'] = PMA_SQP_format(
971 $parsed_sql, 'query_only'
973 if ($mode == 'one_table') {
974 $GLOBALS['dbi']->query($GLOBALS['sql_constraints_query']);
976 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query'];
977 if ($mode == 'one_table') {
978 unset($GLOBALS['sql_constraints_query']);
982 // add indexes to the table
983 if (!empty($GLOBALS['sql_indexes'])) {
985 $index_queries = array();
986 $sql_indexes = $GLOBALS['sql_indexes'];
987 $GLOBALS['sql_indexes'] = '';
989 $parsed_sql = PMA_SQP_parse($sql_indexes);
990 $cnt = $parsed_sql['len'] - 1;
993 for ($j = 0; $j < $cnt; $j++
) {
994 if ($parsed_sql[$j]['type'] == 'punct_queryend') {
995 $index_queries[] = substr(
996 $sql_indexes, $k, $parsed_sql[$j]['pos'] - $k
998 $k = $parsed_sql[$j]['pos'];
1002 foreach ($index_queries as $index_query) {
1004 $parsed_sql = PMA_SQP_parse($index_query);
1007 while ($parsed_sql[$i]['type'] != $table_delimiter) {
1011 $parsed_sql[$i]['data'] = $target;
1013 $cnt = $parsed_sql['len'] - 1;
1015 for ($j = $i; $j < $cnt; $j++
) {
1016 $dataUpper = /*overload*/mb_strtoupper($parsed_sql[$j]['data']);
1017 if ($parsed_sql[$j]['type'] == 'alpha_reservedWord'
1018 && $dataUpper == 'CONSTRAINT'
1020 if ($parsed_sql[$j+
1]['type'] == $table_delimiter) {
1021 $parsed_sql[$j+
1]['data'] = '';
1026 $sql_index = PMA_SQP_format($parsed_sql, 'query_only');
1027 if ($mode == 'one_table' ||
$mode == 'db_copy') {
1028 $GLOBALS['dbi']->query($sql_index);
1031 $GLOBALS['sql_indexes'] .= $sql_index;
1034 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_indexes'];
1035 if ($mode == 'one_table' ||
$mode == 'db_copy') {
1036 unset($GLOBALS['sql_indexes']);
1042 * add AUTO_INCREMENT to the table
1044 * @todo refactor with similar code above
1046 if (! empty($GLOBALS['sql_auto_increments'])) {
1047 if ($mode == 'one_table' ||
$mode == 'db_copy') {
1048 $parsed_sql = PMA_SQP_parse($GLOBALS['sql_auto_increments']);
1051 // find the first $table_delimiter, it must be the source
1053 while ($parsed_sql[$i]['type'] != $table_delimiter) {
1057 // replace it by the target table name, no need
1059 $parsed_sql[$i]['data'] = $target;
1061 // Generate query back
1062 $GLOBALS['sql_auto_increments'] = PMA_SQP_format(
1063 $parsed_sql, 'query_only'
1065 $GLOBALS['dbi']->query($GLOBALS['sql_auto_increments']);
1066 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_auto_increments'];
1067 unset($GLOBALS['sql_auto_increments']);
1072 $GLOBALS['sql_query'] = '';
1075 // Copy the data unless this is a VIEW
1076 if (($what == 'data' ||
$what == 'dataonly')
1077 && ! PMA_Table
::isView($target_db, $target_table)
1079 $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
1080 $GLOBALS['dbi']->query($sql_set_mode);
1081 $GLOBALS['sql_query'] .= "\n\n" . $sql_set_mode . ';';
1083 $sql_insert_data = 'INSERT INTO ' . $target
1084 . ' SELECT * FROM ' . $source;
1085 $GLOBALS['dbi']->query($sql_insert_data);
1086 $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';';
1089 $GLOBALS['cfgRelation'] = PMA_getRelationsParam();
1091 // Drops old table if the user has requested to move it
1094 // This could avoid some problems with replicated databases, when
1095 // moving table from replicated one to not replicated one
1096 $GLOBALS['dbi']->selectDb($source_db);
1098 if (PMA_Table
::isView($source_db, $source_table)) {
1099 $sql_drop_query = 'DROP VIEW';
1101 $sql_drop_query = 'DROP TABLE';
1103 $sql_drop_query .= ' ' . $source;
1104 $GLOBALS['dbi']->query($sql_drop_query);
1106 // Renable table in configuration storage
1107 PMA_REL_renameTable(
1108 $source_db, $target_db,
1109 $source_table, $target_table
1112 $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';';
1116 // Create new entries as duplicates from old PMA DBs
1117 if ($what != 'dataonly' && ! isset($maintain_relations)) {
1118 if ($GLOBALS['cfgRelation']['commwork']) {
1119 // Get all comments and MIME-Types for current table
1120 $comments_copy_rs = PMA_queryAsControlUser(
1121 'SELECT column_name, comment'
1122 . ($GLOBALS['cfgRelation']['mimework']
1123 ?
', mimetype, transformation, transformation_options'
1126 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1128 . PMA_Util
::backquote($GLOBALS['cfgRelation']['column_info'])
1131 . PMA_Util
::sqlAddSlashes($source_db) . '\''
1133 . ' table_name = \''
1134 . PMA_Util
::sqlAddSlashes($source_table) . '\''
1137 // Write every comment as new copied entry. [MIME]
1138 while ($comments_copy_row
1139 = $GLOBALS['dbi']->fetchAssoc($comments_copy_rs)) {
1140 $new_comment_query = 'REPLACE INTO '
1141 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1143 . PMA_Util
::backquote(
1144 $GLOBALS['cfgRelation']['column_info']
1146 . ' (db_name, table_name, column_name, comment'
1147 . ($GLOBALS['cfgRelation']['mimework']
1148 ?
', mimetype, transformation, transformation_options'
1152 . '\'' . PMA_Util
::sqlAddSlashes($target_db)
1154 . '\'' . PMA_Util
::sqlAddSlashes($target_table)
1157 . PMA_Util
::sqlAddSlashes(
1158 $comments_copy_row['column_name']
1161 . ($GLOBALS['cfgRelation']['mimework']
1163 . PMA_Util
::sqlAddSlashes(
1164 $comments_copy_row['comment']
1166 . '\'' . PMA_Util
::sqlAddSlashes(
1167 $comments_copy_row['mimetype']
1169 . '\'' . PMA_Util
::sqlAddSlashes(
1170 $comments_copy_row['transformation']
1172 . '\'' . PMA_Util
::sqlAddSlashes(
1173 $comments_copy_row['transformation_options']
1177 PMA_queryAsControlUser($new_comment_query);
1179 $GLOBALS['dbi']->freeResult($comments_copy_rs);
1180 unset($comments_copy_rs);
1183 // duplicating the bookmarks must not be done here, but
1186 $get_fields = array('display_field');
1187 $where_fields = array(
1188 'db_name' => $source_db,
1189 'table_name' => $source_table
1191 $new_fields = array(
1192 'db_name' => $target_db,
1193 'table_name' => $target_table
1195 PMA_Table
::duplicateInfo(
1204 * @todo revise this code when we support cross-db relations
1206 $get_fields = array(
1211 $where_fields = array(
1212 'master_db' => $source_db,
1213 'master_table' => $source_table
1215 $new_fields = array(
1216 'master_db' => $target_db,
1217 'foreign_db' => $target_db,
1218 'master_table' => $target_table
1220 PMA_Table
::duplicateInfo(
1228 $get_fields = array(
1233 $where_fields = array(
1234 'foreign_db' => $source_db,
1235 'foreign_table' => $source_table
1237 $new_fields = array(
1238 'master_db' => $target_db,
1239 'foreign_db' => $target_db,
1240 'foreign_table' => $target_table
1242 PMA_Table
::duplicateInfo(
1251 * @todo Can't get duplicating PDFs the right way. The
1252 * page numbers always get screwed up independently from
1253 * duplication because the numbers do not seem to be stored on a
1254 * per-database basis. Would the author of pdf support please
1255 * have a look at it?
1257 $get_fields = array('page_descr');
1258 $where_fields = array('db_name' => $source_db);
1259 $new_fields = array('db_name' => $target_db);
1260 $last_id = PMA_Table::duplicateInfo(
1268 if (isset($last_id) && $last_id >= 0) {
1269 $get_fields = array('x', 'y');
1270 $where_fields = array(
1271 'db_name' => $source_db,
1272 'table_name' => $source_table
1274 $new_fields = array(
1275 'db_name' => $target_db,
1276 'table_name' => $target_table,
1277 'pdf_page_number' => $last_id
1279 PMA_Table::duplicateInfo(
1294 * checks if given name is a valid table name,
1295 * currently if not empty, trailing spaces, '.', '/' and '\'
1297 * @param string $table_name name to check
1299 * @todo add check for valid chars in filename on current system/os
1300 * @see http://dev.mysql.com/doc/refman/5.0/en/legal-names.html
1302 * @return boolean whether the string is valid or not
1304 static function isValidName($table_name)
1306 if ($table_name !== trim($table_name)) {
1311 if (! /*overload*/mb_strlen($table_name)) {
1316 if (preg_match('/[.\/\\\\]+/i', $table_name)) {
1317 // illegal char . / \
1327 * @param string $new_name new table name
1328 * @param string $new_db new database name
1330 * @return bool success
1332 function rename($new_name, $new_db = null)
1334 if (null !== $new_db && $new_db !== $this->getDbName()) {
1335 // Ensure the target is valid
1336 if (! $GLOBALS['pma']->databases
->exists($new_db)) {
1337 $this->errors
[] = __('Invalid database:') . ' ' . $new_db;
1341 $new_db = $this->getDbName();
1344 $new_table = new PMA_Table($new_name, $new_db);
1346 if ($this->getFullName() === $new_table->getFullName()) {
1350 if (! PMA_Table
::isValidName($new_name)) {
1351 $this->errors
[] = __('Invalid table name:') . ' '
1352 . $new_table->getFullName();
1356 // If the table is moved to a different database drop its triggers first
1357 $triggers = $GLOBALS['dbi']->getTriggers(
1358 $this->getDbName(), $this->getName(), ''
1360 $handle_triggers = $this->getDbName() != $new_db && $triggers;
1361 if ($handle_triggers) {
1362 foreach ($triggers as $trigger) {
1363 $sql = 'DROP TRIGGER IF EXISTS '
1364 . PMA_Util
::backquote($this->getDbName())
1365 . '.' . PMA_Util
::backquote($trigger['name']) . ';';
1366 $GLOBALS['dbi']->query($sql);
1371 * tested also for a view, in MySQL 5.0.92, 5.1.55 and 5.5.13
1373 $GLOBALS['sql_query'] = '
1374 RENAME TABLE ' . $this->getFullName(true) . '
1375 TO ' . $new_table->getFullName(true) . ';';
1376 // I don't think a specific error message for views is necessary
1377 if (! $GLOBALS['dbi']->query($GLOBALS['sql_query'])) {
1378 // Restore triggers in the old database
1379 if ($handle_triggers) {
1380 $GLOBALS['dbi']->selectDb($this->getDbName());
1381 foreach ($triggers as $trigger) {
1382 $GLOBALS['dbi']->query($trigger['create']);
1385 $this->errors
[] = sprintf(
1386 __('Failed to rename table %1$s to %2$s!'),
1387 $this->getFullName(),
1388 $new_table->getFullName()
1393 $old_name = $this->getName();
1394 $old_db = $this->getDbName();
1395 $this->setName($new_name);
1396 $this->setDbName($new_db);
1398 // Renable table in configuration storage
1399 PMA_REL_renameTable(
1401 $old_name, $new_name
1404 $this->messages
[] = sprintf(
1405 __('Table %1$s has been renamed to %2$s.'),
1406 htmlspecialchars($old_name),
1407 htmlspecialchars($new_name)
1413 * Get all unique columns
1415 * returns an array with all columns with unique content, in fact these are
1416 * all columns being single indexed in PRIMARY or UNIQUE
1419 * - PRIMARY(id) // id
1420 * - UNIQUE(name) // name
1421 * - PRIMARY(fk_id1, fk_id2) // NONE
1422 * - UNIQUE(x,y) // NONE
1424 * @param bool $backquoted whether to quote name with backticks ``
1425 * @param bool $fullName whether to include full name of the table as a prefix
1429 public function getUniqueColumns($backquoted = true, $fullName = true)
1431 $sql = $GLOBALS['dbi']->getTableIndexesSql(
1436 $uniques = $GLOBALS['dbi']->fetchResult(
1438 array('Key_name', null),
1443 foreach ($uniques as $index) {
1444 if (count($index) > 1) {
1448 $possible_column = $this->getFullName($backquoted) . '.';
1450 $possible_column = '';
1453 $possible_column .= PMA_Util
::backquote($index[0]);
1455 $possible_column .= $index[0];
1457 // a column might have a primary and an unique index on it
1458 if (! in_array($possible_column, $return)) {
1459 $return[] = $possible_column;
1467 * Get all indexed columns
1469 * returns an array with all columns that make use of an index
1471 * e.g. index(col1, col2) would return col1, col2
1473 * @param bool $backquoted whether to quote name with backticks ``
1474 * @param bool $fullName whether to include full name of the table as a prefix
1478 public function getIndexedColumns($backquoted = true, $fullName = true)
1480 $sql = $GLOBALS['dbi']->getTableIndexesSql(
1485 $indexed = $GLOBALS['dbi']->fetchResult($sql, 'Column_name', 'Column_name');
1488 foreach ($indexed as $column) {
1489 $return[] = ($fullName ?
$this->getFullName($backquoted) . '.' : '')
1490 . ($backquoted ? PMA_Util
::backquote($column) : $column);
1499 * returns an array with all columns
1501 * @param bool $backquoted whether to quote name with backticks ``
1502 * @param bool $fullName whether to include full name of the table as a prefix
1506 public function getColumns($backquoted = true, $fullName = true)
1508 $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true);
1509 $indexed = $GLOBALS['dbi']->fetchResult($sql, 'Field', 'Field');
1512 foreach ($indexed as $column) {
1513 $return[] = ($fullName ?
$this->getFullName($backquoted) . '.' : '')
1514 . ($backquoted ? PMA_Util
::backquote($column) : $column);
1521 * Get meta info for fields in table
1525 public function getColumnsMeta()
1527 $move_columns_sql_query = sprintf(
1528 'SELECT * FROM %s.%s LIMIT 1',
1529 PMA_Util
::backquote($this->_db_name
),
1530 PMA_Util
::backquote($this->_name
)
1532 $move_columns_sql_result = $this->_dbi
->tryQuery($move_columns_sql_query);
1533 return $this->_dbi
->getFieldsMeta($move_columns_sql_result);
1537 * Return UI preferences for this table from phpMyAdmin database.
1541 protected function getUiPrefsFromDb()
1543 $cfgRelation = PMA_getRelationsParam();
1544 $pma_table = PMA_Util
::backquote($cfgRelation['db']) . "."
1545 . PMA_Util
::backquote($cfgRelation['table_uiprefs']);
1547 // Read from phpMyAdmin database
1548 $sql_query = " SELECT `prefs` FROM " . $pma_table
1549 . " WHERE `username` = '" . $GLOBALS['cfg']['Server']['user'] . "'"
1550 . " AND `db_name` = '" . PMA_Util
::sqlAddSlashes($this->_db_name
) . "'"
1551 . " AND `table_name` = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'";
1553 $row = $GLOBALS['dbi']->fetchArray(PMA_queryAsControlUser($sql_query));
1554 if (isset($row[0])) {
1555 return json_decode($row[0], true);
1562 * Save this table's UI preferences into phpMyAdmin database.
1564 * @return true|PMA_Message
1566 protected function saveUiPrefsToDb()
1568 $cfgRelation = PMA_getRelationsParam();
1569 $pma_table = PMA_Util
::backquote($cfgRelation['db']) . "."
1570 . PMA_Util
::backquote($cfgRelation['table_uiprefs']);
1572 $secureDbName = PMA_Util
::sqlAddSlashes($this->_db_name
);
1574 $username = $GLOBALS['cfg']['Server']['user'];
1575 $sql_query = " REPLACE INTO " . $pma_table
1576 . " (username, db_name, table_name, prefs) VALUES ('"
1577 . $username . "', '" . $secureDbName
1578 . "', '" . PMA_Util
::sqlAddSlashes($this->_name
) . "', '"
1579 . PMA_Util
::sqlAddSlashes(json_encode($this->uiprefs
)) . "')";
1581 $success = $GLOBALS['dbi']->tryQuery($sql_query, $GLOBALS['controllink']);
1584 $message = PMA_Message
::error(
1585 __('Could not save table UI preferences!')
1587 $message->addMessage('<br /><br />');
1588 $message->addMessage(
1589 PMA_Message
::rawError(
1590 $GLOBALS['dbi']->getError($GLOBALS['controllink'])
1596 // Remove some old rows in table_uiprefs if it exceeds the configured
1598 $sql_query = 'SELECT COUNT(*) FROM ' . $pma_table;
1599 $rows_count = $GLOBALS['dbi']->fetchValue($sql_query);
1600 $max_rows = $GLOBALS['cfg']['Server']['MaxTableUiprefs'];
1601 if ($rows_count > $max_rows) {
1602 $num_rows_to_delete = $rows_count - $max_rows;
1604 = ' DELETE FROM ' . $pma_table .
1605 ' ORDER BY last_update ASC' .
1606 ' LIMIT ' . $num_rows_to_delete;
1607 $success = $GLOBALS['dbi']->tryQuery(
1608 $sql_query, $GLOBALS['controllink']
1612 $message = PMA_Message
::error(
1614 __('Failed to cleanup table UI preferences (see $cfg[\'Servers\'][$i][\'MaxTableUiprefs\'] %s)'),
1615 PMA_Util
::showDocu('config', 'cfg_Servers_MaxTableUiprefs')
1618 $message->addMessage('<br /><br />');
1619 $message->addMessage(
1620 PMA_Message
::rawError(
1621 $GLOBALS['dbi']->getError($GLOBALS['controllink'])
1633 * Loads the UI preferences for this table.
1634 * If pmadb and table_uiprefs is set, it will load the UI preferences from
1635 * phpMyAdmin database.
1639 protected function loadUiPrefs()
1641 $cfgRelation = PMA_getRelationsParam();
1642 $server_id = $GLOBALS['server'];
1644 // set session variable if it's still undefined
1645 if (!isset($_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name
][$this->_name
])) {
1646 // check whether we can get from pmadb
1647 $_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name
]
1648 [$this->_name
] = $cfgRelation['uiprefswork']
1649 ?
$this->getUiPrefsFromDb()
1652 $this->uiprefs
=& $_SESSION['tmpval']['table_uiprefs'][$server_id]
1653 [$this->_db_name
][$this->_name
];
1657 * Get a property from UI preferences.
1658 * Return false if the property is not found.
1659 * Available property:
1660 * - PROP_SORTED_COLUMN
1661 * - PROP_COLUMN_ORDER
1662 * - PROP_COLUMN_VISIB
1664 * @param string $property property
1668 public function getUiProp($property)
1670 if (! isset($this->uiprefs
)) {
1671 $this->loadUiPrefs();
1673 // do checking based on property
1674 if ($property == self
::PROP_SORTED_COLUMN
) {
1675 if (isset($this->uiprefs
[$property])) {
1676 if (! isset($_REQUEST['discard_remembered_sort'])) {
1677 // check if the column name exists in this table
1678 $tmp = explode(' ', $this->uiprefs
[$property]);
1680 //remove backquoting from colname
1681 $colname = str_replace('`', '', $colname);
1682 //get the available column name without backquoting
1683 $avail_columns = $this->getColumns(false);
1685 foreach ($avail_columns as $each_col) {
1686 // check if $each_col ends with $colname
1690 /*overload*/mb_strlen($each_col)
1691 - /*overload*/mb_strlen($colname)
1694 return $this->uiprefs
[$property];
1698 // remove the property, since it no longer exists in database
1699 $this->removeUiProp(self
::PROP_SORTED_COLUMN
);
1704 } elseif ($property == self
::PROP_COLUMN_ORDER
1705 ||
$property == self
::PROP_COLUMN_VISIB
1707 if (! PMA_Table
::isView($this->_db_name
, $this->_name
)
1708 && isset($this->uiprefs
[$property])
1710 // check if the table has not been modified
1711 if (self
::sGetStatusInfo(
1713 $this->_name
, 'Create_time'
1714 ) == $this->uiprefs
['CREATE_TIME']) {
1715 return $this->uiprefs
[$property];
1717 // remove the property, since the table has been modified
1718 $this->removeUiProp(self
::PROP_COLUMN_ORDER
);
1725 // default behaviour for other property:
1726 return isset($this->uiprefs
[$property]) ?
$this->uiprefs
[$property] : false;
1730 * Set a property from UI preferences.
1731 * If pmadb and table_uiprefs is set, it will save the UI preferences to
1732 * phpMyAdmin database.
1733 * Available property:
1734 * - PROP_SORTED_COLUMN
1735 * - PROP_COLUMN_ORDER
1736 * - PROP_COLUMN_VISIB
1738 * @param string $property Property
1739 * @param mixed $value Value for the property
1740 * @param string $table_create_time Needed for PROP_COLUMN_ORDER
1741 * and PROP_COLUMN_VISIB
1743 * @return boolean|PMA_Message
1745 public function setUiProp($property, $value, $table_create_time = null)
1747 if (! isset($this->uiprefs
)) {
1748 $this->loadUiPrefs();
1750 // we want to save the create time if the property is PROP_COLUMN_ORDER
1751 if (! PMA_Table
::isView($this->_db_name
, $this->_name
)
1752 && ($property == self
::PROP_COLUMN_ORDER
1753 ||
$property == self
::PROP_COLUMN_VISIB
)
1755 $curr_create_time = self
::sGetStatusInfo(
1760 if (isset($table_create_time)
1761 && $table_create_time == $curr_create_time
1763 $this->uiprefs
['CREATE_TIME'] = $curr_create_time;
1765 // there is no $table_create_time, or
1766 // supplied $table_create_time is older than current create time,
1768 return PMA_Message
::error(
1770 __('Cannot save UI property "%s". The changes made will not be persistent after you refresh this page. Please check if the table structure has been changed.'),
1777 $this->uiprefs
[$property] = $value;
1779 // check if pmadb is set
1780 $cfgRelation = PMA_getRelationsParam();
1781 if ($cfgRelation['uiprefswork']) {
1782 return $this->saveUiprefsToDb();
1788 * Remove a property from UI preferences.
1790 * @param string $property the property
1792 * @return true|PMA_Message
1794 public function removeUiProp($property)
1796 if (! isset($this->uiprefs
)) {
1797 $this->loadUiPrefs();
1799 if (isset($this->uiprefs
[$property])) {
1800 unset($this->uiprefs
[$property]);
1802 // check if pmadb is set
1803 $cfgRelation = PMA_getRelationsParam();
1804 if ($cfgRelation['uiprefswork']) {
1805 return $this->saveUiprefsToDb();
1812 * Get all column names which are MySQL reserved words
1817 public function getReservedColumnNames()
1819 $columns = $this->getColumns(false);
1821 foreach ($columns as $column) {
1822 $temp = explode('.', $column);
1823 $column_name = $temp[2];
1824 if (PMA_SQP_isKeyWord($column_name)) {
1825 $return[] = $column_name;
1832 * Function to get the name and type of the columns of a table
1836 public function getNameAndTypeOfTheColumns()
1839 foreach ($this->_dbi
->getColumnsFull(
1840 $this->_db_name
, $this->_name
1842 if (preg_match('@^(set|enum)\((.+)\)$@i', $row['Type'], $tmp)) {
1843 $tmp[2] = /*overload*/
1845 preg_replace('@([^,])\'\'@', '\\1\\\'', ',' . $tmp[2]), 1
1847 $columns[$row['Field']] = $tmp[1] . '('
1848 . str_replace(',', ', ', $tmp[2]) . ')';
1850 $columns[$row['Field']] = $row['Type'];
1857 * Get index with index name
1859 * @param string $index Index name
1863 public function getIndex($index)
1865 return PMA_Index
::singleton($this->_db_name
, $this->_name
, $index);
1869 * Function to get the sql query for index creation or edit
1871 * @param PMA_Index $index current index
1872 * @param bool &$error whether error occurred or not
1876 public function getSqlQueryForIndexCreateOrEdit($index, &$error)
1878 // $sql_query is the one displayed in the query box
1879 $sql_query = sprintf(
1880 'ALTER TABLE %s.%s',
1881 PMA_Util
::backquote($this->_db_name
),
1882 PMA_Util
::backquote($this->_name
)
1885 // Drops the old index
1886 if (! empty($_REQUEST['old_index'])) {
1887 if ($_REQUEST['old_index'] == 'PRIMARY') {
1888 $sql_query .= ' DROP PRIMARY KEY,';
1890 $sql_query .= sprintf(
1892 PMA_Util
::backquote($_REQUEST['old_index'])
1897 // Builds the new one
1898 switch ($index->getChoice()) {
1900 if ($index->getName() == '') {
1901 $index->setName('PRIMARY');
1902 } elseif ($index->getName() != 'PRIMARY') {
1903 $error = PMA_Message
::error(
1904 __('The name of the primary key must be "PRIMARY"!')
1907 $sql_query .= ' ADD PRIMARY KEY';
1913 if ($index->getName() == 'PRIMARY') {
1914 $error = PMA_Message
::error(
1915 __('Can\'t rename index to PRIMARY!')
1918 $sql_query .= sprintf(
1922 if ($index->getName()) {
1923 $sql_query .= PMA_Util
::backquote($index->getName());
1928 $index_fields = array();
1929 foreach ($index->getColumns() as $key => $column) {
1930 $index_fields[$key] = PMA_Util
::backquote($column->getName());
1931 if ($column->getSubPart()) {
1932 $index_fields[$key] .= '(' . $column->getSubPart() . ')';
1936 if (empty($index_fields)) {
1937 $error = PMA_Message
::error(__('No index parts defined!'));
1939 $sql_query .= ' (' . implode(', ', $index_fields) . ')';
1942 $keyBlockSizes = $index->getKeyBlockSize();
1943 if (! empty($keyBlockSizes)) {
1944 $sql_query .= sprintf(
1945 ' KEY_BLOCK_SIZE = ',
1946 PMA_Util
::sqlAddSlashes($keyBlockSizes)
1950 // specifying index type is allowed only for primary, unique and index only
1951 $type = $index->getType();
1952 if ($index->getChoice() != 'SPATIAL'
1953 && $index->getChoice() != 'FULLTEXT'
1954 && in_array($type, PMA_Index
::getIndexTypes())
1956 $sql_query .= ' USING ' . $type;
1959 $parser = $index->getParser();
1960 if ($index->getChoice() == 'FULLTEXT' && ! empty($parser)) {
1961 $sql_query .= ' WITH PARSER ' . PMA_Util
::sqlAddSlashes($parser);
1964 $comment = $index->getComment();
1965 if (! empty($comment)) {
1966 $sql_query .= sprintf(
1968 PMA_Util
::sqlAddSlashes($comment)
1978 * Function to handle update for display field
1980 * @param string $disp current display field
1981 * @param string $display_field display field
1982 * @param array $cfgRelation configuration relation
1984 * @return boolean True on update succeed or False on failure
1986 public function updateDisplayField($disp, $display_field, $cfgRelation) {
1989 if ($display_field == '') {
1990 $upd_query = 'DELETE FROM '
1991 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1992 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
1993 . ' WHERE db_name = \'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
1994 . ' AND table_name = \'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\'';
1995 } elseif ($disp != $display_field) {
1996 $upd_query = 'UPDATE '
1997 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1998 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
1999 . ' SET display_field = \''
2000 . PMA_Util
::sqlAddSlashes($display_field) . '\''
2001 . ' WHERE db_name = \'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
2002 . ' AND table_name = \'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\'';
2004 } elseif ($display_field != '') {
2005 $upd_query = 'INSERT INTO '
2006 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2007 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
2008 . '(db_name, table_name, display_field) VALUES('
2009 . '\'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\','
2010 . '\'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\','
2011 . '\'' . PMA_Util
::sqlAddSlashes($display_field) . '\')';
2017 $GLOBALS['controllink'],
2027 * Function to get update query for updating internal relations
2029 * @param array $multi_edit_columns_name multi edit column names
2030 * @param array $destination_db destination tables
2031 * @param array $destination_table destination tables
2032 * @param array $destination_column destination columns
2033 * @param array $cfgRelation configuration relation
2034 * @param array|null $existrel db, table, column
2038 public function updateInternalRelations($multi_edit_columns_name,
2039 $destination_db, $destination_table, $destination_column,
2040 $cfgRelation, $existrel) {
2042 foreach ($destination_db as $master_field_md5 => $foreign_db) {
2044 // Map the fieldname's md5 back to its real name
2045 $master_field = $multi_edit_columns_name[$master_field_md5];
2046 $foreign_table = $destination_table[$master_field_md5];
2047 $foreign_field = $destination_column[$master_field_md5];
2048 if (! empty($foreign_db)
2049 && ! empty($foreign_table)
2050 && ! empty($foreign_field)
2052 if (! isset($existrel[$master_field])) {
2053 $upd_query = 'INSERT INTO '
2054 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2055 . '.' . PMA_Util
::backquote($cfgRelation['relation'])
2056 . '(master_db, master_table, master_field, foreign_db,'
2057 . ' foreign_table, foreign_field)'
2059 . '\'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\', '
2060 . '\'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\', '
2061 . '\'' . PMA_Util
::sqlAddSlashes($master_field) . '\', '
2062 . '\'' . PMA_Util
::sqlAddSlashes($foreign_db) . '\', '
2063 . '\'' . PMA_Util
::sqlAddSlashes($foreign_table) . '\','
2064 . '\'' . PMA_Util
::sqlAddSlashes($foreign_field) . '\')';
2066 } elseif ($existrel[$master_field]['foreign_db'] != $foreign_db
2067 ||
$existrel[$master_field]['foreign_table'] != $foreign_table
2068 ||
$existrel[$master_field]['foreign_field'] != $foreign_field
2070 $upd_query = 'UPDATE '
2071 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2072 . '.' . PMA_Util
::backquote($cfgRelation['relation']) . ' SET'
2073 . ' foreign_db = \''
2074 . PMA_Util
::sqlAddSlashes($foreign_db) . '\', '
2075 . ' foreign_table = \''
2076 . PMA_Util
::sqlAddSlashes($foreign_table) . '\', '
2077 . ' foreign_field = \''
2078 . PMA_Util
::sqlAddSlashes($foreign_field) . '\' '
2079 . ' WHERE master_db = \''
2080 . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
2081 . ' AND master_table = \''
2082 . PMA_Util
::sqlAddSlashes($this->_name
) . '\''
2083 . ' AND master_field = \''
2084 . PMA_Util
::sqlAddSlashes($master_field) . '\'';
2085 } // end if... else....
2086 } elseif (isset($existrel[$master_field])) {
2087 $upd_query = 'DELETE FROM '
2088 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2089 . '.' . PMA_Util
::backquote($cfgRelation['relation'])
2090 . ' WHERE master_db = \'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
2091 . ' AND master_table = \'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\''
2092 . ' AND master_field = \'' . PMA_Util
::sqlAddSlashes($master_field)
2094 } // end if... else....
2096 if (isset($upd_query)) {
2099 $GLOBALS['controllink'],
2110 * Function to handle foreign key updates
2112 * @param array $destination_foreign_db destination foreign database
2113 * @param array $multi_edit_columns_name multi edit column names
2114 * @param array $destination_foreign_table destination foreign table
2115 * @param array $destination_foreign_column destination foreign column
2116 * @param array $options_array options array
2117 * @param string $table current table
2118 * @param array $existrel_foreign db, table, column
2122 public function updateForeignKeys($destination_foreign_db,
2123 $multi_edit_columns_name, $destination_foreign_table,
2124 $destination_foreign_column, $options_array, $table, $existrel_foreign) {
2126 $preview_sql_data = '';
2127 $display_query = '';
2128 $seen_error = false;
2130 foreach ($destination_foreign_db as $master_field_md5 => $foreign_db) {
2134 // Map the fieldname's md5 back to its real name
2135 $master_field = $multi_edit_columns_name[$master_field_md5];
2137 $foreign_table = $destination_foreign_table[$master_field_md5];
2138 $foreign_field = $destination_foreign_column[$master_field_md5];
2140 if (isset($existrel_foreign[$master_field_md5]['ref_db_name'])) {
2141 $ref_db_name = $existrel_foreign[$master_field_md5]['ref_db_name'];
2143 $ref_db_name = $GLOBALS['db'];
2146 $empty_fields = false;
2147 foreach ($master_field as $key => $one_field) {
2148 if ((! empty($one_field) && empty($foreign_field[$key]))
2149 ||
(empty($one_field) && ! empty($foreign_field[$key]))
2151 $empty_fields = true;
2154 if (empty($one_field) && empty($foreign_field[$key])) {
2155 unset($master_field[$key]);
2156 unset($foreign_field[$key]);
2160 if (! empty($foreign_db)
2161 && ! empty($foreign_table)
2164 if (isset($existrel_foreign[$master_field_md5])) {
2165 $constraint_name = $existrel_foreign[$master_field_md5]['constraint'];
2166 $on_delete = ! empty(
2167 $existrel_foreign[$master_field_md5]['on_delete'])
2168 ?
$existrel_foreign[$master_field_md5]['on_delete']
2170 $on_update = ! empty(
2171 $existrel_foreign[$master_field_md5]['on_update'])
2172 ?
$existrel_foreign[$master_field_md5]['on_update']
2175 if ($ref_db_name != $foreign_db
2176 ||
$existrel_foreign[$master_field_md5]['ref_table_name'] != $foreign_table
2177 ||
$existrel_foreign[$master_field_md5]['ref_index_list'] != $foreign_field
2178 ||
$existrel_foreign[$master_field_md5]['index_list'] != $master_field
2179 ||
$_REQUEST['constraint_name'][$master_field_md5] != $constraint_name
2180 ||
($_REQUEST['on_delete'][$master_field_md5] != $on_delete)
2181 ||
($_REQUEST['on_update'][$master_field_md5] != $on_update)
2183 // another foreign key is already defined for this field
2184 // or an option has been changed for ON DELETE or ON UPDATE
2187 } // end if... else....
2189 // no key defined for this field(s)
2192 } elseif (isset($existrel_foreign[$master_field_md5])) {
2194 } // end if... else....
2196 $tmp_error_drop = false;
2198 $drop_query = 'ALTER TABLE ' . PMA_Util
::backquote($table)
2199 . ' DROP FOREIGN KEY ' . PMA_Util
::backquote($existrel_foreign[$master_field_md5]['constraint']) . ';';
2201 if (! isset($_REQUEST['preview_sql'])) {
2202 $display_query .= $drop_query . "\n";
2203 $this->_dbi
->tryQuery($drop_query);
2204 $tmp_error_drop = $GLOBALS['dbi']->getError();
2206 if (! empty($tmp_error_drop)) {
2208 $html_output .= PMA_Util
::mysqlDie(
2209 $tmp_error_drop, $drop_query, false, '', false
2214 $preview_sql_data .= $drop_query . "\n";
2217 $tmp_error_create = false;
2222 $create_query = $this->getSQLToCreateForeignKey(
2223 $table, $master_field, $foreign_db, $foreign_table, $foreign_field,
2224 $_REQUEST['constraint_name'][$master_field_md5],
2225 $options_array[$_REQUEST['on_delete'][$master_field_md5]],
2226 $options_array[$_REQUEST['on_update'][$master_field_md5]]
2229 if (! isset($_REQUEST['preview_sql'])) {
2230 $display_query .= $create_query . "\n";
2231 $GLOBALS['dbi']->tryQuery($create_query);
2232 $tmp_error_create = $GLOBALS['dbi']->getError();
2233 if (! empty($tmp_error_create)) {
2236 if (substr($tmp_error_create, 1, 4) == '1005') {
2237 $message = PMA_Message
::error(
2238 __('Error creating foreign key on %1$s (check data types)')
2240 $message->addParam(implode(', ', $master_field));
2241 $html_output .= $message->getDisplay();
2243 $html_output .= PMA_Util
::mysqlDie(
2244 $tmp_error_create, $create_query, false, '', false
2247 $html_output .= PMA_Util
::showMySQLDocu(
2248 'InnoDB_foreign_key_constraints'
2252 $preview_sql_data .= $create_query . "\n";
2255 // this is an alteration and the old constraint has been dropped
2256 // without creation of a new one
2257 if ($drop && $create && empty($tmp_error_drop)
2258 && ! empty($tmp_error_create)
2260 // a rollback may be better here
2261 $sql_query_recreate = '# Restoring the dropped constraint...' . "\n";
2262 $sql_query_recreate .= $this->getSQLToCreateForeignKey(
2265 $existrel_foreign[$master_field_md5]['ref_db_name'],
2266 $existrel_foreign[$master_field_md5]['ref_table_name'],
2267 $existrel_foreign[$master_field_md5]['ref_index_list'],
2268 $existrel_foreign[$master_field_md5]['constraint'],
2269 $options_array[$existrel_foreign[$master_field_md5]['on_delete']],
2270 $options_array[$existrel_foreign[$master_field_md5]['on_update']]
2272 if (! isset($_REQUEST['preview_sql'])) {
2273 $display_query .= $sql_query_recreate . "\n";
2274 $this->_dbi
->tryQuery($sql_query_recreate);
2276 $preview_sql_data .= $sql_query_recreate;
2290 * Returns the SQL query for foreign key constraint creation
2292 * @param string $table table name
2293 * @param array $field field names
2294 * @param string $foreignDb foreign database name
2295 * @param string $foreignTable foreign table name
2296 * @param array $foreignField foreign field names
2297 * @param string $name name of the constraint
2298 * @param string $onDelete on delete action
2299 * @param string $onUpdate on update action
2301 * @return string SQL query for foreign key constraint creation
2303 private function getSQLToCreateForeignKey($table, $field, $foreignDb, $foreignTable,
2304 $foreignField, $name = null, $onDelete = null, $onUpdate = null
2306 $sql_query = 'ALTER TABLE ' . PMA_Util
::backquote($table) . ' ADD ';
2307 // if user entered a constraint name
2308 if (! empty($name)) {
2309 $sql_query .= ' CONSTRAINT ' . PMA_Util
::backquote($name);
2312 foreach ($field as $key => $one_field) {
2313 $field[$key] = PMA_Util
::backquote($one_field);
2315 foreach ($foreignField as $key => $one_field) {
2316 $foreignField[$key] = PMA_Util
::backquote($one_field);
2318 $sql_query .= ' FOREIGN KEY (' . implode(', ', $field) . ')'
2319 . ' REFERENCES ' . PMA_Util
::backquote($foreignDb)
2320 . '.' . PMA_Util
::backquote($foreignTable)
2321 . '(' . implode(', ', $foreignField) . ')';
2323 if (! empty($onDelete)) {
2324 $sql_query .= ' ON DELETE ' . $onDelete;
2326 if (! empty($onUpdate)) {
2327 $sql_query .= ' ON UPDATE ' . $onUpdate;