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';
28 * @var string engine (innodb, myisam, bdb, ...)
33 * @var string type (view, base table, system view)
38 * @var array UI preferences
43 * @var array errors occurred
45 var $errors = array();
50 var $messages = array();
53 * @var string table name
55 protected $_name = '';
58 * @var string database name
60 protected $_db_name = '';
63 * @var PMA_DatabaseInterface
70 * @param string $table_name table name
71 * @param string $db_name database name
72 * @param PMA_DatabaseInterface $dbi database interface for the table
74 function __construct($table_name, $db_name, PMA_DatabaseInterface
$dbi = null)
77 $dbi = $GLOBALS['dbi'];
80 $this->_name
= $table_name;
81 $this->_db_name
= $db_name;
87 * @see PMA_Table::getName()
88 * @return string table name
92 return $this->getName();
96 * return the last error
98 * @return string the last error
100 function getLastError()
102 return end($this->errors
);
106 * return the last message
108 * @return string the last message
110 function getLastMessage()
112 return end($this->messages
);
118 * @param boolean $backquoted whether to quote name with backticks ``
120 * @return string table name
122 function getName($backquoted = false)
125 return PMA_Util
::backquote($this->_name
);
131 * returns database name for this table
133 * @param boolean $backquoted whether to quote name with backticks ``
135 * @return string database name for this table
137 function getDbName($backquoted = false)
140 return PMA_Util
::backquote($this->_db_name
);
142 return $this->_db_name
;
146 * returns full name for table, including database name
148 * @param boolean $backquoted whether to quote name with backticks ``
152 function getFullName($backquoted = false)
154 return $this->getDbName($backquoted) . '.'
155 . $this->getName($backquoted);
159 * returns whether the table is actually a view
161 * @return boolean whether the given is a view
163 public function isView()
165 $db = $this->_db_name
;
166 $table = $this->_name
;
167 if (empty($db) ||
empty($table)) {
171 // use cached data or load information with SHOW command
172 if ($this->_dbi
->getCachedTableContent("${db}.${table}") != null
173 ||
$GLOBALS['cfg']['Server']['DisableIS']
175 $type = $this->sGetStatusInfo('TABLE_TYPE');
176 return $type == 'VIEW' ||
$type == 'SYSTEM VIEW';
179 // information_schema tables are 'SYSTEM VIEW's
180 if ($db == 'information_schema') {
184 // query information_schema
185 $result = $this->_dbi
->fetchResult(
187 FROM information_schema.VIEWS
188 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($db) . "'
189 AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($table) . "'"
191 return $result ?
true : false;
195 * Returns whether the table is actually an updatable view
197 * @return boolean whether the given is an updatable view
199 public function isUpdatableView()
201 if (empty($this->_db_name
) ||
empty($this->_name
)) {
205 $result = $this->_dbi
->fetchResult(
207 FROM information_schema.VIEWS
208 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($this->_db_name
) . "'
209 AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'
210 AND IS_UPDATABLE = 'YES'"
212 return $result ?
true : false;
216 * Returns the analysis of 'SHOW CREATE TABLE' query for the table.
217 * In case of a view, the values are taken from the information_schema.
219 * @return array analysis of 'SHOW CREATE TABLE' query for the table
221 public function analyzeStructure()
223 if (empty($this->_db_name
) ||
empty($this->_name
)) {
227 $analyzed_sql = array();
228 if ($this->isView()) {
229 // For a view, 'SHOW CREATE TABLE' returns the definition,
230 // but the structure of the view. So, we try to mock
231 // the result of analyzing 'SHOW CREATE TABLE' query.
232 $analyzed_sql[0] = array();
233 $analyzed_sql[0]['create_table_fields'] = array();
235 $results = $this->_dbi
->fetchResult(
236 "SELECT COLUMN_NAME, DATA_TYPE
237 FROM information_schema.COLUMNS
238 WHERE TABLE_SCHEMA = '" . PMA_Util
::sqlAddSlashes($this->_db_name
)
239 . " AND TABLE_NAME = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'"
242 foreach ($results as $result) {
243 $analyzed_sql[0]['create_table_fields'][$result['COLUMN_NAME']]
245 'type' => /*overload*/mb_strtoupper($result['DATA_TYPE'])
249 $show_create_table = $this->_dbi
->fetchValue(
251 . PMA_Util
::backquote($this->_db_name
)
252 . '.' . PMA_Util
::backquote($this->_name
),
256 $analyzed_sql = PMA_SQP_analyze(PMA_SQP_parse($show_create_table));
258 return $analyzed_sql;
262 * Checks if this is a merge table
264 * If the ENGINE of the table is MERGE or MRG_MYISAM (alias),
265 * this is a merge table.
268 * @return boolean true if it is a merge table
270 public function isMerge()
273 // if called static, with parameters
274 if (! empty($this->_db_name
) && ! empty($this->_name
)) {
275 $engine = $this->sGetStatusInfo('ENGINE', null, true);
278 // did we get engine?
279 if (empty($engine)) {
283 // any of known merge engines?
285 /*overload*/mb_strtoupper($engine),
286 array('MERGE', 'MRG_MYISAM')
291 * Returns full table status info, or specific if $info provided
292 * this info is collected from information_schema
294 * @param string $info specific information to be fetched
295 * @param boolean $force_read read new rather than serving from cache
296 * @param boolean $disable_error if true, disables error message
298 * @todo DatabaseInterface::getTablesFull needs to be merged
299 * somehow into this class or at least better documented
303 public function sGetStatusInfo(
306 $disable_error = false
308 $db = $this->_db_name
;
309 $table = $this->_name
;
311 if (! empty($_SESSION['is_multi_query'])) {
312 $disable_error = true;
315 // sometimes there is only one entry (ExactRows) so
316 // we have to get the table's details
317 if ($this->_dbi
->getCachedTableContent("${db}.${table}") == null
319 ||
count($this->_dbi
->getCachedTableContent("${db}.${table}")) == 1
321 $this->_dbi
->getTablesFull($db, $table);
324 if ($this->_dbi
->getCachedTableContent("${db}.${table}") == null) {
325 // happens when we enter the table creation dialog
326 // or when we really did not get any status info, for example
327 // when $table == 'TABLE_NAMES' after the user tried SHOW TABLES
331 if (null === $info) {
332 return $this->_dbi
->getCachedTableContent("${db}.${table}");
335 // array_key_exists allows for null values
336 if (!array_key_exists(
337 $info, $this->_dbi
->getCachedTableContent("${db}.${table}")
340 if (! $disable_error) {
342 __('Unknown table status:') . ' ' . $info,
349 return $this->_dbi
->getCachedTableContent("${db}.${table}.${info}");
353 * generates column specification for ALTER or CREATE TABLE syntax
355 * @param string $name name
356 * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
357 * @param string $length length ('2', '5,2', '', ...)
358 * @param string $attribute attribute
359 * @param string $collation collation
360 * @param bool|string $null with 'NULL' or 'NOT NULL'
361 * @param string $default_type whether default is CURRENT_TIMESTAMP,
362 * NULL, NONE, USER_DEFINED
363 * @param string $default_value default value for USER_DEFINED
365 * @param string $extra 'AUTO_INCREMENT'
366 * @param string $comment field comment
367 * @param string $virtuality virtuality of the column
368 * @param string $expression expression for the virtual column
369 * @param string $move_to new position for column
371 * @todo move into class PMA_Column
372 * @todo on the interface, some js to clear the default value when the
373 * default current_timestamp is checked
375 * @return string field specification
377 static function generateFieldSpec($name, $type, $length = '',
378 $attribute = '', $collation = '', $null = false,
379 $default_type = 'USER_DEFINED', $default_value = '', $extra = '',
380 $comment = '', $virtuality = '', $expression = '', $move_to = ''
382 $is_timestamp = /*overload*/mb_strpos(
383 /*overload*/mb_strtoupper($type),
387 $query = PMA_Util
::backquote($name) . ' ' . $type;
389 // allow the possibility of a length for TIME, DATETIME and TIMESTAMP
390 // (will work on MySQL >= 5.6.4)
392 // MySQL permits a non-standard syntax for FLOAT and DOUBLE,
393 // see http://dev.mysql.com/doc/refman/5.5/en/floating-point-types.html
395 $pattern = '@^(DATE|TINYBLOB|TINYTEXT|BLOB|TEXT|'
396 . 'MEDIUMBLOB|MEDIUMTEXT|LONGBLOB|LONGTEXT|SERIAL|BOOLEAN|UUID)$@i';
397 if ($length != '' && ! preg_match($pattern, $type)) {
398 $query .= '(' . $length . ')';
402 $query .= ' AS (' . $expression . ') ' . $virtuality;
404 if ($attribute != '') {
405 $query .= ' ' . $attribute;
408 $matches = preg_match(
409 '@^(TINYTEXT|TEXT|MEDIUMTEXT|LONGTEXT|VARCHAR|CHAR|ENUM|SET)$@i',
412 if (! empty($collation) && $collation != 'NULL' && $matches) {
413 $query .= PMA_generateCharsetQueryPart($collation);
416 if ($null !== false) {
417 if ($null == 'NULL') {
420 $query .= ' NOT NULL';
424 switch ($default_type) {
425 case 'USER_DEFINED' :
426 if ($is_timestamp && $default_value === '0') {
427 // a TIMESTAMP does not accept DEFAULT '0'
428 // but DEFAULT 0 works
429 $query .= ' DEFAULT 0';
430 } elseif ($type == 'BIT') {
431 $query .= ' DEFAULT b\''
432 . preg_replace('/[^01]/', '0', $default_value)
434 } elseif ($type == 'BOOLEAN') {
435 if (preg_match('/^1|T|TRUE|YES$/i', $default_value)) {
436 $query .= ' DEFAULT TRUE';
437 } elseif (preg_match('/^0|F|FALSE|NO$/i', $default_value)) {
438 $query .= ' DEFAULT FALSE';
440 // Invalid BOOLEAN value
441 $query .= ' DEFAULT \''
442 . PMA_Util
::sqlAddSlashes($default_value) . '\'';
444 } elseif ($type == 'BINARY' ||
$type == 'VARBINARY') {
445 $query .= ' DEFAULT 0x' . $default_value;
447 $query .= ' DEFAULT \''
448 . PMA_Util
::sqlAddSlashes($default_value) . '\'';
451 /** @noinspection PhpMissingBreakStatementInspection */
453 // If user uncheck null checkbox and not change default value null,
454 // default value will be ignored.
455 if ($null !== false && $null !== 'NULL') {
458 // else fall-through intended, no break here
459 case 'CURRENT_TIMESTAMP' :
460 $query .= ' DEFAULT ' . $default_type;
467 if (!empty($extra)) {
468 $query .= ' ' . $extra;
471 if (!empty($comment)) {
472 $query .= " COMMENT '" . PMA_Util
::sqlAddSlashes($comment) . "'";
476 if ($move_to == '-first') { // dash can't appear as part of column name
478 } elseif ($move_to != '') {
479 $query .= ' AFTER ' . PMA_Util
::backquote($move_to);
485 * Counts and returns (or displays) the number of records in a table
487 * @param bool $force_exact whether to force an exact count
489 * @return mixed the number of records if "retain" param is true,
492 public function countRecords($force_exact = false)
494 $is_view = $this->isView();
495 $db = $this->_db_name
;
496 $table = $this->_name
;
498 if ($this->_dbi
->getCachedTableContent("${db}.${table}.ExactRows") != null) {
499 $row_count = $this->_dbi
->getCachedTableContent("${db}.${table}.ExactRows");
504 if (! $force_exact) {
505 if (($this->_dbi
->getCachedTableContent("${db}.${table}.Rows") == null)
508 $tmp_tables = $this->_dbi
->getTablesFull($db, $table);
509 if (isset($tmp_tables[$table])) {
510 $this->_dbi
->cacheTableContent("${db}.${table}", $tmp_tables[$table]);
513 if ($this->_dbi
->getCachedTableContent("${db}.${table}.Rows") != null) {
514 $row_count = $this->_dbi
->getCachedTableContent("${db}.${table}.Rows");
519 // for a VIEW, $row_count is always false at this point
520 if (false !== $row_count
521 && $row_count >= $GLOBALS['cfg']['MaxExactCount']
526 // Make an exception for views in I_S and D_D schema in
527 // Drizzle, as these map to in-memory data and should execute
530 ||
(PMA_DRIZZLE
&& $this->_dbi
->isSystemSchema($db))
532 $row_count = $this->_dbi
->fetchValue(
533 'SELECT COUNT(*) FROM ' . PMA_Util
::backquote($db) . '.'
534 . PMA_Util
::backquote($table)
537 // For complex views, even trying to get a partial record
538 // count could bring down a server, so we offer an
539 // alternative: setting MaxExactCountViews to 0 will bypass
540 // completely the record counting for views
542 if ($GLOBALS['cfg']['MaxExactCountViews'] == 0) {
545 // Counting all rows of a VIEW could be too long,
546 // so use a LIMIT clause.
547 // Use try_query because it can fail (when a VIEW is
548 // based on a table that no longer exists)
549 $result = $this->_dbi
->tryQuery(
550 'SELECT 1 FROM ' . PMA_Util
::backquote($db) . '.'
551 . PMA_Util
::backquote($table) . ' LIMIT '
552 . $GLOBALS['cfg']['MaxExactCountViews'],
554 PMA_DatabaseInterface
::QUERY_STORE
556 if (!$this->_dbi
->getError()) {
557 $row_count = $this->_dbi
->numRows($result);
558 $this->_dbi
->freeResult($result);
563 $this->_dbi
->cacheTableContent("${db}.${table}.ExactRows", $row_count);
567 } // end of the 'PMA_Table::countRecords()' function
570 * Generates column specification for ALTER syntax
572 * @param string $oldcol old column name
573 * @param string $newcol new column name
574 * @param string $type type ('INT', 'VARCHAR', 'BIT', ...)
575 * @param string $length length ('2', '5,2', '', ...)
576 * @param string $attribute attribute
577 * @param string $collation collation
578 * @param bool|string $null with 'NULL' or 'NOT NULL'
579 * @param string $default_type whether default is CURRENT_TIMESTAMP,
580 * NULL, NONE, USER_DEFINED
581 * @param string $default_value default value for USER_DEFINED default
583 * @param string $extra 'AUTO_INCREMENT'
584 * @param string $comment field comment
585 * @param string $virtuality virtuality of the column
586 * @param string $expression expression for the virtual column
587 * @param string $move_to new position for column
589 * @see PMA_Table::generateFieldSpec()
591 * @return string field specification
593 static public function generateAlter($oldcol, $newcol, $type, $length,
594 $attribute, $collation, $null, $default_type, $default_value,
595 $extra, $comment, $virtuality, $expression, $move_to
597 return PMA_Util
::backquote($oldcol) . ' '
598 . PMA_Table
::generateFieldSpec(
599 $newcol, $type, $length, $attribute,
600 $collation, $null, $default_type, $default_value, $extra,
601 $comment, $virtuality, $expression, $move_to
606 * Inserts existing entries in a PMA_* table by reading a value from an old
609 * @param string $work The array index, which Relation feature to
610 * check ('relwork', 'commwork', ...)
611 * @param string $pma_table The array index, which PMA-table to update
612 * ('bookmark', 'relation', ...)
613 * @param array $get_fields Which fields will be SELECT'ed from the old entry
614 * @param array $where_fields Which fields will be used for the WHERE query
615 * (array('FIELDNAME' => 'FIELDVALUE'))
616 * @param array $new_fields Which fields will be used as new VALUES.
617 * These are the important keys which differ
619 * (array('FIELDNAME' => 'NEW FIELDVALUE'))
621 * @global relation variable
625 static public function duplicateInfo($work, $pma_table, $get_fields,
626 $where_fields, $new_fields
630 if (!isset($GLOBALS['cfgRelation']) ||
!$GLOBALS['cfgRelation'][$work]) {
634 $select_parts = array();
635 $row_fields = array();
636 foreach ($get_fields as $get_field) {
637 $select_parts[] = PMA_Util
::backquote($get_field);
638 $row_fields[$get_field] = 'cc';
641 $where_parts = array();
642 foreach ($where_fields as $_where => $_value) {
643 $where_parts[] = PMA_Util
::backquote($_where) . ' = \''
644 . PMA_Util
::sqlAddSlashes($_value) . '\'';
647 $new_parts = array();
648 $new_value_parts = array();
649 foreach ($new_fields as $_where => $_value) {
650 $new_parts[] = PMA_Util
::backquote($_where);
651 $new_value_parts[] = PMA_Util
::sqlAddSlashes($_value);
654 $table_copy_query = '
655 SELECT ' . implode(', ', $select_parts) . '
656 FROM ' . PMA_Util
::backquote($GLOBALS['cfgRelation']['db']) . '.'
657 . PMA_Util
::backquote($GLOBALS['cfgRelation'][$pma_table]) . '
658 WHERE ' . implode(' AND ', $where_parts);
660 // must use PMA_DatabaseInterface::QUERY_STORE here, since we execute
661 // another query inside the loop
662 $table_copy_rs = PMA_queryAsControlUser(
663 $table_copy_query, true, PMA_DatabaseInterface
::QUERY_STORE
666 while ($table_copy_row = @$GLOBALS['dbi']->fetchAssoc($table_copy_rs)) {
667 $value_parts = array();
668 foreach ($table_copy_row as $_key => $_val) {
669 if (isset($row_fields[$_key]) && $row_fields[$_key] == 'cc') {
670 $value_parts[] = PMA_Util
::sqlAddSlashes($_val);
674 $new_table_query = 'INSERT IGNORE INTO '
675 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
676 . '.' . PMA_Util
::backquote($GLOBALS['cfgRelation'][$pma_table])
677 . ' (' . implode(', ', $select_parts) . ', '
678 . implode(', ', $new_parts) . ') VALUES (\''
679 . implode('\', \'', $value_parts) . '\', \''
680 . implode('\', \'', $new_value_parts) . '\')';
682 PMA_queryAsControlUser($new_table_query);
683 $last_id = $GLOBALS['dbi']->insertId();
686 $GLOBALS['dbi']->freeResult($table_copy_rs);
689 } // end of 'PMA_Table::duplicateInfo()' function
692 * Copies or renames table
694 * @param string $source_db source database
695 * @param string $source_table source table
696 * @param string $target_db target database
697 * @param string $target_table target table
698 * @param string $what what to be moved or copied (data, dataonly)
699 * @param bool $move whether to move
700 * @param string $mode mode
702 * @return bool true if success, false otherwise
704 static public function moveCopy($source_db, $source_table, $target_db,
705 $target_table, $what, $move, $mode
710 // Try moving the tables directly, using native `RENAME` statement.
711 if ($move && $what == 'data') {
712 $tbl = new PMA_Table($source_table, $source_db);
713 if ($tbl->rename($target_table, $target_db)) {
714 $GLOBALS['message'] = $tbl->getLastMessage();
719 // Setting required export settings.
720 $GLOBALS['sql_backquotes'] = 1;
721 $GLOBALS['asfile'] = 1;
723 // Ensuring the target database is valid.
724 if (! $GLOBALS['pma']->databases
->exists($source_db, $target_db)) {
725 if (! $GLOBALS['pma']->databases
->exists($source_db)) {
726 $GLOBALS['message'] = PMA_Message
::rawError(
728 __('Source database `%s` was not found!'),
729 htmlspecialchars($source_db)
733 if (! $GLOBALS['pma']->databases
->exists($target_db)) {
734 $GLOBALS['message'] = PMA_Message
::rawError(
736 __('Target database `%s` was not found!'),
737 htmlspecialchars($target_db)
745 * The full name of source table, quoted.
746 * @var string $source
748 $source = PMA_Util
::backquote($source_db)
749 . '.' . PMA_Util
::backquote($source_table);
751 // If the target database is not specified, the operation is taking
752 // place in the same database.
753 if (! isset($target_db) ||
! /*overload*/mb_strlen($target_db)) {
754 $target_db = $source_db;
757 // Selecting the database could avoid some problems with replicated
758 // databases, when moving table from replicated one to not replicated one.
759 $GLOBALS['dbi']->selectDb($target_db);
762 * The full name of target table, quoted.
763 * @var string $target
765 $target = PMA_Util
::backquote($target_db)
766 . '.' . PMA_Util
::backquote($target_table);
768 // No table is created when this is a data-only operation.
769 if ($what != 'dataonly') {
771 include_once "libraries/plugin_interface.lib.php";
774 * Instance used for exporting the current structure of the table.
778 $export_sql_plugin = PMA_getPlugin(
781 'libraries/plugins/export/',
783 'export_type' => 'table',
784 'single_table' => false,
788 $no_constraints_comments = true;
789 $GLOBALS['sql_constraints_query'] = '';
790 // set the value of global sql_auto_increment variable
791 if (isset($_POST['sql_auto_increment'])) {
792 $GLOBALS['sql_auto_increment'] = $_POST['sql_auto_increment'];
796 * The old structure of the table..
797 * @var string $sql_structure
799 $sql_structure = $export_sql_plugin->getTableDef(
800 $source_db, $source_table, "\n", $err_url, false, false
803 unset($no_constraints_comments);
805 // -----------------------------------------------------------------
806 // Phase 0: Preparing structures used.
809 * The destination where the table is moved or copied to.
810 * @var SqlParser\Components\Expression
812 $destination = new SqlParser\Components\
Expression(
813 $target_db, $target_table, ''
816 // Find server's SQL mode so the builder can generate correct
818 // One of the options that alters the behaviour is `ANSI_QUOTES`.
819 // This is not availabile for Drizzle.
821 SqlParser\Context
::setMode(
822 $GLOBALS['dbi']->fetchValue(
823 "SHOW VARIABLES LIKE 'sql_mode'", 0, 1
828 // -----------------------------------------------------------------
829 // Phase 1: Dropping existent element of the same name (if exists
832 if (isset($_REQUEST['drop_if_exists'])
833 && $_REQUEST['drop_if_exists'] == 'true'
837 * Drop statement used for building the query.
838 * @var SqlParser\Statements\DropStatement $statement
840 $statement = new SqlParser\Statements\
DropStatement();
842 $tbl = new PMA_Table($target_db, $target_table);
844 $statement->options
= new SqlParser\Components\
OptionsArray(
846 $tbl->isView() ?
'VIEW' : 'TABLE',
851 $statement->fields
= array($destination);
853 // Building the query.
854 $drop_query = $statement->build() . ';';
857 $GLOBALS['dbi']->query($drop_query);
858 $GLOBALS['sql_query'] .= "\n" . $drop_query;
860 // If an existing table gets deleted, maintain any entries for
862 $maintain_relations = true;
865 // -----------------------------------------------------------------
866 // Phase 2: Generating the new query of this structure.
869 * The parser responsible for parsing the old queries.
870 * @var SqlParser\Parser $parser
872 $parser = new SqlParser\
Parser($sql_structure);
874 if (!empty($parser->statements
[0])) {
877 * The CREATE statement of this structure.
878 * @var SqlParser\Statements\CreateStatement $statement
880 $statement = $parser->statements
[0];
882 // Changing the destination.
883 $statement->name
= $destination;
885 // Building back the query.
886 $sql_structure = $statement->build() . ';';
889 $GLOBALS['dbi']->query($sql_structure);
890 $GLOBALS['sql_query'] .= "\n" . $sql_structure;
893 // -----------------------------------------------------------------
894 // Phase 3: Adding constraints.
895 // All constraint names are removed because they must be unique.
897 if (($move ||
isset($GLOBALS['add_constraints']))
898 && !empty($GLOBALS['sql_constraints_query'])
901 $parser = new SqlParser\
Parser($GLOBALS['sql_constraints_query']);
904 * The ALTER statement that generates the constraints.
905 * @var SqlParser\Statements\AlterStatement $statement
907 $statement = $parser->statements
[0];
909 // Changing the altered table to the destination.
910 $statement->table
= $destination;
912 // Removing the name of the constraints.
913 foreach ($statement->altered
as $idx => $altered) {
914 // All constraint names are removed because they must be unique.
915 if ($altered->options
->has('CONSTRAINT')) {
916 $altered->field
= null;
920 // Building back the query.
921 $GLOBALS['sql_constraints_query'] = $statement->build() . ';';
924 if ($mode == 'one_table') {
925 $GLOBALS['dbi']->query($GLOBALS['sql_constraints_query']);
927 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_constraints_query'];
928 if ($mode == 'one_table') {
929 unset($GLOBALS['sql_constraints_query']);
933 // -----------------------------------------------------------------
934 // Phase 4: Adding indexes.
937 if (!empty($GLOBALS['sql_indexes'])) {
939 $parser = new SqlParser\
Parser($GLOBALS['sql_indexes']);
942 * The ALTER statement that generates the indexes.
943 * @var SqlParser\Statements\AlterStatement $statement
945 $statement = $parser->statements
[0];
947 // Changing the altered table to the destination.
948 $statement->table
= $destination;
950 // Removing the name of the constraints.
951 foreach ($statement->altered
as $idx => $altered) {
952 // All constraint names are removed because they must be unique.
953 if ($altered->options
->has('CONSTRAINT')) {
954 $altered->field
= null;
958 // Building back the query.
959 $GLOBALS['sql_indexes'] = $statement->build() . ';';
962 if ($mode == 'one_table' ||
$mode == 'db_copy') {
963 $GLOBALS['dbi']->query($GLOBALS['sql_indexes']);
965 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_indexes'];
966 if ($mode == 'one_table' ||
$mode == 'db_copy') {
967 unset($GLOBALS['sql_indexes']);
971 // -----------------------------------------------------------------
972 // Phase 5: Adding AUTO_INCREMENT.
974 if (! empty($GLOBALS['sql_auto_increments'])) {
975 if ($mode == 'one_table' ||
$mode == 'db_copy') {
977 $parser = new SqlParser\
Parser($GLOBALS['sql_auto_increments']);
980 * The ALTER statement that alters the AUTO_INCREMENT value.
981 * @var SqlParser\Statements\AlterStatement $statement
983 $statement = $parser->statements
[0];
985 // Changing the altered table to the destination.
986 $statement->table
= $destination;
988 // Building back the query.
989 $GLOBALS['sql_auto_increments'] = $statement->build() . ';';
992 $GLOBALS['dbi']->query($GLOBALS['sql_auto_increments']);
993 $GLOBALS['sql_query'] .= "\n" . $GLOBALS['sql_auto_increments'];
994 unset($GLOBALS['sql_auto_increments']);
998 $GLOBALS['sql_query'] = '';
1001 $_table = new PMA_Table($target_table, $target_db);
1002 // Copy the data unless this is a VIEW
1003 if (($what == 'data' ||
$what == 'dataonly')
1004 && ! $_table->isView()
1006 if (! PMA_DRIZZLE
) {
1007 $sql_set_mode = "SET SQL_MODE='NO_AUTO_VALUE_ON_ZERO'";
1008 $GLOBALS['dbi']->query($sql_set_mode);
1009 $GLOBALS['sql_query'] .= "\n\n" . $sql_set_mode . ';';
1012 $sql_insert_data = 'INSERT INTO ' . $target
1013 . ' SELECT * FROM ' . $source;
1014 $GLOBALS['dbi']->query($sql_insert_data);
1015 $GLOBALS['sql_query'] .= "\n\n" . $sql_insert_data . ';';
1018 PMA_getRelationsParam();
1020 // Drops old table if the user has requested to move it
1023 // This could avoid some problems with replicated databases, when
1024 // moving table from replicated one to not replicated one
1025 $GLOBALS['dbi']->selectDb($source_db);
1027 $_source_table = new PMA_Table($source_table, $source_db);
1028 if ($_source_table->isView()) {
1029 $sql_drop_query = 'DROP VIEW';
1031 $sql_drop_query = 'DROP TABLE';
1033 $sql_drop_query .= ' ' . $source;
1034 $GLOBALS['dbi']->query($sql_drop_query);
1036 // Renable table in configuration storage
1037 PMA_REL_renameTable(
1038 $source_db, $target_db,
1039 $source_table, $target_table
1042 $GLOBALS['sql_query'] .= "\n\n" . $sql_drop_query . ';';
1048 // Create new entries as duplicates from old PMA DBs
1049 if ($what == 'dataonly' ||
isset($maintain_relations)) {
1053 if ($GLOBALS['cfgRelation']['commwork']) {
1054 // Get all comments and MIME-Types for current table
1055 $comments_copy_rs = PMA_queryAsControlUser(
1056 'SELECT column_name, comment'
1057 . ($GLOBALS['cfgRelation']['mimework']
1058 ?
', mimetype, transformation, transformation_options'
1061 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1063 . PMA_Util
::backquote($GLOBALS['cfgRelation']['column_info'])
1066 . PMA_Util
::sqlAddSlashes($source_db) . '\''
1068 . ' table_name = \''
1069 . PMA_Util
::sqlAddSlashes($source_table) . '\''
1072 // Write every comment as new copied entry. [MIME]
1073 while ($comments_copy_row
1074 = $GLOBALS['dbi']->fetchAssoc($comments_copy_rs)) {
1075 $new_comment_query = 'REPLACE INTO '
1076 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1077 . '.' . PMA_Util
::backquote(
1078 $GLOBALS['cfgRelation']['column_info']
1080 . ' (db_name, table_name, column_name, comment'
1081 . ($GLOBALS['cfgRelation']['mimework']
1082 ?
', mimetype, transformation, transformation_options'
1084 . ') ' . ' VALUES(' . '\'' . PMA_Util
::sqlAddSlashes($target_db)
1085 . '\',\'' . PMA_Util
::sqlAddSlashes($target_table) . '\',\''
1086 . PMA_Util
::sqlAddSlashes($comments_copy_row['column_name'])
1088 . ($GLOBALS['cfgRelation']['mimework']
1089 ?
',\'' . PMA_Util
::sqlAddSlashes(
1090 $comments_copy_row['comment']
1092 . '\',' . '\'' . PMA_Util
::sqlAddSlashes(
1093 $comments_copy_row['mimetype']
1095 . '\',' . '\'' . PMA_Util
::sqlAddSlashes(
1096 $comments_copy_row['transformation']
1098 . '\',' . '\'' . PMA_Util
::sqlAddSlashes(
1099 $comments_copy_row['transformation_options']
1104 PMA_queryAsControlUser($new_comment_query);
1106 $GLOBALS['dbi']->freeResult($comments_copy_rs);
1107 unset($comments_copy_rs);
1110 // duplicating the bookmarks must not be done here, but
1113 $get_fields = array('display_field');
1114 $where_fields = array(
1115 'db_name' => $source_db,
1116 'table_name' => $source_table
1118 $new_fields = array(
1119 'db_name' => $target_db,
1120 'table_name' => $target_table
1122 PMA_Table
::duplicateInfo(
1131 * @todo revise this code when we support cross-db relations
1133 $get_fields = array(
1138 $where_fields = array(
1139 'master_db' => $source_db,
1140 'master_table' => $source_table
1142 $new_fields = array(
1143 'master_db' => $target_db,
1144 'foreign_db' => $target_db,
1145 'master_table' => $target_table
1147 PMA_Table
::duplicateInfo(
1155 $get_fields = array(
1160 $where_fields = array(
1161 'foreign_db' => $source_db,
1162 'foreign_table' => $source_table
1164 $new_fields = array(
1165 'master_db' => $target_db,
1166 'foreign_db' => $target_db,
1167 'foreign_table' => $target_table
1169 PMA_Table
::duplicateInfo(
1178 * @todo Can't get duplicating PDFs the right way. The
1179 * page numbers always get screwed up independently from
1180 * duplication because the numbers do not seem to be stored on a
1181 * per-database basis. Would the author of pdf support please
1182 * have a look at it?
1184 $get_fields = array('page_descr');
1185 $where_fields = array('db_name' => $source_db);
1186 $new_fields = array('db_name' => $target_db);
1187 $last_id = PMA_Table::duplicateInfo(
1195 if (isset($last_id) && $last_id >= 0) {
1196 $get_fields = array('x', 'y');
1197 $where_fields = array(
1198 'db_name' => $source_db,
1199 'table_name' => $source_table
1201 $new_fields = array(
1202 'db_name' => $target_db,
1203 'table_name' => $target_table,
1204 'pdf_page_number' => $last_id
1206 PMA_Table::duplicateInfo(
1220 * checks if given name is a valid table name,
1221 * currently if not empty, trailing spaces, '.', '/' and '\'
1223 * @param string $table_name name to check
1225 * @todo add check for valid chars in filename on current system/os
1226 * @see http://dev.mysql.com/doc/refman/5.0/en/legal-names.html
1228 * @return boolean whether the string is valid or not
1230 static function isValidName($table_name)
1232 if ($table_name !== trim($table_name)) {
1237 if (! /*overload*/mb_strlen($table_name)) {
1242 if (preg_match('/[.\/\\\\]+/i', $table_name)) {
1243 // illegal char . / \
1253 * @param string $new_name new table name
1254 * @param string $new_db new database name
1256 * @return bool success
1258 function rename($new_name, $new_db = null)
1260 if (null !== $new_db && $new_db !== $this->getDbName()) {
1261 // Ensure the target is valid
1262 if (! $GLOBALS['pma']->databases
->exists($new_db)) {
1263 $this->errors
[] = __('Invalid database:') . ' ' . $new_db;
1267 $new_db = $this->getDbName();
1270 $new_table = new PMA_Table($new_name, $new_db);
1272 if ($this->getFullName() === $new_table->getFullName()) {
1276 if (! PMA_Table
::isValidName($new_name)) {
1277 $this->errors
[] = __('Invalid table name:') . ' '
1278 . $new_table->getFullName();
1282 // If the table is moved to a different database drop its triggers first
1283 $triggers = $this->_dbi
->getTriggers(
1284 $this->getDbName(), $this->getName(), ''
1286 $handle_triggers = $this->getDbName() != $new_db && $triggers;
1287 if ($handle_triggers) {
1288 foreach ($triggers as $trigger) {
1289 $sql = 'DROP TRIGGER IF EXISTS '
1290 . PMA_Util
::backquote($this->getDbName())
1291 . '.' . PMA_Util
::backquote($trigger['name']) . ';';
1292 $this->_dbi
->query($sql);
1297 * tested also for a view, in MySQL 5.0.92, 5.1.55 and 5.5.13
1299 $GLOBALS['sql_query'] = '
1300 RENAME TABLE ' . $this->getFullName(true) . '
1301 TO ' . $new_table->getFullName(true) . ';';
1302 // I don't think a specific error message for views is necessary
1303 if (! $this->_dbi
->query($GLOBALS['sql_query'])) {
1304 // Restore triggers in the old database
1305 if ($handle_triggers) {
1306 $this->_dbi
->selectDb($this->getDbName());
1307 foreach ($triggers as $trigger) {
1308 $this->_dbi
->query($trigger['create']);
1311 $this->errors
[] = sprintf(
1312 __('Failed to rename table %1$s to %2$s!'),
1313 $this->getFullName(),
1314 $new_table->getFullName()
1319 $old_name = $this->getName();
1320 $old_db = $this->getDbName();
1321 $this->_name
= $new_name;
1322 $this->_db_name
= $new_db;
1324 // Renable table in configuration storage
1325 PMA_REL_renameTable(
1327 $old_name, $new_name
1330 $this->messages
[] = sprintf(
1331 __('Table %1$s has been renamed to %2$s.'),
1332 htmlspecialchars($old_name),
1333 htmlspecialchars($new_name)
1339 * Get all unique columns
1341 * returns an array with all columns with unique content, in fact these are
1342 * all columns being single indexed in PRIMARY or UNIQUE
1345 * - PRIMARY(id) // id
1346 * - UNIQUE(name) // name
1347 * - PRIMARY(fk_id1, fk_id2) // NONE
1348 * - UNIQUE(x,y) // NONE
1350 * @param bool $backquoted whether to quote name with backticks ``
1351 * @param bool $fullName whether to include full name of the table as a prefix
1355 public function getUniqueColumns($backquoted = true, $fullName = true)
1357 $sql = $this->_dbi
->getTableIndexesSql(
1362 $uniques = $this->_dbi
->fetchResult(
1364 array('Key_name', null),
1369 foreach ($uniques as $index) {
1370 if (count($index) > 1) {
1374 $possible_column = $this->getFullName($backquoted) . '.';
1376 $possible_column = '';
1379 $possible_column .= PMA_Util
::backquote($index[0]);
1381 $possible_column .= $index[0];
1383 // a column might have a primary and an unique index on it
1384 if (! in_array($possible_column, $return)) {
1385 $return[] = $possible_column;
1393 * Get all indexed columns
1395 * returns an array with all columns that make use of an index
1397 * e.g. index(col1, col2) would return col1, col2
1399 * @param bool $backquoted whether to quote name with backticks ``
1400 * @param bool $fullName whether to include full name of the table as a prefix
1404 public function getIndexedColumns($backquoted = true, $fullName = true)
1406 $sql = $this->_dbi
->getTableIndexesSql(
1411 $indexed = $this->_dbi
->fetchResult($sql, 'Column_name', 'Column_name');
1414 foreach ($indexed as $column) {
1415 $return[] = ($fullName ?
$this->getFullName($backquoted) . '.' : '')
1416 . ($backquoted ? PMA_Util
::backquote($column) : $column);
1425 * returns an array with all columns
1427 * @param bool $backquoted whether to quote name with backticks ``
1428 * @param bool $fullName whether to include full name of the table as a prefix
1432 public function getColumns($backquoted = true, $fullName = true)
1434 $sql = 'SHOW COLUMNS FROM ' . $this->getFullName(true);
1435 $indexed = $this->_dbi
->fetchResult($sql, 'Field', 'Field');
1438 foreach ($indexed as $column) {
1439 $return[] = ($fullName ?
$this->getFullName($backquoted) . '.' : '')
1440 . ($backquoted ? PMA_Util
::backquote($column) : $column);
1447 * Get meta info for fields in table
1451 public function getColumnsMeta()
1453 $move_columns_sql_query = sprintf(
1454 'SELECT * FROM %s.%s LIMIT 1',
1455 PMA_Util
::backquote($this->_db_name
),
1456 PMA_Util
::backquote($this->_name
)
1458 $move_columns_sql_result = $this->_dbi
->tryQuery($move_columns_sql_query);
1459 return $this->_dbi
->getFieldsMeta($move_columns_sql_result);
1463 * Return UI preferences for this table from phpMyAdmin database.
1467 protected function getUiPrefsFromDb()
1469 $cfgRelation = PMA_getRelationsParam();
1470 $pma_table = PMA_Util
::backquote($cfgRelation['db']) . "."
1471 . PMA_Util
::backquote($cfgRelation['table_uiprefs']);
1473 // Read from phpMyAdmin database
1474 $sql_query = " SELECT `prefs` FROM " . $pma_table
1475 . " WHERE `username` = '" . $GLOBALS['cfg']['Server']['user'] . "'"
1476 . " AND `db_name` = '" . PMA_Util
::sqlAddSlashes($this->_db_name
) . "'"
1477 . " AND `table_name` = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'";
1479 $row = $this->_dbi
->fetchArray(PMA_queryAsControlUser($sql_query));
1480 if (isset($row[0])) {
1481 return json_decode($row[0], true);
1488 * Save this table's UI preferences into phpMyAdmin database.
1490 * @return true|PMA_Message
1492 protected function saveUiPrefsToDb()
1494 $cfgRelation = PMA_getRelationsParam();
1495 $pma_table = PMA_Util
::backquote($cfgRelation['db']) . "."
1496 . PMA_Util
::backquote($cfgRelation['table_uiprefs']);
1498 $secureDbName = PMA_Util
::sqlAddSlashes($this->_db_name
);
1500 $username = $GLOBALS['cfg']['Server']['user'];
1501 $sql_query = " REPLACE INTO " . $pma_table
1502 . " (username, db_name, table_name, prefs) VALUES ('"
1503 . $username . "', '" . $secureDbName
1504 . "', '" . PMA_Util
::sqlAddSlashes($this->_name
) . "', '"
1505 . PMA_Util
::sqlAddSlashes(json_encode($this->uiprefs
)) . "')";
1507 $success = $this->_dbi
->tryQuery($sql_query, $GLOBALS['controllink']);
1510 $message = PMA_Message
::error(
1511 __('Could not save table UI preferences!')
1513 $message->addMessage('<br /><br />');
1514 $message->addMessage(
1515 PMA_Message
::rawError(
1516 $this->_dbi
->getError($GLOBALS['controllink'])
1522 // Remove some old rows in table_uiprefs if it exceeds the configured
1524 $sql_query = 'SELECT COUNT(*) FROM ' . $pma_table;
1525 $rows_count = $this->_dbi
->fetchValue($sql_query);
1526 $max_rows = $GLOBALS['cfg']['Server']['MaxTableUiprefs'];
1527 if ($rows_count > $max_rows) {
1528 $num_rows_to_delete = $rows_count - $max_rows;
1530 = ' DELETE FROM ' . $pma_table .
1531 ' ORDER BY last_update ASC' .
1532 ' LIMIT ' . $num_rows_to_delete;
1533 $success = $this->_dbi
->tryQuery(
1534 $sql_query, $GLOBALS['controllink']
1538 $message = PMA_Message
::error(
1541 'Failed to cleanup table UI preferences (see ' .
1542 '$cfg[\'Servers\'][$i][\'MaxTableUiprefs\'] %s)'
1544 PMA_Util
::showDocu('config', 'cfg_Servers_MaxTableUiprefs')
1547 $message->addMessage('<br /><br />');
1548 $message->addMessage(
1549 PMA_Message
::rawError(
1550 $this->_dbi
->getError($GLOBALS['controllink'])
1562 * Loads the UI preferences for this table.
1563 * If pmadb and table_uiprefs is set, it will load the UI preferences from
1564 * phpMyAdmin database.
1568 protected function loadUiPrefs()
1570 $cfgRelation = PMA_getRelationsParam();
1571 $server_id = $GLOBALS['server'];
1573 // set session variable if it's still undefined
1574 if (!isset($_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name
][$this->_name
])) {
1575 // check whether we can get from pmadb
1576 $_SESSION['tmpval']['table_uiprefs'][$server_id][$this->_db_name
]
1577 [$this->_name
] = $cfgRelation['uiprefswork']
1578 ?
$this->getUiPrefsFromDb()
1581 $this->uiprefs
=& $_SESSION['tmpval']['table_uiprefs'][$server_id]
1582 [$this->_db_name
][$this->_name
];
1586 * Get a property from UI preferences.
1587 * Return false if the property is not found.
1588 * Available property:
1589 * - PROP_SORTED_COLUMN
1590 * - PROP_COLUMN_ORDER
1591 * - PROP_COLUMN_VISIB
1593 * @param string $property property
1597 public function getUiProp($property)
1599 if (! isset($this->uiprefs
)) {
1600 $this->loadUiPrefs();
1602 // do checking based on property
1603 if ($property == self
::PROP_SORTED_COLUMN
) {
1604 if (isset($this->uiprefs
[$property])) {
1605 if (! isset($_REQUEST['discard_remembered_sort'])) {
1606 // check if the column name exists in this table
1607 $tmp = explode(' ', $this->uiprefs
[$property]);
1609 //remove backquoting from colname
1610 $colname = str_replace('`', '', $colname);
1611 //get the available column name without backquoting
1612 $avail_columns = $this->getColumns(false);
1614 foreach ($avail_columns as $each_col) {
1615 // check if $each_col ends with $colname
1619 /*overload*/mb_strlen($each_col)
1620 - /*overload*/mb_strlen($colname)
1623 return $this->uiprefs
[$property];
1627 // remove the property, since it no longer exists in database
1628 $this->removeUiProp(self
::PROP_SORTED_COLUMN
);
1633 } elseif ($property == self
::PROP_COLUMN_ORDER
1634 ||
$property == self
::PROP_COLUMN_VISIB
1636 if (! $this->isView()
1637 && isset($this->uiprefs
[$property])
1639 // check if the table has not been modified
1640 if ($this->sGetStatusInfo('Create_time') == $this->uiprefs
['CREATE_TIME']) {
1641 return $this->uiprefs
[$property];
1643 // remove the property, since the table has been modified
1644 $this->removeUiProp(self
::PROP_COLUMN_ORDER
);
1651 // default behaviour for other property:
1652 return isset($this->uiprefs
[$property]) ?
$this->uiprefs
[$property] : false;
1656 * Set a property from UI preferences.
1657 * If pmadb and table_uiprefs is set, it will save the UI preferences to
1658 * phpMyAdmin database.
1659 * Available property:
1660 * - PROP_SORTED_COLUMN
1661 * - PROP_COLUMN_ORDER
1662 * - PROP_COLUMN_VISIB
1664 * @param string $property Property
1665 * @param mixed $value Value for the property
1666 * @param string $table_create_time Needed for PROP_COLUMN_ORDER
1667 * and PROP_COLUMN_VISIB
1669 * @return boolean|PMA_Message
1671 public function setUiProp($property, $value, $table_create_time = null)
1673 if (! isset($this->uiprefs
)) {
1674 $this->loadUiPrefs();
1676 // we want to save the create time if the property is PROP_COLUMN_ORDER
1677 if (! $this->isView()
1678 && ($property == self
::PROP_COLUMN_ORDER
1679 ||
$property == self
::PROP_COLUMN_VISIB
)
1681 $curr_create_time = $this->sGetStatusInfo('CREATE_TIME');
1682 if (isset($table_create_time)
1683 && $table_create_time == $curr_create_time
1685 $this->uiprefs
['CREATE_TIME'] = $curr_create_time;
1687 // there is no $table_create_time, or
1688 // supplied $table_create_time is older than current create time,
1690 return PMA_Message
::error(
1693 'Cannot save UI property "%s". The changes made will ' .
1694 'not be persistent after you refresh this page. ' .
1695 'Please check if the table structure has been changed.'
1703 $this->uiprefs
[$property] = $value;
1705 // check if pmadb is set
1706 $cfgRelation = PMA_getRelationsParam();
1707 if ($cfgRelation['uiprefswork']) {
1708 return $this->saveUiprefsToDb();
1714 * Remove a property from UI preferences.
1716 * @param string $property the property
1718 * @return true|PMA_Message
1720 public function removeUiProp($property)
1722 if (! isset($this->uiprefs
)) {
1723 $this->loadUiPrefs();
1725 if (isset($this->uiprefs
[$property])) {
1726 unset($this->uiprefs
[$property]);
1728 // check if pmadb is set
1729 $cfgRelation = PMA_getRelationsParam();
1730 if ($cfgRelation['uiprefswork']) {
1731 return $this->saveUiprefsToDb();
1738 * Get all column names which are MySQL reserved words
1743 public function getReservedColumnNames()
1745 $columns = $this->getColumns(false);
1747 foreach ($columns as $column) {
1748 $temp = explode('.', $column);
1749 $column_name = $temp[2];
1750 if (SqlParser\Context
::isKeyword($column_name, true)) {
1751 $return[] = $column_name;
1758 * Function to get the name and type of the columns of a table
1762 public function getNameAndTypeOfTheColumns()
1765 foreach ($this->_dbi
->getColumnsFull(
1766 $this->_db_name
, $this->_name
1768 if (preg_match('@^(set|enum)\((.+)\)$@i', $row['Type'], $tmp)) {
1769 $tmp[2] = /*overload*/
1771 preg_replace('@([^,])\'\'@', '\\1\\\'', ',' . $tmp[2]), 1
1773 $columns[$row['Field']] = $tmp[1] . '('
1774 . str_replace(',', ', ', $tmp[2]) . ')';
1776 $columns[$row['Field']] = $row['Type'];
1783 * Get index with index name
1785 * @param string $index Index name
1789 public function getIndex($index)
1791 return PMA_Index
::singleton($this->_db_name
, $this->_name
, $index);
1795 * Function to get the sql query for index creation or edit
1797 * @param PMA_Index $index current index
1798 * @param bool &$error whether error occurred or not
1802 public function getSqlQueryForIndexCreateOrEdit($index, &$error)
1804 // $sql_query is the one displayed in the query box
1805 $sql_query = sprintf(
1806 'ALTER TABLE %s.%s',
1807 PMA_Util
::backquote($this->_db_name
),
1808 PMA_Util
::backquote($this->_name
)
1811 // Drops the old index
1812 if (! empty($_REQUEST['old_index'])) {
1813 if ($_REQUEST['old_index'] == 'PRIMARY') {
1814 $sql_query .= ' DROP PRIMARY KEY,';
1816 $sql_query .= sprintf(
1818 PMA_Util
::backquote($_REQUEST['old_index'])
1823 // Builds the new one
1824 switch ($index->getChoice()) {
1826 if ($index->getName() == '') {
1827 $index->setName('PRIMARY');
1828 } elseif ($index->getName() != 'PRIMARY') {
1829 $error = PMA_Message
::error(
1830 __('The name of the primary key must be "PRIMARY"!')
1833 $sql_query .= ' ADD PRIMARY KEY';
1839 if ($index->getName() == 'PRIMARY') {
1840 $error = PMA_Message
::error(
1841 __('Can\'t rename index to PRIMARY!')
1844 $sql_query .= sprintf(
1848 if ($index->getName()) {
1849 $sql_query .= PMA_Util
::backquote($index->getName());
1854 $index_fields = array();
1855 foreach ($index->getColumns() as $key => $column) {
1856 $index_fields[$key] = PMA_Util
::backquote($column->getName());
1857 if ($column->getSubPart()) {
1858 $index_fields[$key] .= '(' . $column->getSubPart() . ')';
1862 if (empty($index_fields)) {
1863 $error = PMA_Message
::error(__('No index parts defined!'));
1865 $sql_query .= ' (' . implode(', ', $index_fields) . ')';
1868 $keyBlockSizes = $index->getKeyBlockSize();
1869 if (! empty($keyBlockSizes)) {
1870 $sql_query .= sprintf(
1871 ' KEY_BLOCK_SIZE = ',
1872 PMA_Util
::sqlAddSlashes($keyBlockSizes)
1876 // specifying index type is allowed only for primary, unique and index only
1877 $type = $index->getType();
1878 if ($index->getChoice() != 'SPATIAL'
1879 && $index->getChoice() != 'FULLTEXT'
1880 && in_array($type, PMA_Index
::getIndexTypes())
1882 $sql_query .= ' USING ' . $type;
1885 $parser = $index->getParser();
1886 if ($index->getChoice() == 'FULLTEXT' && ! empty($parser)) {
1887 $sql_query .= ' WITH PARSER ' . PMA_Util
::sqlAddSlashes($parser);
1890 $comment = $index->getComment();
1891 if (! empty($comment)) {
1892 $sql_query .= sprintf(
1894 PMA_Util
::sqlAddSlashes($comment)
1904 * Function to handle update for display field
1906 * @param string $disp current display field
1907 * @param string $display_field display field
1908 * @param array $cfgRelation configuration relation
1910 * @return boolean True on update succeed or False on failure
1912 public function updateDisplayField($disp, $display_field, $cfgRelation)
1916 if ($display_field == '') {
1917 $upd_query = 'DELETE FROM '
1918 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1919 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
1920 . ' WHERE db_name = \''
1921 . PMA_Util
::sqlAddSlashes($this->db_name
) . '\''
1922 . ' AND table_name = \''
1923 . PMA_Util
::sqlAddSlashes($this->name
) . '\'';
1924 } elseif ($disp != $display_field) {
1925 $upd_query = 'UPDATE '
1926 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1927 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
1928 . ' SET display_field = \''
1929 . PMA_Util
::sqlAddSlashes($display_field) . '\''
1930 . ' WHERE db_name = \''
1931 . PMA_Util
::sqlAddSlashes($this->db_name
) . '\''
1932 . ' AND table_name = \''
1933 . PMA_Util
::sqlAddSlashes($this->name
) . '\'';
1935 } elseif ($display_field != '') {
1936 $upd_query = 'INSERT INTO '
1937 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1938 . '.' . PMA_Util
::backquote($cfgRelation['table_info'])
1939 . '(db_name, table_name, display_field) VALUES('
1940 . '\'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\','
1941 . '\'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\','
1942 . '\'' . PMA_Util
::sqlAddSlashes($display_field) . '\')';
1948 $GLOBALS['controllink'],
1958 * Function to get update query for updating internal relations
1960 * @param array $multi_edit_columns_name multi edit column names
1961 * @param array $destination_db destination tables
1962 * @param array $destination_table destination tables
1963 * @param array $destination_column destination columns
1964 * @param array $cfgRelation configuration relation
1965 * @param array|null $existrel db, table, column
1969 public function updateInternalRelations($multi_edit_columns_name,
1970 $destination_db, $destination_table, $destination_column,
1971 $cfgRelation, $existrel
1974 foreach ($destination_db as $master_field_md5 => $foreign_db) {
1976 // Map the fieldname's md5 back to its real name
1977 $master_field = $multi_edit_columns_name[$master_field_md5];
1978 $foreign_table = $destination_table[$master_field_md5];
1979 $foreign_field = $destination_column[$master_field_md5];
1980 if (! empty($foreign_db)
1981 && ! empty($foreign_table)
1982 && ! empty($foreign_field)
1984 if (! isset($existrel[$master_field])) {
1985 $upd_query = 'INSERT INTO '
1986 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
1987 . '.' . PMA_Util
::backquote($cfgRelation['relation'])
1988 . '(master_db, master_table, master_field, foreign_db,'
1989 . ' foreign_table, foreign_field)'
1991 . '\'' . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\', '
1992 . '\'' . PMA_Util
::sqlAddSlashes($this->_name
) . '\', '
1993 . '\'' . PMA_Util
::sqlAddSlashes($master_field) . '\', '
1994 . '\'' . PMA_Util
::sqlAddSlashes($foreign_db) . '\', '
1995 . '\'' . PMA_Util
::sqlAddSlashes($foreign_table) . '\','
1996 . '\'' . PMA_Util
::sqlAddSlashes($foreign_field) . '\')';
1998 } elseif ($existrel[$master_field]['foreign_db'] != $foreign_db
1999 ||
$existrel[$master_field]['foreign_table'] != $foreign_table
2000 ||
$existrel[$master_field]['foreign_field'] != $foreign_field
2002 $upd_query = 'UPDATE '
2003 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2004 . '.' . PMA_Util
::backquote($cfgRelation['relation'])
2005 . ' SET foreign_db = \''
2006 . PMA_Util
::sqlAddSlashes($foreign_db) . '\', '
2007 . ' foreign_table = \''
2008 . PMA_Util
::sqlAddSlashes($foreign_table) . '\', '
2009 . ' foreign_field = \''
2010 . PMA_Util
::sqlAddSlashes($foreign_field) . '\' '
2011 . ' WHERE master_db = \''
2012 . PMA_Util
::sqlAddSlashes($this->_db_name
) . '\''
2013 . ' AND master_table = \''
2014 . PMA_Util
::sqlAddSlashes($this->_name
) . '\''
2015 . ' AND master_field = \''
2016 . PMA_Util
::sqlAddSlashes($master_field) . '\'';
2017 } // end if... else....
2018 } elseif (isset($existrel[$master_field])) {
2019 $upd_query = 'DELETE FROM '
2020 . PMA_Util
::backquote($GLOBALS['cfgRelation']['db'])
2021 . '.' . PMA_Util
::backquote($cfgRelation['relation'])
2022 . ' WHERE master_db = \''
2023 . PMA_Util
::sqlAddSlashes($this->db_name
) . '\''
2024 . ' AND master_table = \''
2025 . PMA_Util
::sqlAddSlashes($this->name
) . '\''
2026 . ' AND master_field = \''
2027 . PMA_Util
::sqlAddSlashes($master_field) . '\'';
2028 } // end if... else....
2030 if (isset($upd_query)) {
2033 $GLOBALS['controllink'],
2044 * Function to handle foreign key updates
2046 * @param array $destination_foreign_db destination foreign database
2047 * @param array $multi_edit_columns_name multi edit column names
2048 * @param array $destination_foreign_table destination foreign table
2049 * @param array $destination_foreign_column destination foreign column
2050 * @param array $options_array options array
2051 * @param string $table current table
2052 * @param array $existrel_foreign db, table, column
2056 public function updateForeignKeys($destination_foreign_db,
2057 $multi_edit_columns_name, $destination_foreign_table,
2058 $destination_foreign_column, $options_array, $table, $existrel_foreign
2061 $preview_sql_data = '';
2062 $display_query = '';
2063 $seen_error = false;
2065 foreach ($destination_foreign_db as $master_field_md5 => $foreign_db) {
2069 // Map the fieldname's md5 back to its real name
2070 $master_field = $multi_edit_columns_name[$master_field_md5];
2072 $foreign_table = $destination_foreign_table[$master_field_md5];
2073 $foreign_field = $destination_foreign_column[$master_field_md5];
2075 if (isset($existrel_foreign[$master_field_md5]['ref_db_name'])) {
2076 $ref_db_name = $existrel_foreign[$master_field_md5]['ref_db_name'];
2078 $ref_db_name = $GLOBALS['db'];
2081 $empty_fields = false;
2082 foreach ($master_field as $key => $one_field) {
2083 if ((! empty($one_field) && empty($foreign_field[$key]))
2084 ||
(empty($one_field) && ! empty($foreign_field[$key]))
2086 $empty_fields = true;
2089 if (empty($one_field) && empty($foreign_field[$key])) {
2090 unset($master_field[$key]);
2091 unset($foreign_field[$key]);
2095 if (! empty($foreign_db)
2096 && ! empty($foreign_table)
2099 if (isset($existrel_foreign[$master_field_md5])) {
2101 = $existrel_foreign[$master_field_md5]['constraint'];
2102 $on_delete = !empty(
2103 $existrel_foreign[$master_field_md5]['on_delete']
2105 ?
$existrel_foreign[$master_field_md5]['on_delete']
2107 $on_update = ! empty(
2108 $existrel_foreign[$master_field_md5]['on_update']
2110 ?
$existrel_foreign[$master_field_md5]['on_update']
2113 if ($ref_db_name != $foreign_db
2114 ||
$existrel_foreign[$master_field_md5]['ref_table_name'] != $foreign_table
2115 ||
$existrel_foreign[$master_field_md5]['ref_index_list'] != $foreign_field
2116 ||
$existrel_foreign[$master_field_md5]['index_list'] != $master_field
2117 ||
$_REQUEST['constraint_name'][$master_field_md5] != $constraint_name
2118 ||
($_REQUEST['on_delete'][$master_field_md5] != $on_delete)
2119 ||
($_REQUEST['on_update'][$master_field_md5] != $on_update)
2121 // another foreign key is already defined for this field
2122 // or an option has been changed for ON DELETE or ON UPDATE
2125 } // end if... else....
2127 // no key defined for this field(s)
2130 } elseif (isset($existrel_foreign[$master_field_md5])) {
2132 } // end if... else....
2134 $tmp_error_drop = false;
2136 $drop_query = 'ALTER TABLE ' . PMA_Util
::backquote($table)
2137 . ' DROP FOREIGN KEY ' . PMA_Util
::backquote($existrel_foreign[$master_field_md5]['constraint']) . ';';
2139 if (! isset($_REQUEST['preview_sql'])) {
2140 $display_query .= $drop_query . "\n";
2141 $this->_dbi
->tryQuery($drop_query);
2142 $tmp_error_drop = $this->_dbi
->getError();
2144 if (! empty($tmp_error_drop)) {
2146 $html_output .= PMA_Util
::mysqlDie(
2147 $tmp_error_drop, $drop_query, false, '', false
2152 $preview_sql_data .= $drop_query . "\n";
2155 $tmp_error_create = false;
2160 $create_query = $this->_getSQLToCreateForeignKey(
2161 $table, $master_field, $foreign_db, $foreign_table, $foreign_field,
2162 $_REQUEST['constraint_name'][$master_field_md5],
2163 $options_array[$_REQUEST['on_delete'][$master_field_md5]],
2164 $options_array[$_REQUEST['on_update'][$master_field_md5]]
2167 if (! isset($_REQUEST['preview_sql'])) {
2168 $display_query .= $create_query . "\n";
2169 $this->_dbi
->tryQuery($create_query);
2170 $tmp_error_create = $this->_dbi
->getError();
2171 if (! empty($tmp_error_create)) {
2174 if (substr($tmp_error_create, 1, 4) == '1005') {
2175 $message = PMA_Message
::error(
2177 'Error creating foreign key on %1$s (check data ' .
2181 $message->addParam(implode(', ', $master_field));
2182 $html_output .= $message->getDisplay();
2184 $html_output .= PMA_Util
::mysqlDie(
2185 $tmp_error_create, $create_query, false, '', false
2188 $html_output .= PMA_Util
::showMySQLDocu(
2189 'InnoDB_foreign_key_constraints'
2193 $preview_sql_data .= $create_query . "\n";
2196 // this is an alteration and the old constraint has been dropped
2197 // without creation of a new one
2198 if ($drop && $create && empty($tmp_error_drop)
2199 && ! empty($tmp_error_create)
2201 // a rollback may be better here
2202 $sql_query_recreate = '# Restoring the dropped constraint...' . "\n";
2203 $sql_query_recreate .= $this->_getSQLToCreateForeignKey(
2206 $existrel_foreign[$master_field_md5]['ref_db_name'],
2207 $existrel_foreign[$master_field_md5]['ref_table_name'],
2208 $existrel_foreign[$master_field_md5]['ref_index_list'],
2209 $existrel_foreign[$master_field_md5]['constraint'],
2210 $options_array[$existrel_foreign[$master_field_md5]['on_delete']],
2211 $options_array[$existrel_foreign[$master_field_md5]['on_update']]
2213 if (! isset($_REQUEST['preview_sql'])) {
2214 $display_query .= $sql_query_recreate . "\n";
2215 $this->_dbi
->tryQuery($sql_query_recreate);
2217 $preview_sql_data .= $sql_query_recreate;
2231 * Returns the SQL query for foreign key constraint creation
2233 * @param string $table table name
2234 * @param array $field field names
2235 * @param string $foreignDb foreign database name
2236 * @param string $foreignTable foreign table name
2237 * @param array $foreignField foreign field names
2238 * @param string $name name of the constraint
2239 * @param string $onDelete on delete action
2240 * @param string $onUpdate on update action
2242 * @return string SQL query for foreign key constraint creation
2244 private function _getSQLToCreateForeignKey(
2254 $sql_query = 'ALTER TABLE ' . PMA_Util
::backquote($table) . ' ADD ';
2255 // if user entered a constraint name
2256 if (! empty($name)) {
2257 $sql_query .= ' CONSTRAINT ' . PMA_Util
::backquote($name);
2260 foreach ($field as $key => $one_field) {
2261 $field[$key] = PMA_Util
::backquote($one_field);
2263 foreach ($foreignField as $key => $one_field) {
2264 $foreignField[$key] = PMA_Util
::backquote($one_field);
2266 $sql_query .= ' FOREIGN KEY (' . implode(', ', $field) . ')'
2267 . ' REFERENCES ' . PMA_Util
::backquote($foreignDb)
2268 . '.' . PMA_Util
::backquote($foreignTable)
2269 . '(' . implode(', ', $foreignField) . ')';
2271 if (! empty($onDelete)) {
2272 $sql_query .= ' ON DELETE ' . $onDelete;
2274 if (! empty($onUpdate)) {
2275 $sql_query .= ' ON UPDATE ' . $onUpdate;
2283 * Returns the generation expression for virtual columns
2285 * @param string $column name of the column
2287 * @return array associative array of column name and their expressions
2289 public function getColumnGenerationExpression($column = null)
2291 $serverType = PMA_Util
::getServerType();
2292 if ($serverType == 'MySQL'
2293 && PMA_MYSQL_INT_VERSION
> 50705
2294 && ! $GLOBALS['cfg']['Server']['DisableIS']
2298 `COLUMN_NAME` AS `Field`,
2299 `GENERATION_EXPRESSION` AS `Expression`
2301 `information_schema`.`COLUMNS`
2303 `TABLE_SCHEMA` = '" . PMA_Util
::sqlAddSlashes($this->_db_name
) . "'
2304 AND `TABLE_NAME` = '" . PMA_Util
::sqlAddSlashes($this->_name
) . "'";
2305 if ($column != null) {
2306 $sql .= " AND `COLUMN_NAME` = '" . PMA_Util
::sqlAddSlashes($column)
2309 $columns = $this->_dbi
->fetchResult($sql, 'Field', 'Expression');
2313 $createTable = $this->showCreate();
2314 if (!$createTable) {
2318 $parser = new SqlParser\
Parser($createTable);
2320 * @var CreateStatement $stmt
2322 $stmt = $parser->statements
[0];
2323 $fields = SqlParser\Utils\Table
::getFields($stmt);
2324 if ($column != null) {
2325 $expression = isset($fields[$column]['expr']) ?
2326 substr($fields[$column]['expr'], 1, -1) : '';
2327 return array($column => $expression);
2331 foreach ($fields as $field => $options) {
2332 if (isset($options['expr'])) {
2333 $ret[$field] = substr($options['expr'], 1, -1);
2340 * Returns the CREATE statement for this table
2344 public function showCreate()
2346 return $this->_dbi
->fetchValue(
2347 'SHOW CREATE TABLE ' . PMA_Util
::backquote($this->_db_name
) . '.'
2348 . PMA_Util
::backquote($this->_name
),